/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.javascript2.editor.formatter;

import java.awt.Component;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.AbstractPreferences;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.editor.settings.SimpleValueNames;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.editor.indent.api.Reformat;
import org.netbeans.modules.options.editor.spi.PreferencesCustomizer;
import org.netbeans.modules.options.editor.spi.PreviewProvider;
import org.netbeans.modules.javascript2.editor.formatter.CodeStyle.WrapStyle;
import org.openide.text.CloneableEditorSupport;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;

/**
 *
 * @author phrebejk
 *
 */
public class FmtOptions {

    private static final Logger LOGGER = Logger.getLogger(FmtOptions.class.getName());

    public static final String expandTabToSpaces = SimpleValueNames.EXPAND_TABS;
    public static final String tabSize = SimpleValueNames.TAB_SIZE;
    public static final String spacesPerTab = SimpleValueNames.SPACES_PER_TAB;
    public static final String indentSize = SimpleValueNames.INDENT_SHIFT_WIDTH;
    public static final String continuationIndentSize = "continuationIndentSize"; //NOI18N
    public static final String itemsInArrayDeclarationIndentSize = "itemsInArrayDeclarationIndentSize"; //NOI18N
    public static final String reformatComments = "reformatComments"; //NOI18N
    public static final String indentHtml = "indentHtml"; //NOI18N
    public static final String rightMargin = SimpleValueNames.TEXT_LIMIT_WIDTH;
    public static final String initialIndent = "init.indent"; //NOI18N

    public static final String functionDeclBracePlacement = "functionDeclBracePlacement"; //NOI18N
    public static final String classDeclBracePlacement = "classDeclBracePlacement"; //NOI18N
    public static final String ifBracePlacement = "ifBracePlacement"; //NOI18N
    public static final String forBracePlacement = "forBracePlacement"; //NOI18N
    public static final String whileBracePlacement = "whileBracePlacement"; //NOI18N
    public static final String switchBracePlacement = "switchBracePlacement"; //NOI18N
    public static final String catchBracePlacement = "catchBracePlacement"; //NOI18N
    public static final String withBracePlacement = "withBracePlacement"; //NOI18N
    public static final String objectLiteralContinuation = "objectLiteralContinuation"; //NOI18N

    public static final String blankLinesBeforeNamespace = "blankLinesBeforeNamespace"; //NOI18N
    public static final String blankLinesAfterNamespace = "blankLinesAfterNamespace"; //NOI18N
    public static final String blankLinesBeforeUse = "blankLinesBeforeUse"; //NOI18N
    public static final String blankLinesBeforeUseTrait = "blankLinesBeforeUseTrait"; //NOI18N
    public static final String blankLinesAfterUse = "blankLinesAfterUse"; //NOI18N
    public static final String blankLinesBeforeClass = "blankLinesBeforeClass"; //NOI18N
    public static final String blankLinesBeforeClassEnd = "blankLinesBeforeClassEnd"; //NOI18N
    public static final String blankLinesAfterClass = "blankLinesAfterClass"; //NOI18N
    public static final String blankLinesAfterClassHeader = "blankLinesAfterClassHeader"; //NOI18N
    public static final String blankLinesBeforeFields = "blankLinesBeforeField"; //NOI18N
    public static final String blankLinesBetweenFields = "blankLinesBetweenField"; //NOI18N
    public static final String blankLinesAfterFields = "blankLinesAfterField"; //NOI18N
    public static final String blankLinesGroupFieldsWithoutDoc = "blankLinesGroupFieldsWithoutDoc"; //NOI18N
    public static final String blankLinesBeforeFunction = "blankLinesBeforeFunction"; //NOI18N
    public static final String blankLinesAfterFunction = "blankLinesAfterFunction"; //NOI18N
    public static final String blankLinesBeforeFunctionEnd = "blankLinesBeforeFunctionEnd"; //NOI18N
    public static final String blankLinesAfterOpenPHPTag = "blankLinesAfterOpenPHPTag"; //NOI18N
    public static final String blankLinesAfterOpenPHPTagInHTML = "blankLinesAfterOpenPHPTagInHTML"; //NOI18N
    public static final String blankLinesBeforeClosePHPTag = "blankLinesBeforeClosePHPTag"; //NOI18N

    public static final String spaceBeforeWhile = "spaceBeforeWhile"; //NOI18N
    public static final String spaceBeforeElse = "spaceBeforeElse"; //NOI18N
    public static final String spaceBeforeCatch = "spaceBeforeCatch"; //NOI18N
    public static final String spaceBeforeFinally = "spaceBeforeFinally"; //NOI18N
    public static final String spaceBeforeAnonMethodDeclParen = "spaceBeforeAnonMethodDeclParen"; //NOI18N
    public static final String spaceBeforeMethodDeclParen = "spaceBeforeMethodDeclParen"; //NOI18N
    public static final String spaceBeforeMethodCallParen = "spaceBeforeMethodCallParen"; //NOI18N
    public static final String spaceBeforeIfParen = "spaceBeforeIfParen"; //NOI18N
    public static final String spaceBeforeForParen = "spaceBeforeForParen"; //NOI18N
    public static final String spaceBeforeWhileParen = "spaceBeforeWhileParen"; //NOI18N
    public static final String spaceBeforeCatchParen = "spaceBeforeCatchParen"; //NOI18N
    public static final String spaceBeforeSwitchParen = "spaceBeforeSwitchParen"; //NOI18N
    public static final String spaceBeforeWithParen = "spaceBeforeWithParen"; //NOI18N

    public static final String spaceAroundUnaryOps = "spaceAroundUnaryOps"; //NOI18N
    public static final String spaceAroundBinaryOps = "spaceAroundBinaryOps"; //NOI18N
    public static final String spaceAroundTernaryOps = "spaceAroundTernaryOps"; //NOI18N
    public static final String spaceAroundStringConcatOps = "spaceAroundStringConcatOps"; //NOI18N
    public static final String spaceAroundAssignOps = "spaceAroundAssignOps"; //NOI18N
    public static final String spaceAroundArrowOps = "spaceAroundArrowOps"; //NOI18N
    public static final String spaceAroundKeyValueOps = "spaceAroundKeyValueOps"; //NOI18N
    public static final String spaceAroundObjectOps = "spaceAroundObjectOps"; //NOI18N
    public static final String spaceBeforeClassDeclLeftBrace = "spaceBeforeClassDeclLeftBrace"; //NOI18N
    public static final String spaceBeforeMethodDeclLeftBrace = "spaceBeforeMethodDeclLeftBrace"; //NOI18N
    public static final String spaceBeforeIfLeftBrace = "spaceBeforeIfLeftBrace"; //NOI18N
    public static final String spaceBeforeElseLeftBrace = "spaceBeforeElseLeftBrace"; //NOI18N
    public static final String spaceBeforeWhileLeftBrace = "spaceBeforeWhileLeftBrace"; //NOI18N
    public static final String spaceBeforeForLeftBrace = "spaceBeforeForLeftBrace"; //NOI18N
    public static final String spaceBeforeDoLeftBrace = "spaceBeforeDoLeftBrace"; //NOI18N
    public static final String spaceBeforeSwitchLeftBrace = "spaceBeforeSwitchLeftBrace"; //NOI18N
    public static final String spaceBeforeTryLeftBrace = "spaceBeforeTryLeftBrace"; //NOI18N
    public static final String spaceBeforeCatchLeftBrace = "spaceBeforeCatchLeftBrace"; //NOI18N
    public static final String spaceBeforeFinallyLeftBrace = "spaceBeforeFinallyLeftBrace"; //NOI18N
    public static final String spaceBeforeWithLeftBrace = "spaceBeforeWithLeftBrace"; //NOI18N
//    public static final String spaceBeforeSynchronizedLeftBrace = "spaceBeforeSynchronizedLeftBrace"; //NOI18N
//    public static final String spaceBeforeStaticInitLeftBrace = "spaceBeforeStaticInitLeftBrace"; //NOI18N
//    public static final String spaceBeforeArrayInitLeftBrace = "spaceBeforeArrayInitLeftBrace"; //NOI18N
    public static final String spaceWithinParens = "spaceWithinParens"; //NOI18N
    public static final String spaceWithinArrayDeclParens = "spaceWithinArrayDeclParens"; //NOI18N
    public static final String spaceWithinMethodDeclParens = "spaceWithinMethodDeclParens"; //NOI18N
    public static final String spaceWithinMethodCallParens = "spaceWithinMethodCallParens"; //NOI18N
    public static final String spaceWithinIfParens = "spaceWithinIfParens"; //NOI18N
    public static final String spaceWithinForParens = "spaceWithinForParens"; //NOI18N
    public static final String spaceWithinWhileParens = "spaceWithinWhileParens"; //NOI18N
    public static final String spaceWithinSwitchParens = "spaceWithinSwitchParens"; //NOI18N
    public static final String spaceWithinCatchParens = "spaceWithinCatchParens"; //NOI18N
    public static final String spaceWithinWithParens = "spaceWithinWithParens"; //NOI18N
//    public static final String spaceWithinSynchronizedParens = "spaceWithinSynchronizedParens"; //NOI18N
    public static final String spaceWithinTypeCastParens = "spaceWithinTypeCastParens"; //NOI18N
//    public static final String spaceWithinAnnotationParens = "spaceWithinAnnotationParens"; //NOI18N
    public static final String spaceWithinBraces = "spaceWithinBraces"; //NOI18N
    public static final String spaceWithinArrayBrackets = "spaceWithinArrayBrackets"; //NOI18N
    public static final String spaceBeforeComma = "spaceBeforeComma"; //NOI18N
    public static final String spaceAfterComma = "spaceAfterComma"; //NOI18N
    public static final String spaceBeforeSemi = "spaceBeforeSemi"; //NOI18N
    public static final String spaceAfterSemi = "spaceAfterSemi"; //NOI18N
    public static final String spaceBeforeColon = "spaceBeforeColon"; //NOI18N
    public static final String spaceAfterColon = "spaceAfterColon"; //NOI18N

    public static final String placeElseOnNewLine = "placeElseOnNewLine"; //NOI18N
    public static final String placeWhileOnNewLine = "placeWhileOnNewLine"; //NOI18N
    public static final String placeCatchOnNewLine = "placeCatchOnNewLine"; //NOI18N
    public static final String placeFinallyOnNewLine = "placeFinallyOnNewLine"; //NOI18N
    public static final String alignMultilineMethodParams = "alignMultilineMethodParams"; //NOI18N
    public static final String alignMultilineCallArgs = "alignMultilineCallArgs"; //NOI18N
    public static final String alignMultilineImplements = "alignMultilineImplements"; //NOI18N
    public static final String alignMultilineParenthesized = "alignMultilineParenthesized"; //NOI18N
    public static final String alignMultilineBinaryOp = "alignMultilineBinaryOp"; //NOI18N
    public static final String alignMultilineTernaryOp = "alignMultilineTernaryOp"; //NOI18N
    public static final String alignMultilineAssignment = "alignMultilineAssignment"; //NOI18N
    public static final String alignMultilineFor = "alignMultilineFor"; //NOI18N
    public static final String alignMultilineArrayInit = "alignMultilineArrayInit"; //NOI18N

    public static final String groupAlignmentAssignment = "groupAlignmentAssignment"; //NOI18N
    public static final String groupAlignmentArrayInit = "groupAlignmentArrayInit"; //NOI18N

    public static final String wrapStatement = "wrapStatement"; //NOI18N
    public static final String wrapVariables = "wrapVariables"; //NOI18N
    public static final String wrapMethodParams = "wrapMethodParams"; //NOI18N
    public static final String wrapMethodCallArgs = "wrapMethodCallArgs"; //NOI18N
    public static final String wrapChainedMethodCalls = "wrapChainedMethodCalls"; //NOI18N
    public static final String wrapAfterDotInChainedMethodCalls = "wrapAfterDotInChainedMethodCalls"; //NOI18N
    public static final String wrapArrayInit = "wrapArrayInit"; //NOI18N
    public static final String wrapArrayInitItems = "wrapArrayInitItems"; //NOI18N
    public static final String wrapFor = "wrapFor"; //NOI18N
    public static final String wrapForStatement = "wrapForStatement"; //NOI18N
    public static final String wrapIfStatement = "wrapIfStatement"; //NOI18N
    public static final String wrapWhileStatement = "wrapWhileStatement"; //NOI18N
    public static final String wrapDoWhileStatement = "wrapDoWhileStatement"; //NOI18N
    public static final String wrapWithStatement = "wrapWithStatement"; //NOI18N
    public static final String wrapBinaryOps = "wrapBinaryOps"; //NOI18N
    public static final String wrapAfterBinaryOps = "wrapAfterBinaryOps"; //NOI18N
    public static final String wrapTernaryOps = "wrapTernaryOps"; //NOI18N
    public static final String wrapAfterTernaryOps = "wrapAfterTernaryOps"; //NOI18N
    public static final String wrapAssignOps = "wrapAssignOps"; //NOI18N
    public static final String wrapArrowOps = "wrapArrowOps"; //NOI18N
    public static final String wrapBlockBraces = "wrapBlockBraces";  //NOI18N
    public static final String wrapStatementsOnTheLine = "wrapStateMentsOnTheLine"; // NOI18N
    public static final String wrapObjects = "wrapObjects"; // NOI18N
    public static final String wrapProperties = "wrapProperties"; // NOI18N
    public static final String wrapDecorators = "wrapDecorators"; // NOI18N
    public static final String wrapClasses = "wrapClasses"; // NOI18N
    public static final String wrapElements = "wrapElements"; // NOI18N
    public static final String wrapClassExtends = "wrapClassExtends"; // NOI18N

    public static final String maxPreservedClassLines = "maxPreservedClassLines"; // NOI18N
    public static final String maxPreservedObjectLines = "maxPreservedObjectLines"; // NOI18N
    public static final String maxPreservedArrayLines = "maxPreservedArrayLines"; // NOI18N
    public static final String maxPreservedCodeLines = "maxPreservedCodeLines"; // NOI18N

    public static final String preferFullyQualifiedNames = "preferFullyQualifiedNames"; //NOI18N
    public static final String preferMultipleUseStatementsCombined = "preferMultipleUseStatementsCombined"; //NOI18N
    public static final String startUseWithNamespaceSeparator = "startUseWithNamespaceSeparator"; //NOI18N

    private FmtOptions() {}

    private static int getDefaultAsInt(String key) {
        return Integer.parseInt(defaults.get(key));
    }

    private static boolean getDefaultAsBoolean(String key) {
        return Boolean.parseBoolean(defaults.get(key));
    }

    private static String getDefaultAsString(String key) {
        return defaults.get(key);
    }

    // Private section ---------------------------------------------------------

    private static final String TRUE = "true";      // NOI18N
    private static final String FALSE = "false";    // NOI18N

    //opening brace styles
    public static final String OBRACE_NEWLINE = CodeStyle.BracePlacement.NEW_LINE.name();
    public static final String OBRACE_SAMELINE = CodeStyle.BracePlacement.SAME_LINE.name();
    public static final String OBRACE_PRESERVE = CodeStyle.BracePlacement.PRESERVE_EXISTING.name();
    public static final String OBRACE_NEWLINE_INDENTED = CodeStyle.BracePlacement.NEW_LINE_INDENTED.name();

    public static final String WRAP_ALWAYS = CodeStyle.WrapStyle.WRAP_ALWAYS.name();
    public static final String WRAP_IF_LONG = CodeStyle.WrapStyle.WRAP_IF_LONG.name();
    public static final String WRAP_NEVER = CodeStyle.WrapStyle.WRAP_NEVER.name();

    private static Map<String,String> defaults;

    static {
        createDefaults();
    }

    private static void createDefaults() {
        String defaultValues[][] = {
            { expandTabToSpaces, TRUE}, //NOI18N
            { tabSize, "8"}, //NOI18N
            { indentSize, "4"}, //NOI18N
            { continuationIndentSize, "8"}, //NOI18N
            { itemsInArrayDeclarationIndentSize, "4"}, // NOI18N
            { reformatComments, FALSE }, //NOI18N
            { indentHtml, TRUE }, //NOI18N
            { rightMargin, "80"}, //NOI18N
            { initialIndent, "0"}, //NOI18N

	    { functionDeclBracePlacement, OBRACE_PRESERVE },
            { classDeclBracePlacement, OBRACE_PRESERVE },
	    { ifBracePlacement, OBRACE_PRESERVE },
	    { forBracePlacement, OBRACE_PRESERVE },
	    { whileBracePlacement, OBRACE_PRESERVE },
	    { switchBracePlacement, OBRACE_PRESERVE },
	    { catchBracePlacement, OBRACE_PRESERVE },
            { withBracePlacement, OBRACE_PRESERVE },
            { objectLiteralContinuation, TRUE},

            { blankLinesBeforeNamespace, "1"}, //NOI18N
            { blankLinesAfterNamespace, "1"}, //NOI18N
            { blankLinesBeforeUse, "1"}, //NOI18N
            { blankLinesBeforeUseTrait, "1"}, //NOI18N
            { blankLinesAfterUse, "1"}, //NOI18N
            { blankLinesBeforeClass, "1"}, //NOI18N
            { blankLinesAfterClass, "1"}, //NOI18N
            { blankLinesAfterClassHeader, "0"}, //NOI18N
            { blankLinesBeforeClassEnd, "0"}, //NOI18N
            { blankLinesBeforeFields, "1"}, //NOI18N
	    { blankLinesGroupFieldsWithoutDoc, TRUE}, //NOI18N
	    { blankLinesBetweenFields, "1"}, //NOI18N
            { blankLinesAfterFields, "1"}, //NOI18N
            { blankLinesBeforeFunction, "1"}, //NOI18N
            { blankLinesAfterFunction, "1"}, //NOI18N
            { blankLinesBeforeFunctionEnd, "0"}, //NOI18N
	    { blankLinesAfterOpenPHPTag, "1"}, //NOI18N
	    { blankLinesAfterOpenPHPTagInHTML, "0"}, //NOI18N
	    { blankLinesBeforeClosePHPTag, "0"}, //NOI18N

            { spaceBeforeWhile, TRUE},
            { spaceBeforeElse, TRUE},
            { spaceBeforeCatch, TRUE},
            { spaceBeforeFinally, TRUE},
            { spaceBeforeAnonMethodDeclParen, TRUE},
            { spaceBeforeMethodDeclParen, FALSE},
            { spaceBeforeMethodCallParen, FALSE},
            { spaceBeforeIfParen, TRUE},
            { spaceBeforeForParen, TRUE},
            { spaceBeforeWhileParen, TRUE},
            { spaceBeforeCatchParen, TRUE},
            { spaceBeforeSwitchParen, TRUE},
            { spaceBeforeWithParen, TRUE},
            { spaceAroundUnaryOps, FALSE},
            { spaceAroundBinaryOps, TRUE},
            { spaceAroundTernaryOps, TRUE},
	    { spaceAroundStringConcatOps, TRUE},
	    { spaceAroundKeyValueOps, TRUE},
            { spaceAroundAssignOps, TRUE},
            { spaceAroundArrowOps, TRUE},
	    { spaceAroundObjectOps, FALSE},
            { spaceBeforeClassDeclLeftBrace, TRUE},
            { spaceBeforeMethodDeclLeftBrace, TRUE},
            { spaceBeforeIfLeftBrace, TRUE},
            { spaceBeforeElseLeftBrace, TRUE},
            { spaceBeforeWhileLeftBrace, TRUE},
            { spaceBeforeForLeftBrace, TRUE},
            { spaceBeforeDoLeftBrace, TRUE},
            { spaceBeforeSwitchLeftBrace, TRUE},
            { spaceBeforeTryLeftBrace, TRUE},
            { spaceBeforeCatchLeftBrace, TRUE},
            { spaceBeforeFinallyLeftBrace, TRUE},
            { spaceBeforeWithLeftBrace, TRUE},
//            { spaceBeforeSynchronizedLeftBrace, TRUE},
//            { spaceBeforeStaticInitLeftBrace, TRUE},
//            { spaceBeforeArrayInitLeftBrace, FALSE},
            { spaceWithinParens, FALSE},
	    { spaceWithinArrayDeclParens, FALSE},
            { spaceWithinMethodDeclParens, FALSE},
            { spaceWithinMethodCallParens, FALSE},
            { spaceWithinIfParens, FALSE},
            { spaceWithinForParens, FALSE},
            { spaceWithinWhileParens, FALSE},
            { spaceWithinSwitchParens, FALSE},
            { spaceWithinCatchParens, FALSE},
            { spaceWithinWithParens, FALSE},
//            { spaceWithinSynchronizedParens, FALSE},
            { spaceWithinTypeCastParens, FALSE},
//            { spaceWithinAnnotationParens, FALSE},
            { spaceWithinBraces, FALSE},
            { spaceWithinArrayBrackets, FALSE},
            { spaceBeforeComma, FALSE},
            { spaceAfterComma, TRUE},
            { spaceBeforeSemi, FALSE},
            { spaceAfterSemi, TRUE},
            { spaceBeforeColon, FALSE},
            { spaceAfterColon, TRUE},

	    { alignMultilineMethodParams, FALSE}, //NOI18N
            { alignMultilineCallArgs, FALSE}, //NOI18N
            { alignMultilineImplements, FALSE}, //NOI18N
            { alignMultilineParenthesized, FALSE}, //NOI18N
            { alignMultilineBinaryOp, FALSE}, //NOI18N
            { alignMultilineTernaryOp, FALSE}, //NOI18N
            { alignMultilineAssignment, FALSE}, //NOI18N
            { alignMultilineFor, FALSE}, //NOI18N
            { alignMultilineArrayInit, FALSE}, //NOI18N
            { placeElseOnNewLine, FALSE}, //NOI18N
            { placeWhileOnNewLine, FALSE}, //NOI18N
            { placeCatchOnNewLine, FALSE}, //NOI18N
            { placeFinallyOnNewLine, FALSE}, //NOI18N

            {groupAlignmentArrayInit, FALSE},
            {groupAlignmentAssignment, FALSE},

	    { wrapStatement, WRAP_ALWAYS}, //NOI18N
            { wrapVariables, WRAP_NEVER}, //NOI18N
            { wrapMethodParams, WRAP_NEVER}, //NOI18N
            { wrapMethodCallArgs, WRAP_NEVER}, //NOI18N
            { wrapChainedMethodCalls, WRAP_NEVER}, //NOI18N
            { wrapAfterDotInChainedMethodCalls, TRUE}, //NOI18N
            { wrapArrayInit, WRAP_NEVER}, //NOI18N
            { wrapArrayInitItems, WRAP_NEVER}, //NOI18N
            { wrapFor, WRAP_NEVER}, //NOI18N
            { wrapForStatement, WRAP_ALWAYS}, //NOI18N
            { wrapIfStatement, WRAP_ALWAYS}, //NOI18N
            { wrapWhileStatement, WRAP_ALWAYS}, //NOI18N
            { wrapDoWhileStatement, WRAP_ALWAYS}, //NOI18N
            { wrapWithStatement, WRAP_ALWAYS}, //NOI18N
            { wrapBinaryOps, WRAP_NEVER}, //NOI18N
            { wrapAfterBinaryOps, FALSE}, //NOI18N
            { wrapTernaryOps, WRAP_NEVER},
            { wrapAfterTernaryOps, FALSE}, //NOI18N
            { wrapAssignOps, WRAP_NEVER},
            { wrapArrowOps, WRAP_NEVER},
            { wrapBlockBraces, TRUE},
            { wrapStatementsOnTheLine, TRUE},
            { wrapObjects, WRAP_NEVER},
            { wrapProperties, WRAP_NEVER},
            { wrapDecorators, WRAP_ALWAYS},
            { wrapClasses, WRAP_ALWAYS},
            { wrapElements, WRAP_ALWAYS},
            { wrapClassExtends, WRAP_NEVER},

            { maxPreservedClassLines, "1"},
            { maxPreservedObjectLines, "1"},
            { maxPreservedArrayLines, "1"},
            { maxPreservedCodeLines, "1"},
            
            { preferFullyQualifiedNames, FALSE},
            { preferMultipleUseStatementsCombined, FALSE},
            { startUseWithNamespaceSeparator, FALSE}
        };

        defaults = new HashMap<String,String>();

        for (java.lang.String[] strings : defaultValues) {
            defaults.put(strings[0], strings[1]);
        }

    }

     // Support section ---------------------------------------------------------

    public static class CategorySupport implements ActionListener, DocumentListener, PreviewProvider, PreferencesCustomizer {

        public static final String OPTION_ID = "org.netbeans.modules.javascript2.editor.formatter.FormatingOptions.ID";

        private static final int LOAD = 0;
        private static final int STORE = 1;
        private static final int ADD_LISTENERS = 2;

        private static final ComboItem  bracePlacement[] = new ComboItem[] {
                new ComboItem( OBRACE_NEWLINE, "LBL_bp_NEWLINE" ), // NOI18N
		new ComboItem( OBRACE_NEWLINE_INDENTED, "LBL_bp_NEWLINE_INDENTED" ), // NOI18N
                new ComboItem( OBRACE_SAMELINE, "LBL_bp_SAMELINE" ), // NOI18N
                new ComboItem( OBRACE_PRESERVE, "LBL_bp_PRESERVE" ), // NOI18N
            };

	private static final ComboItem  wrap[] = new ComboItem[] {
                new ComboItem( WrapStyle.WRAP_ALWAYS.name(), "LBL_wrp_WRAP_ALWAYS" ), // NOI18N
                new ComboItem( WrapStyle.WRAP_IF_LONG.name(), "LBL_wrp_WRAP_IF_LONG" ), // NOI18N
                new ComboItem( WrapStyle.WRAP_NEVER.name(), "LBL_wrp_WRAP_NEVER" ) // NOI18N
            };

        private final String previewText;
//        private String forcedOptions[][];

//        private boolean changed = false;
//        private boolean loaded = false;
        private final String id;
        protected final JPanel panel;
        private final List<JComponent> components = new LinkedList<JComponent>();
        private JEditorPane previewPane;

        protected final Defaults.Provider provider;

        private final Preferences preferences;
        private final Preferences previewPrefs;
        private final String mimeType;

        protected CategorySupport(String mimeType, Defaults.Provider provider, Preferences preferences, String id,
                JPanel panel, String previewText, String[]... forcedOptions) {
            this.mimeType = mimeType;
            this.provider = provider;
            this.preferences = preferences;
            this.id = id;
            this.panel = panel;
            this.previewText = previewText != null ? previewText : NbBundle.getMessage(FmtOptions.class, "SAMPLE_Default"); //NOI18N

            // Scan the panel for its components
            scan(panel, components);

            // Initialize the preview preferences
            Preferences forcedPrefs = new PreviewPreferences();
            for (String[] option : forcedOptions) {
                forcedPrefs.put( option[0], option[1]);
            }
            this.previewPrefs = new ProxyPreferences(preferences, forcedPrefs);

            // Load and hook up all the components
            loadFrom(preferences);
            addListeners();
        }

        protected void addListeners() {
            scan(ADD_LISTENERS, null);
        }

        protected void loadFrom(Preferences preferences) {
//            loaded = true;
            scan(LOAD, preferences);
//            loaded = false;
        }
//
//        public void applyChanges() {
//            storeTo(preferences);
//        }
//
        protected void storeTo(Preferences p) {
            scan(STORE, p);
        }

        public void notifyChanged() {
//            if (loaded)
//                return;
            storeTo(preferences);
            refreshPreview();
        }

        // ActionListener implementation ---------------------------------------

        @Override
        public void actionPerformed(ActionEvent e) {
            notifyChanged();
        }

        // DocumentListener implementation -------------------------------------

        @Override
        public void insertUpdate(DocumentEvent e) {
            notifyChanged();
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            notifyChanged();
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            notifyChanged();
        }

        // PreviewProvider methods -----------------------------------------------------

        @Override
        public JComponent getPreviewComponent() {
            if (previewPane == null) {
                previewPane = new JEditorPane();
                previewPane.getAccessibleContext().setAccessibleName(NbBundle.getMessage(FmtOptions.class, "AN_Preview")); //NOI18N
                previewPane.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(FmtOptions.class, "AD_Preview")); //NOI18N
                //previewPane.putClientProperty("HighlightsLayerIncludes", "^org\\.netbeans\\.modules\\.editor\\.lib2\\.highlighting\\.SyntaxHighlighting$"); //NOI18N
                previewPane.setEditorKit(CloneableEditorSupport.getEditorKit(mimeType));
                previewPane.setEditable(false);
            }
            return previewPane;
        }

        @Override
        public void refreshPreview() {
            JEditorPane pane = (JEditorPane) getPreviewComponent();
            try {
                int rm = previewPrefs.getInt(rightMargin, provider.getDefaultAsInt(rightMargin));
                pane.putClientProperty("TextLimitLine", rm); //NOI18N
            }
            catch( NumberFormatException e ) {
                // Ignore it
            }

            Rectangle visibleRectangle = pane.getVisibleRect();
            pane.setText(previewText);
            pane.setIgnoreRepaint(true);

            final Document doc = pane.getDocument();
            if (doc instanceof BaseDocument) {
                final Reformat reformat = Reformat.get(doc);
                reformat.lock();
                try {
                    ((BaseDocument) doc).runAtomic(new Runnable() {
                        @Override
                        public void run() {

                            try {
                                reformat.reformat(0, doc.getLength());
                            } catch (BadLocationException ble) {
                                LOGGER.log(Level.WARNING, null, ble);
                            }
                        }
                    });
                } finally {
                    reformat.unlock();
                }
            } else {
                LOGGER.warning(String.format("Can't format %s; it's not BaseDocument.", doc)); //NOI18N
            }
            pane.setIgnoreRepaint(false);
            pane.scrollRectToVisible(visibleRectangle);
            pane.repaint(100);

        }

        // PreferencesCustomizer implementation --------------------------------

        @Override
        public JComponent getComponent() {
            return panel;
        }

        @Override
        public String getDisplayName() {
            return panel.getName();
        }

        @Override
        public String getId() {
            return id;
        }

        @Override
        public HelpCtx getHelpCtx() {
            return null;
        }

        // PreferencesCustomizer.Factory implementation ------------------------

        public static final class Factory implements PreferencesCustomizer.Factory {

            private final String mimeType;
            private final Defaults.Provider provider;
            private final String id;
            private final Class<? extends JPanel> panelClass;
            private final String previewText;
            private final String[][] forcedOptions;

            public Factory(String mimeType, String id, Class<? extends JPanel> panelClass,
                    String previewText, String[]... forcedOptions) {
                this.mimeType = mimeType;
                this.provider = Defaults.getInstance(mimeType);
                this.id = id;
                this.panelClass = panelClass;
                this.previewText = previewText;
                this.forcedOptions = forcedOptions;
            }

            @Override
            public PreferencesCustomizer create(Preferences preferences) {
                try {
                    return new CategorySupport(mimeType, provider, preferences, id, panelClass.newInstance(), previewText, forcedOptions);
                } catch (Exception e) {
                    LOGGER.log(Level.WARNING, "Exception during creating formatter customiezer", e);
                    return null;
                }
            }
        } // End of CategorySupport.Factory class

        // Private methods -----------------------------------------------------

        private void performOperation(int operation, JComponent jc, String optionID, Preferences p) {
            switch (operation) {
                case LOAD:
                    loadData(jc, optionID, p);
                    break;
                case STORE:
                    storeData(jc, optionID, p);
                    break;
                case ADD_LISTENERS:
                    addListener(jc);
                    break;
                default:
                    LOGGER.log(Level.WARNING, "Unknown operation value {0}", operation);
                    break;
            }
        }

        private void scan(int what, Preferences p ) {
            for (JComponent jc : components) {
                Object o = jc.getClientProperty(OPTION_ID);
                if (o instanceof String) {
                    performOperation(what, jc, (String)o, p);
                } else if (o instanceof String[]) {
                    for(String oid : (String[])o) {
                        performOperation(what, jc, oid, p);
                    }
                }
            }
        }

        private void scan(Container container, List<JComponent> components) {
            for (Component c : container.getComponents()) {
                if (c instanceof JComponent) {
                    JComponent jc = (JComponent)c;
                    Object o = jc.getClientProperty(OPTION_ID);
                    if (o instanceof String || o instanceof String[])
                        components.add(jc);
                }
                if (c instanceof Container)
                    scan((Container)c, components);
            }
        }

        /** Very smart method which tries to set the values in the components correctly
         */
        private void loadData( JComponent jc, String optionID, Preferences node ) {

            if ( jc instanceof JTextField ) {
                JTextField field = (JTextField)jc;
                field.setText( node.get(optionID, provider.getDefaultAsString(optionID)) );
            }
            else if ( jc instanceof JCheckBox ) {
                JCheckBox checkBox = (JCheckBox)jc;
                boolean df = provider.getDefaultAsBoolean(optionID);
                checkBox.setSelected( node.getBoolean(optionID, df));
            }
            else if ( jc instanceof JComboBox) {
                JComboBox cb  = (JComboBox)jc;
                String value = node.get(optionID, provider.getDefaultAsString(optionID) );
                ComboBoxModel model = createModel(value);
                cb.setModel(model);
                ComboItem item = whichItem(value, model);
                cb.setSelectedItem(item);
            }

        }

        private void storeData( JComponent jc, String optionID, Preferences node ) {

            if ( jc instanceof JTextField ) {
                JTextField field = (JTextField)jc;

                String text = field.getText();

                // XXX test for numbers
                if ( isInteger(optionID) ) {
                    try {
                        Integer.parseInt(text);
                    } catch (NumberFormatException e) {
                        return;
                    }
                }

                // XXX: watch out, tabSize, spacesPerTab, indentSize and expandTabToSpaces
                // fall back on getGlopalXXX() values and not getDefaultAsXXX value,
                // which is why we must not remove them. Proper solution would be to
                // store formatting preferences to MimeLookup and not use NbPreferences.
                // The problem currently is that MimeLookup based Preferences do not support subnodes.
                if (!optionID.equals(tabSize) &&
                    !optionID.equals(spacesPerTab) && !optionID.equals(indentSize) &&
                    provider.getDefaultAsString(optionID).equals(text)
                ) {
                    node.remove(optionID);
                } else {
                    node.put(optionID, text);
                }
            }
            else if ( jc instanceof JCheckBox ) {
                JCheckBox checkBox = (JCheckBox)jc;
                if (!optionID.equals(expandTabToSpaces) && provider.getDefaultAsBoolean(optionID) == checkBox.isSelected())
                    node.remove(optionID);
                else
                    node.putBoolean(optionID, checkBox.isSelected());
            }
            else if ( jc instanceof JComboBox) {
                JComboBox cb  = (JComboBox)jc;
                ComboItem comboItem = ((ComboItem) cb.getSelectedItem());
                String value = comboItem == null ? provider.getDefaultAsString(optionID) : comboItem.value;

                if (provider.getDefaultAsString(optionID).equals(value))
                    node.remove(optionID);
                else
                    node.put(optionID,value);
            }
        }

        private void addListener( JComponent jc ) {
            if ( jc instanceof JTextField ) {
                JTextField field = (JTextField)jc;
                field.addActionListener(this);
                field.getDocument().addDocumentListener(this);
            }
            else if ( jc instanceof JCheckBox ) {
                JCheckBox checkBox = (JCheckBox)jc;
                checkBox.addActionListener(this);
            }
            else if ( jc instanceof JComboBox) {
                JComboBox cb  = (JComboBox)jc;
                cb.addActionListener(this);
            }
        }


        private ComboBoxModel createModel( String value ) {

            // is it braces placement?
            for (ComboItem comboItem : bracePlacement) {
                if ( value.equals( comboItem.value) ) {
                    return new DefaultComboBoxModel( bracePlacement );
                }
            }

	    // is it wrap
            for (ComboItem comboItem : wrap) {
                if ( value.equals( comboItem.value) ) {
                    return new DefaultComboBoxModel( wrap );
                }
            }

            return null;
        }

        private static ComboItem whichItem(String value, ComboBoxModel model) {

            for (int i = 0; i < model.getSize(); i++) {
                ComboItem item = (ComboItem)model.getElementAt(i);
                if ( value.equals(item.value)) {
                    return item;
                }
            }
            return null;
        }

        private static class ComboItem {

            String value;
            String displayName;

            public ComboItem(String value, String key) {
                this.value = value;
                this.displayName = NbBundle.getMessage(FmtOptions.class, key);
            }

            @Override
            public String toString() {
                return displayName;
            }

        }
    }

    public static class PreviewPreferences extends AbstractPreferences {

        private Map<String,Object> map = new HashMap<String, Object>();

        public PreviewPreferences() {
            super(null, ""); // NOI18N
        }

        @Override
        protected void putSpi(String key, String value) {
            map.put(key, value);
        }

        @Override
        protected String getSpi(String key) {
            return (String)map.get(key);
        }

        @Override
        protected void removeSpi(String key) {
            map.remove(key);
        }

        @Override
        protected void removeNodeSpi() throws BackingStoreException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected String[] keysSpi() throws BackingStoreException {
            String array[] = new String[map.keySet().size()];
            return map.keySet().toArray( array );
        }

        @Override
        protected String[] childrenNamesSpi() throws BackingStoreException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected AbstractPreferences childSpi(String name) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected void syncSpi() throws BackingStoreException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected void flushSpi() throws BackingStoreException {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }

    // read-only, no subnodes
    public static final class ProxyPreferences extends AbstractPreferences {

        private final Preferences[] delegates;

        public ProxyPreferences(Preferences... delegates) {
            super(null, ""); // NOI18N
            this.delegates = delegates;
        }

        @Override
        protected void putSpi(String key, String value) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected String getSpi(String key) {
            for(Preferences p : delegates) {
                String value = p.get(key, null);
                if (value != null) {
                    return value;
                }
            }
            return null;
        }

        @Override
        protected void removeSpi(String key) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected void removeNodeSpi() throws BackingStoreException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected String[] keysSpi() throws BackingStoreException {
            Set<String> keys = new HashSet<String>();
            for(Preferences p : delegates) {
                keys.addAll(Arrays.asList(p.keys()));
            }
            return keys.toArray(new String[ keys.size() ]);
        }

        @Override
        protected String[] childrenNamesSpi() throws BackingStoreException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected AbstractPreferences childSpi(String name) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected void syncSpi() throws BackingStoreException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        protected void flushSpi() throws BackingStoreException {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    } // End of ProxyPreferences class

    public static class BasicDefaultsProvider implements Defaults.Provider {

        @Override
        public int getDefaultAsInt(String key) {
            return FmtOptions.getDefaultAsInt(key);
        }

        @Override
        public boolean getDefaultAsBoolean(String key) {
            return FmtOptions.getDefaultAsBoolean(key);
        }

        @Override
        public String getDefaultAsString(String key) {
            return FmtOptions.getDefaultAsString(key);
        }

    }

    public static boolean isInteger(String optionID) {
        String value = defaults.get(optionID);

        try {
            Integer.parseInt(value);
            return true;
        } catch (NumberFormatException numberFormatException) {
            return false;
        }
    }
}
