package org.editorconfig.configmanagement;

import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.fileTypes.PlainTextLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import org.editorconfig.core.EditorConfig.OutPair;
import org.editorconfig.plugincomponents.SettingsProviderComponent;
import org.editorconfig.Utils;
import org.jetbrains.annotations.NotNull;

import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.List;

public class CodeStyleManager extends FileEditorManagerAdapter implements WindowFocusListener {
    // Handles the following EditorConfig settings:
    private static final String indentSizeKey = "indent_size";
    private static final String tabWidthKey = "tab_width";
    private static final String indentStyleKey = "indent_style";

    private static final Logger LOG = 
            Logger.getInstance("#org.editorconfig.configmanagement.CodeStyleManager");
    private final CodeStyleSettingsManager codeStyleSettingsManager;
    private final Project project;

    public CodeStyleManager(Project project) {
        codeStyleSettingsManager = CodeStyleSettingsManager.getInstance(project);
        this.project = project;
    }

    @Override
    public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
        applySettings(file);
    }

    @Override
    public void selectionChanged(@NotNull FileEditorManagerEvent event) {
        final VirtualFile file = event.getNewFile();
        applySettings(file);
    }

    @Override
    public void windowGainedFocus(WindowEvent e) {
        final Editor currentEditor = FileEditorManager.getInstance(project).getSelectedTextEditor();
        if (currentEditor != null) {
            final Document currentDocument = currentEditor.getDocument();
            final VirtualFile currentFile = FileDocumentManager.getInstance().getFile(currentDocument);
            applySettings(currentFile);
        }
    }

    @Override
    public void windowLostFocus(WindowEvent e) {}
    
    private void applySettings(final VirtualFile file) {
        if (file != null && file.isInLocalFileSystem()) {
            // Always drop any current temporary settings so that the defaults will be applied if
            // this is a non-editorconfig-managed file
            codeStyleSettingsManager.dropTemporarySettings();
            // Prepare a new settings object, which will maintain the standard settings if no
            // editorconfig settings apply
            final CodeStyleSettings currentSettings = codeStyleSettingsManager.getCurrentSettings();
            final CodeStyleSettings newSettings = new CodeStyleSettings();
            newSettings.copyFrom(currentSettings);
            // Get editorconfig settings
            final String filePath = file.getCanonicalPath();
            final SettingsProviderComponent settingsProvider = SettingsProviderComponent.getInstance();
            final List<OutPair> outPairs = settingsProvider.getOutPairs(filePath);
            // Apply editorconfig settings for the current editor
            applyCodeStyleSettings(outPairs, newSettings, file);
            codeStyleSettingsManager.setTemporarySettings(newSettings);
            final EditorEx currentEditor = (EditorEx) FileEditorManager.getInstance(project).getSelectedTextEditor();
            if (currentEditor != null){
                currentEditor.reinitSettings();
            }
        }
    }

    private void applyCodeStyleSettings(final List<OutPair> outPairs, final CodeStyleSettings codeStyleSettings,
                                        final VirtualFile file) {
        // Apply indent options
        final String indentSize = Utils.configValueForKey(outPairs, indentSizeKey);
        final String tabWidth = Utils.configValueForKey(outPairs, tabWidthKey);
        final String indentStyle = Utils.configValueForKey(outPairs, indentStyleKey);
        final FileType fileType = file.getFileType();
        final Language language = fileType instanceof LanguageFileType ? ((LanguageFileType)fileType).getLanguage() :
            PlainTextLanguage.INSTANCE;
        final CommonCodeStyleSettings commonSettings = codeStyleSettings.getCommonSettings(language);
        final CommonCodeStyleSettings.IndentOptions indentOptions = commonSettings.getIndentOptions();
        applyIndentOptions(indentOptions, indentSize, tabWidth, indentStyle, file.getCanonicalPath());
    }

    private void applyIndentOptions(CommonCodeStyleSettings.IndentOptions indentOptions,
                                    String indentSize, String tabWidth, String indentStyle, String filePath) {
        final String calculatedIndentSize = calculateIndentSize(tabWidth, indentSize);
        final String calculatedTabWidth = calculateTabWidth(tabWidth, indentSize);
        if (!calculatedIndentSize.isEmpty()) {
            if (applyIndentSize(indentOptions, calculatedIndentSize)) {
                LOG.debug(Utils.appliedConfigMessage(calculatedIndentSize, indentSizeKey, filePath));
            } else {
                LOG.warn(Utils.invalidConfigMessage(calculatedIndentSize, indentSizeKey, filePath));
            }
        }
        if (!calculatedTabWidth.isEmpty()) {
            if (applyTabWidth(indentOptions, calculatedTabWidth)) {
                LOG.debug(Utils.appliedConfigMessage(calculatedTabWidth, tabWidthKey, filePath));
            } else {
                LOG.warn(Utils.invalidConfigMessage(calculatedTabWidth, tabWidthKey, filePath));
            }
        }
        if (!indentStyle.isEmpty()) {
            if (applyIndentStyle(indentOptions, indentStyle)) {
                LOG.debug(Utils.appliedConfigMessage(indentStyle, indentStyleKey, filePath));

            } else {
                LOG.warn(Utils.invalidConfigMessage(indentStyle, indentStyleKey, filePath));
            }
        }
    }

    private String calculateIndentSize(final String tabWidth, final String indentSize) {
        return indentSize.equals("tab") ? tabWidth : indentSize;
    }

    private String calculateTabWidth(final String tabWidth, final String indentSize) {
        if (tabWidth.isEmpty() && indentSize.equals("tab")) {
            return "";
        } else if (tabWidth.isEmpty()) {
            return indentSize;
        } else {
            return tabWidth;
        }
    }

    private boolean applyIndentSize(final CommonCodeStyleSettings.IndentOptions indentOptions, final String indentSize) {
        try {
            int indent = Integer.parseInt(indentSize);
            indentOptions.INDENT_SIZE = indent;
            indentOptions.CONTINUATION_INDENT_SIZE = indent;
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    private boolean applyTabWidth(final CommonCodeStyleSettings.IndentOptions indentOptions, final String tabWidth) {
        try {
            indentOptions.TAB_SIZE = Integer.parseInt(tabWidth);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    private boolean applyIndentStyle(CommonCodeStyleSettings.IndentOptions indentOptions, String indentStyle) {
        if (indentStyle.equals("tab") || indentStyle.equals("space")) {
            indentOptions.USE_TAB_CHARACTER = indentStyle.equals("tab");
            return true;
        } else {
            return false;
        }
    }
}