package be.iminds.ilabt.jfed.preferences;

import be.iminds.ilabt.jfed.lowlevel.ssh_key_info.InvalidSshKeyInfoException;
import be.iminds.ilabt.jfed.lowlevel.ssh_key_info.PuTTYFilesKeyInfo;
import be.iminds.ilabt.jfed.ssh_terminal_tool.ssh_key_info.PreferencesSshKeyInfoFactory;
import be.iminds.ilabt.jfed.ssh_terminal_tool.unix.TerminalFinder;
import be.iminds.ilabt.jfed.util.OSDetector;
import be.iminds.ilabt.jfed.util.SshVersionFinder;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.Objects;

/**
 * User: twalcari
 * Date: 11/18/13
 * Time: 6:03 PM
 */
public class JFedGuiPreferences extends JFedCorePreferences {
    private final static Logger LOG = LoggerFactory.getLogger(JFedGuiPreferences.class);

    private static final String DEFAULT_PUTTY_DIR = "PuTTY";
    private static final String[] DEFAULT_PUTTY_LOCATIONS =
            new String[]{System.getenv("ProgramFiles") + File.separator + DEFAULT_PUTTY_DIR,
                         System.getenv("ProgramFiles(x86)") + File.separator + DEFAULT_PUTTY_DIR, "C:\\PuTTY"};


    public JFedGuiPreferences() {
        super();

        config.setAutoSave(true);
    }

    protected void fillinBlanks(PropertiesConfiguration config) {
        super.fillinBlanks(config);

        //translate old key for backward compatibility
        if (config.containsKey("sshkeys.override")) {
            config.setProperty(GuiPreferenceKey.PREF_SSH_USE_CUSTOM_KEY.key, true);
            config.clearProperty("sshkeys.override");
        }

        if (OSDetector.getUnixLikeOsFilter().includedOs(OSDetector.os)) {
            if (!config.containsKey(GuiPreferenceKey.PREF_COMMAND_UNIX_TERMINAL.key))
                config.setProperty(GuiPreferenceKey.PREF_COMMAND_UNIX_TERMINAL.key, findUnixTerminalCommand());

            boolean autoSshCommand = getBoolean(GuiPreferenceKey.PREF_COMMAND_SSH_AUTO, true);

            if (autoSshCommand || !config.containsKey(GuiPreferenceKey.PREF_COMMAND_SSH.key))
                config.setProperty(GuiPreferenceKey.PREF_COMMAND_SSH.key, "ssh -A -X -i '$i' $u@$h -oPort=$p");

            final String defaultUnixSshCommandWithoutNewOpenSsh =
                    "ssh -A -X -i '$i' $u@$h -oPort=$p -oProxyCommand=\"ssh -e none -i '$I' -oPort=$P $U@$H nc -w 5 %h %p\"";
            final String defaultUnixshCommandWithNewOpenSsh =
                    "ssh -A -X -i '$i' $u@$h -oPort=$p -oProxyCommand=\"ssh -i '$I' -oPort=$P $U@$H -W %h:%p\"";

            if (autoSshCommand ||
                    (!config.containsKey(GuiPreferenceKey.PREF_COMMAND_SSH_PROXY.key)) ||
                    Objects.equals(config.getProperty(GuiPreferenceKey.PREF_COMMAND_SSH_PROXY.key), defaultUnixSshCommandWithoutNewOpenSsh)) {
                /**
                 * OpenSSH version >= 5.4 supports the -W host:port option, which does the netcat trick, without netcat.
                 *
                 *      -W host:port
                 *            Requests that standard input and output on the client be
                 *            forwarded to host on port over the secure channel.  Implies -N,
                 *            -T, ExitOnForwardFailure and ClearAllForwardings.  Works with
                 *            Protocol version 2 only.
                 * */
                boolean newOpenSsh = false;
                //we are on unix, so we call openssh:
                //   ssh -v
                // should return something starting with: OpenSSH_5.3p1 Debian-3ubuntu7.1, OpenSSL 0.9.8k 25 M...
                //  On newer version, it's:   ssh -V
                // so we have to try both


                Boolean supportsNc = SshVersionFinder.checkIfVersionSupportsNc();
                if (supportsNc == null) {
                    LOG.debug("OpenSSH version could not be determined: unknown if -nc option is supported");
                    newOpenSsh = false;
                } else {
                    newOpenSsh = supportsNc;
                }

                LOG.debug("newOpenSsh=" + newOpenSsh + "");

                if (newOpenSsh)
                    config.setProperty(GuiPreferenceKey.PREF_COMMAND_SSH_PROXY.key, defaultUnixshCommandWithNewOpenSsh);
                else
                    config.setProperty(GuiPreferenceKey.PREF_COMMAND_SSH_PROXY.key, defaultUnixSshCommandWithoutNewOpenSsh);
            }
        }


        if (OSDetector.findOS() == OSDetector.OS.WIN) {
            //try to find settings for PuTTY
            if (!config.containsKey(GuiPreferenceKey.PREF_PUTTY_DIRECTORY.key)) {
                //try to detect the location of PuTTY
                String puttyDir = JFedGuiPreferences.findPuTTYDirectory();

                boolean enablePageant = true;

                if (puttyDir != null) {
                    config.setProperty(GuiPreferenceKey.PREF_PUTTY_DIRECTORY.key, puttyDir);
                    enablePageant = JFedGuiPreferences.pageantExists(new File(puttyDir));
                } else {
                    //disable pageant, as we didn't find a suitable PuTTY-dir
                    enablePageant = false;
                }

                if (!config.containsKey(GuiPreferenceKey.PREF_SSHAGENT_USE.key))
                    config.setProperty(GuiPreferenceKey.PREF_SSHAGENT_USE.key, enablePageant);

            }


        }

        if (!config.containsKey(GuiPreferenceKey.PREF_FIRSTRUN_VERSION.key))
            config.setProperty(GuiPreferenceKey.PREF_FIRSTRUN_VERSION.key, 0);

        if (!config.containsKey(GuiPreferenceKey.PREF_SSHAGENT_USE.key))
            config.setProperty(GuiPreferenceKey.PREF_SSHAGENT_USE.key, true);

        if (!config.containsKey(GuiPreferenceKey.PREF_SSH_USE_LOGIN_KEY.key))
            config.setProperty(GuiPreferenceKey.PREF_SSH_USE_LOGIN_KEY.key, true);

        if (!config.containsKey(GuiPreferenceKey.PREF_SSH_USE_CUSTOM_KEY.key))
            config.setProperty(GuiPreferenceKey.PREF_SSH_USE_CUSTOM_KEY.key, false);

        if (!config.containsKey(GuiPreferenceKey.PREF_TERMINAL_KEEP_TMP_FILES.key))
            config.setProperty(GuiPreferenceKey.PREF_TERMINAL_KEEP_TMP_FILES.key, true);

        if (!config.containsKey(GuiPreferenceKey.PREF_TERMINAL_DEBUG.key))
            config.setProperty(GuiPreferenceKey.PREF_TERMINAL_DEBUG.key, true);

        if (!config.containsKey(GuiPreferenceKey.PREF_EXPIRATION_WARNING_ENABLED.key))
            config.setProperty(GuiPreferenceKey.PREF_EXPIRATION_WARNING_ENABLED.key, true);

        if (!config.containsKey(GuiPreferenceKey.PREF_EXPIRATION_WARNING_TIME.key))
            config.setProperty(GuiPreferenceKey.PREF_EXPIRATION_WARNING_TIME.key, 60);

        if (!config.containsKey(GuiPreferenceKey.PREF_RECOVER_EXPIRATION_WARNING_ENABLED.key))
            config.setProperty(GuiPreferenceKey.PREF_RECOVER_EXPIRATION_WARNING_ENABLED.key, true);

        if (!config.containsKey(GuiPreferenceKey.PREF_RECOVER_EXPIRATION_WARNING_TIME.key))
            config.setProperty(GuiPreferenceKey.PREF_RECOVER_EXPIRATION_WARNING_TIME.key, 110);

        //make sure at least one key option is true
        if (!getBoolean(GuiPreferenceKey.PREF_SSH_USE_LOGIN_KEY, false)
                && !getBoolean(GuiPreferenceKey.PREF_SSH_USE_CUSTOM_KEY, false))
            config.setProperty(GuiPreferenceKey.PREF_SSH_USE_LOGIN_KEY.key, true);
    }

    public PuTTYFilesKeyInfo getCustomSshKeyInfo() {
        if (getBoolean(GuiPreferenceKey.PREF_SSH_USE_CUSTOM_KEY)) {
            try {
                if (config.containsKey(GuiPreferenceKey.PREF_SSH_PUTTY_KEY_FILE.getKey())) {
                    return PreferencesSshKeyInfoFactory.createPreferencesPuttySshKeyInfo(this, GuiPreferenceKey.PREF_SSH_USE_CUSTOM_KEY,
                            GuiPreferenceKey.PREF_SSH_PUTTY_KEY_FILE);
                } else {
                    return PreferencesSshKeyInfoFactory.createPreferencesSshKeyInfo(this, GuiPreferenceKey.PREF_SSH_USE_CUSTOM_KEY,
                            GuiPreferenceKey.PREF_SSH_PUBLIC_KEY_FILE,
                            GuiPreferenceKey.PREF_SSH_PRIVATE_KEY_FILE,
                            GuiPreferenceKey.PREF_SSH_PUBLIC_KEY,
                            GuiPreferenceKey.PREF_SSH_PRIVATE_KEY);
                }
            } catch (InvalidSshKeyInfoException ex) {
                LOG.warn("Could not create a valid SshKeyInfo from the preferences", ex);
                return null;
            }
        } else
            return null;
    }

    public boolean isSshAgentAvailable() {
        if (OSDetector.os == OSDetector.OS.WIN) {
            String puttyDir = getString(GuiPreferenceKey.PREF_PUTTY_DIRECTORY);
            if (puttyDir == null) return false;
            return new File(puttyDir, "pageant.exe").exists();
        } else {
            //linux or mac
            if (OSDetector.os == OSDetector.OS.MAC)
                return false;
            return true;
        }
    }

    public static boolean isValidPuttyDir(File puttyDir) {
        if (puttyDir == null) return false;
//        LOG.debug("isValidPuttyDir("+puttyDir.getPath()+") exists="+puttyDir.exists()+" isDir="+puttyDir.isDirectory());
        if (!puttyDir.exists()) return false;
        if (!puttyDir.isDirectory()) return false;
        if (!new File(puttyDir, "putty.exe").exists()) return false;
//        if (!new File(puttyDir, "puttygen.exe").exists()) return false;
        if (!new File(puttyDir, "plink.exe").exists()) return false;
        return true;
    }


    public static String findPuTTYDirectory() {
        for (String location : DEFAULT_PUTTY_LOCATIONS) {
            File file = new File(location);
            if (isValidPuttyDir(file))
                return location;
        }
        return null;

    }

    private static boolean pageantExists(File puttyDir) {
        return new File(puttyDir, "pageant.exe").exists();
    }


    private String autoFindUnixTerminalCommand;

    private String findUnixTerminalCommand() {
        if (autoFindUnixTerminalCommand == null) {
            if (OSDetector.os == OSDetector.OS.UNIX)
                autoFindUnixTerminalCommand = TerminalFinder.findLinuxTerminalCommand();

            else if (OSDetector.os == OSDetector.OS.MAC)
                autoFindUnixTerminalCommand = "tell application \"Terminal\" to do script \"%\"";
        }
        if (autoFindUnixTerminalCommand != null) {
            LOG.info("Falling back to auto-detected terminal command: " + autoFindUnixTerminalCommand);
        }
        return autoFindUnixTerminalCommand;

    }

    public boolean isExpirationWarningEnabled() {
        return getBoolean(GuiPreferenceKey.PREF_EXPIRATION_WARNING_ENABLED, true);
    }


    public void setExpirationWarningEnabled(boolean enabled) {
        setBoolean(GuiPreferenceKey.PREF_EXPIRATION_WARNING_ENABLED, enabled);
    }


    public int getExpirationWarningTime() {
        return getInt(GuiPreferenceKey.PREF_EXPIRATION_WARNING_TIME, 60);
    }


    public void setExpirationWarningTime(int value) {
        setInt(GuiPreferenceKey.PREF_EXPIRATION_WARNING_TIME, value);
    }


    public boolean isRecoverExpirationWarningEnabled() {
        return getBoolean(GuiPreferenceKey.PREF_RECOVER_EXPIRATION_WARNING_ENABLED, true);
    }


    public void setRecoverExpirationWarningEnabled(boolean enabled) {
        setBoolean(GuiPreferenceKey.PREF_RECOVER_EXPIRATION_WARNING_ENABLED, enabled);
    }


    public int getRecoverExpirationWarningTime() {
        return getInt(GuiPreferenceKey.PREF_RECOVER_EXPIRATION_WARNING_TIME, 10);
    }


    public void setRecoverExpirationWarningTime(int value) {
        setInt(GuiPreferenceKey.PREF_RECOVER_EXPIRATION_WARNING_TIME, value);
    }

    public boolean getAddProjectMembersSshKeysByDefault() {
        return getBoolean(GuiPreferenceKey.PREF_ADD_PROJECT_MEMBERS_SSH_KEYS, false);
    }

    public void setAddProjectMembersSshKeysByDefault(boolean value) {
        setBoolean(GuiPreferenceKey.PREF_ADD_PROJECT_MEMBERS_SSH_KEYS, value);
    }
}
