package org.jetbrains.plugins.innerbuilder;

import com.intellij.codeInsight.generation.PsiFieldMember;
import com.intellij.ide.util.MemberChooser;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.ui.NonFocusableCheckBox;
import org.jetbrains.annotations.Nullable;

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

public final class InnerBuilderOptionSelector {
    private static final List<SelectorOption> OPTIONS = createGeneratorOptions();

    private static List<SelectorOption> createGeneratorOptions() {
        final List<SelectorOption> options = new ArrayList<SelectorOption>(8);

        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Generate builder methods for final fields")
                        .withMnemonic('f')
                        .withOption(InnerBuilderOption.FINAL_SETTERS)
                        .build());
        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Generate static newBuilder() method")
                        .withMnemonic('n')
                        .withOption(InnerBuilderOption.NEW_BUILDER_METHOD)
                        .build());
        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Generate builder copy constructor")
                        .withMnemonic('o')
                        .withOption(InnerBuilderOption.COPY_CONSTRUCTOR)
                        .build());
        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Use 'with...' notation")
                        .withMnemonic('w')
                        .withToolTip(
                                "Generate builder methods that start with 'with', for example: "
                                        + "builder.withName(String name)")
                        .withOption(InnerBuilderOption.WITH_NOTATION)
                        .build());
        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Use 'set...' notation")
                        .withMnemonic('t')
                        .withToolTip(
                                "Generate builder methods that start with 'set', for example: "
                                        + "builder.setName(String name)")
                        .withOption(InnerBuilderOption.SET_NOTATION)
                        .build());
        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Add JSR-305 @Nonnull annotation")
                        .withMnemonic('j')
                        .withToolTip(
                                "Add @Nonnull annotations to generated methods and parameters, for example: "
                                        + "@Nonnull public Builder withName(@Nonnull String name) { ... }")
                        .withOption(InnerBuilderOption.JSR305_ANNOTATIONS)
                        .build());
        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Add @SuppressWarnings(\"PMD.AvoidFieldNameMatchingMethodName\") annotation")
                        .withMnemonic('p')
                        .withToolTip(
                                "Add @SuppressWarnings(\"PMD.AvoidFieldNameMatchingMethodName\") annotation to the generated Builder class")
                        .withOption(InnerBuilderOption.PMD_AVOID_FIELD_NAME_MATCHING_METHOD_NAME_ANNOTATION)
                        .build());
        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Add Findbugs @NonNull annotation")
                        .withMnemonic('b')
                        .withToolTip(
                                "Add @NonNull annotations to generated methods and parameters, for example: "
                                        + "@NonNull public Builder withName(@NonNull String name) { ... }")
                        .withOption(InnerBuilderOption.FINDBUGS_ANNOTATION)
                        .build());
        options.add(
                SelectorOption.newBuilder()
                        .withCaption("Add Javadoc")
                        .withMnemonic('c')
                        .withToolTip("Add Javadoc to generated builder class and methods")
                        .withOption(InnerBuilderOption.WITH_JAVADOC)
                        .build());

	options.add(
		SelectorOption.newBuilder()
			.withCaption("Use field names in setter")
			.withMnemonic('s')
			.withToolTip(
				"Generate builder methods that has the same parameter names in setter methods as field names, for example: "
				+ "builder.withName(String fieldName)")
			.withOption(InnerBuilderOption.FIELD_NAMES)
			.build());

        return options;
    }

    private InnerBuilderOptionSelector() {
    }

    @Nullable
    public static List<PsiFieldMember> selectFieldsAndOptions(final List<PsiFieldMember> members,
                                                              final Project project) {
        if (members == null || members.isEmpty()) {
            return null;
        }

        if (ApplicationManager.getApplication().isUnitTestMode()) {
            return members;
        }

        final JCheckBox[] optionCheckBoxes = buildOptionCheckBoxes();

        final PsiFieldMember[] memberArray = members.toArray(new PsiFieldMember[members.size()]);

        final MemberChooser<PsiFieldMember> chooser = new MemberChooser<PsiFieldMember>(memberArray,
                false, // allowEmptySelection
                true,  // allowMultiSelection
                project, null, optionCheckBoxes);

        chooser.setTitle("Select Fields and Options for the Builder");
        chooser.selectElements(memberArray);
        if (chooser.showAndGet()) {
            return chooser.getSelectedElements();
        }

        return null;
    }

    private static JCheckBox[] buildOptionCheckBoxes() {
        final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
        final int optionCount = OPTIONS.size();
        final JCheckBox[] checkBoxesArray = new JCheckBox[optionCount];
        for (int i = 0; i < optionCount; i++) {
            checkBoxesArray[i] = buildOptionCheckBox(propertiesComponent, OPTIONS.get(i));
        }

        return checkBoxesArray;
    }

    private static JCheckBox buildOptionCheckBox(final PropertiesComponent propertiesComponent,
                                                 final SelectorOption selectorOption) {
        final InnerBuilderOption option = selectorOption.getOption();

        final JCheckBox optionCheckBox = new NonFocusableCheckBox(selectorOption.getCaption());
        optionCheckBox.setMnemonic(selectorOption.getMnemonic());
        optionCheckBox.setToolTipText(selectorOption.getToolTip());

        final String optionProperty = option.getProperty();
        optionCheckBox.setSelected(propertiesComponent.isTrueValue(optionProperty));
        optionCheckBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(final ItemEvent event) {
                propertiesComponent.setValue(optionProperty, Boolean.toString(optionCheckBox.isSelected()));
            }
        });
        return optionCheckBox;
    }
}