/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
/*
 * Created on Feb 22, 2005
 *
 * @author Fabio Zadrozny
 */
package org.python.pydev.plugin.preferences;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
import org.python.pydev.editor.StyledTextForShowingCodeFactory;
import org.python.pydev.editor.actions.PyFormatStd.FormatStd;
import org.python.pydev.editor.preferences.PyScopedPreferences;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.field_editors.BooleanFieldEditorCustom;
import org.python.pydev.shared_ui.field_editors.ComboFieldEditor;
import org.python.pydev.shared_ui.field_editors.LinkFieldEditor;
import org.python.pydev.shared_ui.field_editors.ScopedFieldEditorPreferencePage;
import org.python.pydev.shared_ui.field_editors.ScopedPreferencesFieldEditor;

/**
 * @author Fabio Zadrozny
 */
public class PyCodeFormatterPage extends ScopedFieldEditorPreferencePage implements IWorkbenchPreferencePage {

    public static final String FORMAT_WITH_AUTOPEP8 = "FORMAT_WITH_AUTOPEP8";
    public static final boolean DEFAULT_FORMAT_WITH_AUTOPEP8 = false;

    public static final String AUTOPEP8_PARAMETERS = "AUTOPEP8_PARAMETERS";

    public static final String FORMAT_ONLY_CHANGED_LINES = "FORMAT_ONLY_CHANGED_LINES";
    public static final boolean DEFAULT_FORMAT_ONLY_CHANGED_LINES = false;

    public static final String TRIM_LINES = "TRIM_EMPTY_LINES";
    public static final boolean DEFAULT_TRIM_LINES = false;

    public static final String TRIM_MULTILINE_LITERALS = "TRIM_MULTILINE_LITERALS";
    public static final boolean DEFAULT_TRIM_MULTILINE_LITERALS = false;

    public static final String ADD_NEW_LINE_AT_END_OF_FILE = "ADD_NEW_LINE_AT_END_OF_FILE";
    public static final boolean DEFAULT_ADD_NEW_LINE_AT_END_OF_FILE = true;

    //a, b, c
    public static final String USE_SPACE_AFTER_COMMA = "USE_SPACE_AFTER_COMMA";
    public static final boolean DEFAULT_USE_SPACE_AFTER_COMMA = true;

    //call( a )
    public static final String USE_SPACE_FOR_PARENTESIS = "USE_SPACE_FOR_PARENTESIS";
    public static final boolean DEFAULT_USE_SPACE_FOR_PARENTESIS = false;

    //call(a = 1)
    public static final String USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS = "USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS";
    public static final boolean DEFAULT_USE_ASSIGN_WITH_PACES_INSIDE_PARENTESIS = false;

    //operators =, !=, <, >, //, etc.
    public static final String USE_OPERATORS_WITH_SPACE = "USE_OPERATORS_WITH_SPACE";
    public static final boolean DEFAULT_USE_OPERATORS_WITH_SPACE = true;

    //Spaces before '#'.
    public static final String SPACES_BEFORE_COMMENT = "SPACES_BEFORE_COMMENT";
    public static final int DEFAULT_SPACES_BEFORE_COMMENT = 2; //pep-8 says 2 spaces before inline comment.

    //Spaces after '#'.
    public static final String SPACES_IN_START_COMMENT = "SPACES_IN_START_COMMENT";
    public static final int DEFAULT_SPACES_IN_START_COMMENT = 1; //pep-8 says 1 space after '#'

    private StyledText labelExample;
    private BooleanFieldEditorCustom formatWithAutoPep8;
    private BooleanFieldEditorCustom spaceAfterComma;
    private BooleanFieldEditorCustom onlyChangedLines;
    private BooleanFieldEditorCustom spaceForParentesis;
    private BooleanFieldEditorCustom assignWithSpaceInsideParentesis;
    private BooleanFieldEditorCustom operatorsWithSpace;
    private BooleanFieldEditorCustom rightTrimLines;
    private BooleanFieldEditorCustom rightTrimMultilineLiterals;
    private BooleanFieldEditorCustom addNewLineAtEndOfFile;
    private StyledTextForShowingCodeFactory formatAndStyleRangeHelper;
    private ComboFieldEditor spacesBeforeComment;
    private ComboFieldEditor spacesInStartComment;
    private Composite fieldParent;
    private StringFieldEditor autopep8Parameters;
    private LinkFieldEditor autopep8Link;
    private boolean disposed = false;

    public PyCodeFormatterPage() {
        super(GRID);
        setPreferenceStore(PydevPlugin.getDefault().getPreferenceStore());
    }

    private static final String[][] ENTRIES_AND_VALUES_FOR_SPACES = new String[][] {
            { "Don't change manual formatting", Integer.toString(FormatStd.DONT_HANDLE_SPACES) },
            { "No spaces", "0" },
            { "1 space", "1" },
            { "2 spaces", "2" },
            { "3 spaces", "3" },
            { "4 spaces", "4" },
    };

    private static final String[][] ENTRIES_AND_VALUES_FOR_SPACES2 = new String[][] {
            { "Don't change manual formatting", Integer.toString(FormatStd.DONT_HANDLE_SPACES) }, //0 and -1 means the same thing here.
            { "At least 1 space", "1" },
            { "At least 2 spaces", "2" },
            { "At least 3 spaces", "3" },
            { "At least 4 spaces", "4" },
    };

    /**
     * @see org.eclipse.jface.preference.FieldEditorPreferencePage#createFieldEditors()
     */
    @Override
    public void createFieldEditors() {
        Composite p = getFieldEditorParent();
        this.fieldParent = p;

        addField(new LinkFieldEditor("link_saveactions", "Note: view <a>save actions</a> to auto-format on save.", p,
                new SelectionListener() {

                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        String id = "org.python.pydev.editor.saveactions.PydevSaveActionsPrefPage";
                        IWorkbenchPreferenceContainer workbenchPreferenceContainer = ((IWorkbenchPreferenceContainer) getContainer());
                        workbenchPreferenceContainer.openPage(id, null);
                    }

                    @Override
                    public void widgetDefaultSelected(SelectionEvent e) {
                    }
                }));

        formatWithAutoPep8 = createBooleanFieldEditorCustom(FORMAT_WITH_AUTOPEP8,
                "Use autopep8.py for code formatting?", p);
        addField(formatWithAutoPep8);

        autopep8Link = new LinkFieldEditor("link_autopep8_interpreter",
                "Note: the default configured <a>Python Interpreter</a> will be used to execute autopep8.py", p,
                new SelectionListener() {

                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        String id = "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPagePython";
                        IWorkbenchPreferenceContainer workbenchPreferenceContainer = ((IWorkbenchPreferenceContainer) getContainer());
                        workbenchPreferenceContainer.openPage(id, null);
                    }

                    @Override
                    public void widgetDefaultSelected(SelectionEvent e) {
                    }
                });
        addField(autopep8Link);

        autopep8Parameters = new StringFieldEditor(AUTOPEP8_PARAMETERS,
                "Parameters for autopep8 (i.e.: -a for aggressive, --ignore E24)", p);
        addField(autopep8Parameters);

        onlyChangedLines = createBooleanFieldEditorCustom(FORMAT_ONLY_CHANGED_LINES,
                "On save, only apply formatting in changed lines?", p);
        addField(onlyChangedLines);

        spaceAfterComma = createBooleanFieldEditorCustom(USE_SPACE_AFTER_COMMA, "Use space after commas?", p);
        addField(spaceAfterComma);

        spaceForParentesis = createBooleanFieldEditorCustom(USE_SPACE_FOR_PARENTESIS,
                "Use space before and after parenthesis?", p);
        addField(spaceForParentesis);

        assignWithSpaceInsideParentesis = createBooleanFieldEditorCustom(USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS,
                "Use space before and after assign for keyword arguments?", p);
        addField(assignWithSpaceInsideParentesis);

        operatorsWithSpace = createBooleanFieldEditorCustom(USE_OPERATORS_WITH_SPACE,
                "Use space before and after operators? (+, -, /, *, //, **, etc.)", p);
        addField(operatorsWithSpace);

        rightTrimLines = createBooleanFieldEditorCustom(TRIM_LINES, "Right trim lines?", p);
        addField(rightTrimLines);

        rightTrimMultilineLiterals = createBooleanFieldEditorCustom(TRIM_MULTILINE_LITERALS,
                "Right trim multi-line string literals?", p);
        addField(rightTrimMultilineLiterals);

        addNewLineAtEndOfFile = createBooleanFieldEditorCustom(ADD_NEW_LINE_AT_END_OF_FILE,
                "Add new line at end of file?", p);
        addField(addNewLineAtEndOfFile);

        spacesBeforeComment = new ComboFieldEditor(SPACES_BEFORE_COMMENT, "Spaces before a comment?",
                ENTRIES_AND_VALUES_FOR_SPACES, p);
        addField(spacesBeforeComment);

        spacesInStartComment = new ComboFieldEditor(SPACES_IN_START_COMMENT, "Spaces in comment start?",
                ENTRIES_AND_VALUES_FOR_SPACES2, p);
        addField(spacesInStartComment);

        formatAndStyleRangeHelper = new StyledTextForShowingCodeFactory();
        labelExample = formatAndStyleRangeHelper.createStyledTextForCodePresentation(p);
        GridData layoutData = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1);
        labelExample.setLayoutData(layoutData);

        addField(new ScopedPreferencesFieldEditor(p, PydevPlugin.DEFAULT_PYDEV_SCOPE, this));
    }

    @Override
    protected void initialize() {
        super.initialize();

        //After initializing, let's check the proper state based on pep8.
        Button checkBox = formatWithAutoPep8.getCheckBox(fieldParent);
        checkBox.addSelectionListener(new SelectionListener() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                updateState();
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
            }
        });
        updateState();

        // And update the example when it's already there
        updateLabelExampleNow(this.getFormatFromEditorContents());
    }

    private void updateState() {
        if (formatWithAutoPep8.getBooleanValue()) {
            assignWithSpaceInsideParentesis.setEnabled(false, fieldParent);
            operatorsWithSpace.setEnabled(false, fieldParent);
            spaceForParentesis.setEnabled(false, fieldParent);
            spaceAfterComma.setEnabled(false, fieldParent);
            addNewLineAtEndOfFile.setEnabled(false, fieldParent);
            rightTrimLines.setEnabled(false, fieldParent);
            rightTrimMultilineLiterals.setEnabled(false, fieldParent);
            spacesBeforeComment.setEnabled(false, fieldParent);
            spacesInStartComment.setEnabled(false, fieldParent);
            onlyChangedLines.setEnabled(false, fieldParent);
            autopep8Parameters.setEnabled(true, fieldParent);
            autopep8Link.setEnabled(true, fieldParent);
        } else {
            assignWithSpaceInsideParentesis.setEnabled(true, fieldParent);
            operatorsWithSpace.setEnabled(true, fieldParent);
            spaceForParentesis.setEnabled(true, fieldParent);
            spaceAfterComma.setEnabled(true, fieldParent);
            addNewLineAtEndOfFile.setEnabled(true, fieldParent);
            rightTrimLines.setEnabled(true, fieldParent);
            rightTrimMultilineLiterals.setEnabled(true, fieldParent);
            spacesBeforeComment.setEnabled(true, fieldParent);
            spacesInStartComment.setEnabled(true, fieldParent);
            onlyChangedLines.setEnabled(true, fieldParent);
            autopep8Parameters.setEnabled(false, fieldParent);
            autopep8Link.setEnabled(false, fieldParent);
        }

    }

    private BooleanFieldEditorCustom createBooleanFieldEditorCustom(String name, String label, Composite parent) {
        return new BooleanFieldEditorCustom(name, label, BooleanFieldEditor.SEPARATE_LABEL, parent);
    }

    // Note: no locking is needed since we're doing everything in the UI thread.
    private Runnable currentRunnable;

    private void updateLabelExample(final FormatStd formatStd) {
        if (!disposed) {
            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    if (disposed) {
                        currentRunnable = null;
                        return;
                    }

                    if (currentRunnable == this) {
                        updateLabelExampleNow(formatStd);
                        currentRunnable = null;
                    }
                }
            };
            currentRunnable = runnable;
            // Give a timeout before updating (otherwise when changing the text for the autopep8 integration
            // it becomes slow).
            Display.getCurrent().timerExec(400, runnable);
        }
    }

    private void updateLabelExampleNow(FormatStd formatStd) {

        String str = "class Example(object):             \n" +
                "                                   \n" +
                "    def Call(self, param1=None):   \n" +
                "        '''docstring'''            \n" +
                "        return param1 + 10 * 10    \n" +
                "                                   \n" +
                "    def Call2(self): #Comment      \n" +
                "        #Comment                   \n" +
                "        return self.Call(param1=10)" +
                "";
        Tuple<String, StyleRange[]> result = formatAndStyleRangeHelper.formatAndGetStyleRanges(formatStd, str,
                PydevPrefs.getChainedPrefStore(), true);
        labelExample.setText(result.o1);
        labelExample.setStyleRanges(result.o2);
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        super.propertyChange(event);
        updateLabelExample(getFormatFromEditorContents());
    }

    @Override
    protected void performDefaults() {
        super.performDefaults();
        updateLabelExample(getFormatFromEditorContents());
        updateState();
    }

    private FormatStd getFormatFromEditorContents() {
        FormatStd formatStd = new FormatStd();
        formatStd.assignWithSpaceInsideParens = this.assignWithSpaceInsideParentesis.getBooleanValue();
        formatStd.operatorsWithSpace = operatorsWithSpace.getBooleanValue();
        formatStd.parametersWithSpace = spaceForParentesis.getBooleanValue();
        formatStd.spaceAfterComma = spaceAfterComma.getBooleanValue();
        formatStd.addNewLineAtEndOfFile = addNewLineAtEndOfFile.getBooleanValue();
        formatStd.trimLines = rightTrimLines.getBooleanValue();
        formatStd.trimMultilineLiterals = rightTrimMultilineLiterals.getBooleanValue();
        formatStd.spacesBeforeComment = Integer.parseInt(spacesBeforeComment.getComboValue());
        formatStd.spacesInStartComment = Integer.parseInt(spacesInStartComment.getComboValue());
        formatStd.formatWithAutopep8 = this.formatWithAutoPep8.getBooleanValue();
        formatStd.autopep8Parameters = this.autopep8Parameters.getStringValue();
        formatStd.updateAutopep8();
        return formatStd;
    }

    /**
     * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
     */
    @Override
    public void init(IWorkbench workbench) {
    }

    public static boolean getFormatWithAutopep8(IAdaptable projectAdaptable) {
        return getBoolean(FORMAT_WITH_AUTOPEP8, projectAdaptable);
    }

    public static boolean getBoolean(String setting, IAdaptable projectAdaptable) {
        return PyScopedPreferences.getBoolean(setting, projectAdaptable);
    }

    protected static String getString(String setting, IAdaptable projectAdaptable) {
        return PyScopedPreferences.getString(setting, projectAdaptable);
    }

    public static String getAutopep8Parameters(IAdaptable projectAdaptable) {
        return getString(AUTOPEP8_PARAMETERS, projectAdaptable);
    }

    public static boolean getFormatOnlyChangedLines(IAdaptable projectAdaptable) {
        if (getFormatWithAutopep8(projectAdaptable)) {
            return false; //i.e.: not available with autopep8.
        }
        return getBoolean(FORMAT_ONLY_CHANGED_LINES, projectAdaptable);
    }

    public static boolean getAddNewLineAtEndOfFile(IAdaptable projectAdaptable) {
        return getBoolean(ADD_NEW_LINE_AT_END_OF_FILE, projectAdaptable);
    }

    public static boolean getTrimLines(IAdaptable projectAdaptable) {
        return getBoolean(TRIM_LINES, projectAdaptable);
    }

    public static boolean getTrimMultilineLiterals(IAdaptable projectAdaptable) {
        return getBoolean(TRIM_MULTILINE_LITERALS, projectAdaptable);
    }

    public static boolean useSpaceAfterComma(IAdaptable projectAdaptable) {
        return getBoolean(USE_SPACE_AFTER_COMMA, projectAdaptable);
    }

    public static boolean useSpaceForParentesis(IAdaptable projectAdaptable) {
        return getBoolean(USE_SPACE_FOR_PARENTESIS, projectAdaptable);
    }

    public static boolean useAssignWithSpacesInsideParenthesis(IAdaptable projectAdaptable) {
        return getBoolean(USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS, projectAdaptable);
    }

    public static boolean useOperatorsWithSpace(IAdaptable projectAdaptable) {
        return getBoolean(USE_OPERATORS_WITH_SPACE, projectAdaptable);
    }

    public static int getSpacesBeforeComment(IAdaptable projectAdaptable) {
        return PyScopedPreferences.getInt(SPACES_BEFORE_COMMENT, projectAdaptable, FormatStd.DONT_HANDLE_SPACES);
    }

    public static int getSpacesInStartComment(IAdaptable projectAdaptable) {
        return PyScopedPreferences.getInt(SPACES_IN_START_COMMENT, projectAdaptable, FormatStd.DONT_HANDLE_SPACES);
    }

    @Override
    public void dispose() {
        disposed = true;
        super.dispose();
        formatAndStyleRangeHelper.dispose();
    }

}
