package com.jscs.settings;

import com.jscs.JscsProjectComponent;
import com.jscs.cli.JscsFinder;
import com.jscs.cli.JscsRunner;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.execution.ExecutionException;
import com.intellij.javascript.nodejs.NodeDetectionUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiManager;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.TextFieldWithHistory;
import com.intellij.ui.TextFieldWithHistoryWithBrowseButton;
import com.intellij.util.NotNullProducer;
import com.intellij.util.ui.UIUtil;
import com.intellij.webcore.ui.SwingHelper;
import com.jscs.cli.JscsSettings;
import com.wix.settings.ValidationInfo;
import com.wix.settings.ValidationUtils;
import com.wix.ui.PackagesNotificationPanel;
import com.wix.utils.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class JscsSettingsPage implements Configurable {
    public static final String FIX_IT = "Fix it";
    public static final String HOW_TO_USE_JSCS = "How to Use JSCS";
    public static final String HOW_TO_USE_LINK = "https://github.com/idok/jscs-plugin";
    protected Project project;

    private JCheckBox pluginEnabledCheckbox;
    private JTextField presetField;
    private JPanel panel;
    private JPanel errorPanel;
    private TextFieldWithHistoryWithBrowseButton jscsBinField;
    private TextFieldWithHistoryWithBrowseButton nodeInterpreterField;
    private TextFieldWithHistoryWithBrowseButton jscsrcFile;
    private JRadioButton searchForJscsrcInRadioButton;
    private JRadioButton useProjectJscsrcRadioButton;
    private HyperlinkLabel usageLink;
    private JLabel jscsConfigFilePathLabel;
    private JLabel rulesDirectoryLabel;
    private JLabel pathToJscsBinLabel;
    private JLabel nodeInterpreterLabel;
    private JCheckBox treatAllIssuesCheckBox;
    private JLabel versionLabel;
    private JCheckBox esnextCheckBox;
    private JTextField esprimaField;
    private final PackagesNotificationPanel packagesNotificationPanel;

    public JscsSettingsPage(@NotNull final Project project) {
        this.project = project;
        configBinField();
        configJscsRcField();
        configNodeField();
        useProjectJscsrcRadioButton.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                jscsrcFile.setEnabled(e.getStateChange() == ItemEvent.SELECTED);
            }
        });
        pluginEnabledCheckbox.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                boolean enabled = e.getStateChange() == ItemEvent.SELECTED;
                setEnabledState(enabled);
            }
        });

        this.packagesNotificationPanel = new PackagesNotificationPanel(project);
        errorPanel.add(this.packagesNotificationPanel.getComponent(), BorderLayout.CENTER);

        DocumentAdapter docAdp = new DocumentAdapter() {
            protected void textChanged(DocumentEvent e) {
                updateLaterInEDT();
            }
        };
        jscsBinField.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp);
        jscsrcFile.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp);
        nodeInterpreterField.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp);
//        rulesPathField.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp);
        presetField.getDocument().addDocumentListener(docAdp);
    }

    private void addDocumentListenerToComp(TextFieldWithHistoryWithBrowseButton field, DocumentAdapter docAdp) {
        field.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp);
    }

    private File getProjectPath() {
        if (project == null || project.getBaseDir() == null) {
            return null;
        }
        return new File(project.getBaseDir().getPath());
    }

    private void updateLaterInEDT() {
        UIUtil.invokeLaterIfNeeded(new Runnable() {
            public void run() {
                JscsSettingsPage.this.update();
            }
        });
    }

    private void update() {
        ApplicationManager.getApplication().assertIsDispatchThread();
        validate();
    }

    private void setEnabledState(boolean enabled) {
        jscsrcFile.setEnabled(enabled);
        presetField.setEnabled(enabled);
        searchForJscsrcInRadioButton.setEnabled(enabled);
        useProjectJscsrcRadioButton.setEnabled(enabled);
        jscsBinField.setEnabled(enabled);
        nodeInterpreterField.setEnabled(enabled);
        jscsConfigFilePathLabel.setEnabled(enabled);
        rulesDirectoryLabel.setEnabled(enabled);
        pathToJscsBinLabel.setEnabled(enabled);
        nodeInterpreterLabel.setEnabled(enabled);
        treatAllIssuesCheckBox.setEnabled(enabled);
        esnextCheckBox.setEnabled(enabled);
        esprimaField.setEnabled(enabled);
    }

    private void validateField(List<ValidationInfo> errors, TextFieldWithHistoryWithBrowseButton field, boolean allowEmpty, String message) {
        if (!ValidationUtils.validatePath(project, field.getChildComponent().getText(), allowEmpty)) {
            ValidationInfo error = new ValidationInfo(field.getChildComponent().getTextEditor(), message, FIX_IT);
            errors.add(error);
        }
    }

    private void validate() {
        if (!pluginEnabledCheckbox.isSelected()) {
            return;
        }
        List<ValidationInfo> errors = new ArrayList<ValidationInfo>();
        validateField(errors, jscsBinField, false, "Path to jscs is invalid {{LINK}}");
        validateField(errors, jscsrcFile, true, "Path to jscsrc is invalid {{LINK}}"); //Please correct path to
        validateField(errors, nodeInterpreterField, false, "Path to node interpreter is invalid {{LINK}}");
        if (errors.isEmpty()) {
            try {
                packagesNotificationPanel.removeAllLinkHandlers();
            } catch (Exception e) {
                e.printStackTrace();
            }
            packagesNotificationPanel.hide();
            getVersion();
        } else {
            packagesNotificationPanel.showErrors(errors);
        }
    }

    private JscsSettings settings;

    private void getVersion() {
        if (settings != null &&
            areEqual(nodeInterpreterField, settings.node) &&
            areEqual(jscsBinField, settings.jscsExecutablePath) &&
            settings.cwd.equals(project.getBasePath())
                ) {
            return;
        }
        settings = new JscsSettings();
        settings.node = nodeInterpreterField.getChildComponent().getText();
        settings.jscsExecutablePath = jscsBinField.getChildComponent().getText();
        settings.cwd = project.getBasePath();
        try {
            String version = JscsRunner.version(settings);
            versionLabel.setText(version.trim());
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    private static TextFieldWithHistory configWithDefaults(TextFieldWithHistoryWithBrowseButton field) {
        TextFieldWithHistory textFieldWithHistory = field.getChildComponent();
        textFieldWithHistory.setHistorySize(-1);
        textFieldWithHistory.setMinimumAndPreferredWidth(0);
        return textFieldWithHistory;
    }

    private void configBinField() {
        configWithDefaults(jscsBinField);
        SwingHelper.addHistoryOnExpansion(jscsBinField.getChildComponent(), new NotNullProducer<List<String>>() {
            @NotNull
            public List<String> produce() {
                List<File> newFiles = JscsFinder.searchForJscsBin(getProjectPath());
                return FileUtils.toAbsolutePath(newFiles);
            }
        });
        SwingHelper.installFileCompletionAndBrowseDialog(project, jscsBinField, "Select jscs.js cli", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor());
    }

    private void configJscsRcField() {
        TextFieldWithHistory textFieldWithHistory = configWithDefaults(jscsrcFile);
        SwingHelper.addHistoryOnExpansion(textFieldWithHistory, new NotNullProducer<List<String>>() {
            @NotNull
            public List<String> produce() {
                return JscsFinder.searchForJscsRCFiles(getProjectPath());
            }
        });
        SwingHelper.installFileCompletionAndBrowseDialog(project, jscsrcFile, "Select JSCS config", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor());
    }

    private void configNodeField() {
        TextFieldWithHistory textFieldWithHistory = configWithDefaults(nodeInterpreterField);
        SwingHelper.addHistoryOnExpansion(textFieldWithHistory, new NotNullProducer<List<String>>() {
            @NotNull
            public List<String> produce() {
                List<File> newFiles = NodeDetectionUtil.listAllPossibleNodeInterpreters();
                return FileUtils.toAbsolutePath(newFiles);
            }
        });
        SwingHelper.installFileCompletionAndBrowseDialog(project, nodeInterpreterField, "Select Node interpreter", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor());
    }

    @Nls
    @Override
    public String getDisplayName() {
        return "JSCS";
    }

    @Nullable
    @Override
    public String getHelpTopic() {
        return null;
    }

    @Nullable
    @Override
    public JComponent createComponent() {
        loadSettings();
        return panel;
    }

    private static boolean areEqual(TextFieldWithHistoryWithBrowseButton field, String value) {
        return field.getChildComponent().getText().equals(value);
    }

    @Override
    public boolean isModified() {
        Settings s = getSettings();
        Settings uiSettings = toSettings();
        return !s.isEqualTo(uiSettings);
    }

    private String getJscsRCFile() {
        return useProjectJscsrcRadioButton.isSelected() ? jscsrcFile.getChildComponent().getText() : "";
    }

    @Override
    public void apply() throws ConfigurationException {
        saveSettings();
        PsiManager.getInstance(project).dropResolveCaches();
    }

    protected void saveSettings() {
        Settings settings = getSettings();
        copyTo(settings);
        project.getComponent(JscsProjectComponent.class).validateSettings();
        DaemonCodeAnalyzer.getInstance(project).restart();
    }

    public Settings toSettings() {
        Settings settings = new Settings();
        copyTo(settings);
        return settings;
    }

    public void copyTo(Settings settings) {
        settings.pluginEnabled = pluginEnabledCheckbox.isSelected();
        settings.jscsExecutable = jscsBinField.getChildComponent().getText();
        settings.nodeInterpreter = nodeInterpreterField.getChildComponent().getText();
        settings.jscsrcFile = getJscsRCFile();
        settings.preset = presetField.getText();
        settings.esprima = esprimaField.getText();
        settings.esnext = esnextCheckBox.isSelected();
        settings.treatAllIssuesAsWarnings = treatAllIssuesCheckBox.isSelected();
    }

    protected void loadSettings() {
        Settings settings = getSettings();
        pluginEnabledCheckbox.setSelected(settings.pluginEnabled);
        jscsBinField.getChildComponent().setText(settings.jscsExecutable);
        jscsrcFile.getChildComponent().setText(settings.jscsrcFile);
        nodeInterpreterField.getChildComponent().setText(settings.nodeInterpreter);
        presetField.setText(settings.preset);
        esprimaField.setText(settings.esprima);
        useProjectJscsrcRadioButton.setSelected(StringUtils.isNotEmpty(settings.jscsrcFile));
        searchForJscsrcInRadioButton.setSelected(StringUtils.isEmpty(settings.jscsrcFile));
        jscsrcFile.setEnabled(useProjectJscsrcRadioButton.isSelected());
        treatAllIssuesCheckBox.setSelected(settings.treatAllIssuesAsWarnings);
        esnextCheckBox.setSelected(settings.esnext);
        setEnabledState(settings.pluginEnabled);
    }

    @Override
    public void reset() {
        loadSettings();
    }

    @Override
    public void disposeUIResources() {
    }

    protected Settings getSettings() {
        return Settings.getInstance(project);
    }

    private void createUIComponents() {
        // TODO: place custom component creation code here
        usageLink = SwingHelper.createWebHyperlink(HOW_TO_USE_JSCS, HOW_TO_USE_LINK);
    }
}