/*******************************************************************************
 * Copyright (c) 2000, 2020 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Christian Georgi<christian.georgi@sap.com> - Bug 462770: Use OS symbol for 'Ctrl'
 *     Gábor Kövesdán - Contribution for Bug 350000 - [content assist] Include non-prefix matches in auto-complete suggestions
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.preferences;

import java.util.Map;

import org.eclipse.osgi.util.NLS;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Text;

import org.eclipse.core.runtime.IStatus;

import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.preference.IPreferenceStore;

import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;

import org.eclipse.jdt.core.JavaCore;

import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.ui.PreferenceConstants;

import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal;
import org.eclipse.jdt.internal.ui.text.java.ProposalSorterHandle;
import org.eclipse.jdt.internal.ui.text.java.ProposalSorterRegistry;
import org.eclipse.jdt.internal.ui.wizards.IStatusChangeListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;


/**
 * Configures the content assist preferences.
 *
 * @since 3.0
 */
class CodeAssistConfigurationBlock extends OptionsConfigurationBlock {

	private static final Key PREF_CODEASSIST_AUTOACTIVATION= getJDTUIKey(PreferenceConstants.CODEASSIST_AUTOACTIVATION);
	private static final Key PREF_CODEASSIST_AUTOACTIVATION_DELAY= getJDTUIKey(PreferenceConstants.CODEASSIST_AUTOACTIVATION_DELAY);
	private static final Key PREF_CODEASSIST_AUTOINSERT= getJDTUIKey(PreferenceConstants.CODEASSIST_AUTOINSERT);
	private static final Key PREF_CODEASSIST_DISABLE_COMPLETION_PROPOSAL_TRIGGER_CHARS= getJDTUIKey(PreferenceConstants.CODEASSIST_DISABLE_COMPLETION_PROPOSAL_TRIGGER_CHARS);
	private static final Key PREF_CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA= getJDTUIKey(PreferenceConstants.CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA);
	private static final Key PREF_CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVADOC= getJDTUIKey(PreferenceConstants.CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVADOC);
	private static final Key PREF_CODEASSIST_SHOW_VISIBLE_PROPOSALS= getJDTUIKey(PreferenceConstants.CODEASSIST_SHOW_VISIBLE_PROPOSALS);
	private static final Key PREF_CODEASSIST_SORTER= getJDTUIKey(PreferenceConstants.CODEASSIST_SORTER);
	private static final Key PREF_CODEASSIST_CASE_SENSITIVITY= getJDTUIKey(PreferenceConstants.CODEASSIST_CASE_SENSITIVITY);
	private static final Key PREF_CODEASSIST_ADDIMPORT= getJDTUIKey(PreferenceConstants.CODEASSIST_ADDIMPORT);
	private static final Key PREF_CODEASSIST_SUGGEST_STATIC_IMPORTS= getJDTCoreKey(JavaCore.CODEASSIST_SUGGEST_STATIC_IMPORTS);
	private static final Key PREF_CODEASSIST_INSERT_COMPLETION= getJDTUIKey(PreferenceConstants.CODEASSIST_INSERT_COMPLETION);
	private static final Key PREF_CODEASSIST_FILL_ARGUMENT_NAMES= getJDTUIKey(PreferenceConstants.CODEASSIST_FILL_ARGUMENT_NAMES);
	private static final Key PREF_CODEASSIST_GUESS_METHOD_ARGUMENTS= getJDTUIKey(PreferenceConstants.CODEASSIST_GUESS_METHOD_ARGUMENTS);
	private static final Key PREF_CODEASSIST_PREFIX_COMPLETION= getJDTUIKey(PreferenceConstants.CODEASSIST_PREFIX_COMPLETION);
	private static final Key PREF_CODEASSIST_DEPRECATION_CHECK= getJDTCoreKey(JavaCore.CODEASSIST_DEPRECATION_CHECK);
	private static final Key PREF_CODEASSIST_CAMEL_CASE_MATCH= getJDTCoreKey(JavaCore.CODEASSIST_CAMEL_CASE_MATCH);
	private static final Key PREF_CODEASSIST_SUBWORD_MATCH= getJDTCoreKey(JavaCore.CODEASSIST_SUBWORD_MATCH);

	private static Key[] getAllKeys() {
		return new Key[] {
				PREF_CODEASSIST_AUTOACTIVATION,
				PREF_CODEASSIST_AUTOACTIVATION_DELAY,
				PREF_CODEASSIST_AUTOINSERT,
				PREF_CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA,
				PREF_CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVADOC,
				PREF_CODEASSIST_SHOW_VISIBLE_PROPOSALS,
				PREF_CODEASSIST_SORTER,
				PREF_CODEASSIST_CASE_SENSITIVITY,
				PREF_CODEASSIST_ADDIMPORT,
				PREF_CODEASSIST_SUGGEST_STATIC_IMPORTS,
				PREF_CODEASSIST_INSERT_COMPLETION,
				PREF_CODEASSIST_FILL_ARGUMENT_NAMES,
				PREF_CODEASSIST_GUESS_METHOD_ARGUMENTS,
				PREF_CODEASSIST_PREFIX_COMPLETION,
				PREF_CODEASSIST_DEPRECATION_CHECK,
				PREF_CODEASSIST_CAMEL_CASE_MATCH,
				PREF_CODEASSIST_SUBWORD_MATCH,
				PREF_CODEASSIST_DISABLE_COMPLETION_PROPOSAL_TRIGGER_CHARS
		};
	}

	private static final String[] trueFalse= new String[] { IPreferenceStore.TRUE, IPreferenceStore.FALSE };
	private static final String[] enabledDisabled= new String[] { JavaCore.ENABLED, JavaCore.DISABLED };

	private Button fCompletionInsertsRadioButton;
	private Button fCompletionOverwritesRadioButton;
	private Button fInsertParameterNamesRadioButton;
	private Button fInsertBestGuessRadioButton;

	public CodeAssistConfigurationBlock(IStatusChangeListener statusListener, IWorkbenchPreferenceContainer workbenchcontainer) {
		super(statusListener, null, getAllKeys(), workbenchcontainer);
	}

	@Override
	protected Control createContents(Composite parent) {
		ScrolledPageContent scrolled= new ScrolledPageContent(parent, SWT.H_SCROLL | SWT.V_SCROLL);
		scrolled.setExpandHorizontal(true);
		scrolled.setExpandVertical(true);

		Composite control= new Composite(scrolled, SWT.NONE);
		GridLayout layout= new GridLayout();
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		control.setLayout(layout);

		Composite composite;

		composite= createSubsection(control, PreferencesMessages.CodeAssistConfigurationBlock_insertionSection_title);
		addInsertionSection(composite);

		composite= createSubsection(control, PreferencesMessages.CodeAssistConfigurationBlock_sortingSection_title);
		addSortingSection(composite);

		composite= createSubsection(control, PreferencesMessages.CodeAssistConfigurationBlock_autoactivationSection_title);
		addAutoActivationSection(composite);

		initialize();

		scrolled.setContent(control);
		final Point size= control.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		scrolled.setMinSize(size.x, size.y);
		return scrolled;
	}

	protected Composite createSubsection(Composite parent, String label) {
		Group group= new Group(parent, SWT.SHADOW_NONE);
		group.setText(label);
		GridData data= new GridData(SWT.FILL, SWT.CENTER, true, false);
		group.setLayoutData(data);
		GridLayout layout= new GridLayout();
		layout.numColumns= 3;
		group.setLayout(layout);

		return group;
	}

	private void addInsertionSection(Composite composite) {
		addCompletionRadioButtons(composite);

		String label;
		label= PreferencesMessages.JavaEditorPreferencePage_insertSingleProposalsAutomatically;
		addCheckBox(composite, label, PREF_CODEASSIST_AUTOINSERT, trueFalse, 0);

		label= PreferencesMessages.JavaEditorPreferencePage_completePrefixes;
		addCheckBox(composite, label, PREF_CODEASSIST_PREFIX_COMPLETION, trueFalse, 0);

		addCheckBox(composite, PreferencesMessages.JavaEditorPreferencePage_disableCompletionProposalTriggerChars, PREF_CODEASSIST_DISABLE_COMPLETION_PROPOSAL_TRIGGER_CHARS, trueFalse, 0);

		label= PreferencesMessages.JavaEditorPreferencePage_automaticallyAddImportInsteadOfQualifiedName;
		Button master= addCheckBox(composite, label, PREF_CODEASSIST_ADDIMPORT, trueFalse, 0);

		label= PreferencesMessages.JavaEditorPreferencePage_suggestStaticImports;
		Button slave= addCheckBoxWithLink(composite, label, PREF_CODEASSIST_SUGGEST_STATIC_IMPORTS, enabledDisabled, 20, SWT.DEFAULT, new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				openStaticImportFavoritesPage();
			}
		});
		createSelectionDependency(master, slave.getParent());


		label= PreferencesMessages.JavaEditorPreferencePage_fillArgumentsOnMethodCompletion;
		master= addCheckBox(composite, label, PREF_CODEASSIST_FILL_ARGUMENT_NAMES, trueFalse, 0);

		Composite fillComposite= new Composite(composite, SWT.NONE);
		GridData gd= new GridData();
		gd.horizontalSpan= 2;
		gd.horizontalIndent= LayoutUtil.getIndent();
		fillComposite.setLayoutData(gd);
		GridLayout layout= new GridLayout();
		layout.marginWidth= 0;
		layout.marginHeight= 0;
		layout.numColumns= 2;
		fillComposite.setLayout(layout);


		SelectionListener completionSelectionListener= new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				boolean state= fInsertBestGuessRadioButton.getSelection();
				setValue(PREF_CODEASSIST_GUESS_METHOD_ARGUMENTS, state);
			}
		};

		fInsertParameterNamesRadioButton= new Button(fillComposite, SWT.RADIO | SWT.LEFT);
		fInsertParameterNamesRadioButton.setText(PreferencesMessages.JavaEditorPreferencePage_fillParameterNamesOnMethodCompletion);
		fInsertParameterNamesRadioButton.setLayoutData(new GridData());
		fInsertParameterNamesRadioButton.addSelectionListener(completionSelectionListener);

		fInsertBestGuessRadioButton= new Button(fillComposite, SWT.RADIO | SWT.LEFT);
		fInsertBestGuessRadioButton.setText(PreferencesMessages.JavaEditorPreferencePage_fillBestGuessedArgumentsOnMethodCompletion);
		fInsertBestGuessRadioButton.setLayoutData(new GridData());
		fInsertBestGuessRadioButton.addSelectionListener(completionSelectionListener);

		createSelectionDependency(master, fInsertParameterNamesRadioButton);
		createSelectionDependency(master, fInsertBestGuessRadioButton);
	}

	protected final void openStaticImportFavoritesPage() {
		if (getPreferenceContainer() != null) {
			getPreferenceContainer().openPage(CodeAssistFavoritesPreferencePage.PAGE_ID, null);
		} else {
			PreferencesUtil.createPreferenceDialogOn(getShell(), CodeAssistFavoritesPreferencePage.PAGE_ID, null, null).open();
		}
	}

	/**
	 * Creates a selection dependency between a master and a slave control.
	 *
	 * @param master
	 *                   The master button that controls the state of the slave
	 * @param slave
	 *                   The slave control that is enabled only if the master is
	 *                   selected
	 */
	protected static void createSelectionDependency(final Button master, final Control slave) {
		master.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent event) {
				deepSetEnabled(slave, master.getSelection());
			}

			private void deepSetEnabled(final Control control, boolean enabled) {
				control.setEnabled(enabled);
				if (control instanceof Composite) {
					for (Control child : ((Composite)control).getChildren()) {
						deepSetEnabled(child, enabled);
					}
				}
			}
		});
		slave.setEnabled(master.getSelection());
	}

	private void addSortingSection(Composite composite) {
		String label;

		label= PreferencesMessages.CodeAssistConfigurationBlock_typeFilters_link;
		createPreferencePageLink(composite, label, null);
		new Label(composite, SWT.NONE);

		label= PreferencesMessages.JavaEditorPreferencePage_presentProposalsInAlphabeticalOrder;
		ProposalSorterHandle[] sorters= ProposalSorterRegistry.getDefault().getSorters();
		String[] labels= new String[sorters.length];
		String[] values= new String[sorters.length];
		for (int i= 0; i < sorters.length; i++) {
			ProposalSorterHandle handle= sorters[i];
			labels[i]= handle.getName();
			values[i]= handle.getId();
		}

		addComboBox(composite, label, PREF_CODEASSIST_SORTER, values, labels, 0);

		label= PreferencesMessages.CodeAssistConfigurationBlock_matchCamelCase_label;
		addCheckBox(composite, label, PREF_CODEASSIST_CAMEL_CASE_MATCH, enabledDisabled, 0);

		label= PreferencesMessages.CodeAssistConfigurationBlock_matchSubword_label;
		addCheckBox(composite, label, PREF_CODEASSIST_SUBWORD_MATCH, enabledDisabled, 0);

		label= PreferencesMessages.JavaEditorPreferencePage_showOnlyProposalsVisibleInTheInvocationContext;
		addCheckBox(composite, label, PREF_CODEASSIST_SHOW_VISIBLE_PROPOSALS, trueFalse, 0);

		label= PreferencesMessages.CodeAssistConfigurationBlock_hideDeprecated_label;
		addCheckBox(composite, label, PREF_CODEASSIST_DEPRECATION_CHECK, enabledDisabled, 0);
	}

	private void createPreferencePageLink(Composite composite, String label, final Map<String, String> targetInfo) {
		final Link link= new Link(composite, SWT.NONE);
		link.setText(label);
		link.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				PreferencesUtil.createPreferenceDialogOn(link.getShell(), e.text, null, targetInfo);
			}
		});
	}

	private void addAutoActivationSection(Composite composite) {
		String label;
		label= PreferencesMessages.JavaEditorPreferencePage_enableAutoActivation;
		final Button autoactivation= addCheckBox(composite, label, PREF_CODEASSIST_AUTOACTIVATION, trueFalse, 0);
		autoactivation.addSelectionListener(new SelectionAdapter(){
			@Override
			public void widgetSelected(SelectionEvent e) {
				updateAutoactivationControls();
			}
		});

		label= PreferencesMessages.JavaEditorPreferencePage_autoActivationDelay;
		addLabelledTextField(composite, label, PREF_CODEASSIST_AUTOACTIVATION_DELAY, 4, 20);

		label= PreferencesMessages.JavaEditorPreferencePage_autoActivationTriggersForJava;
		addLabelledTextField(composite, label, PREF_CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA, 100, 0, 20);

		label= PreferencesMessages.JavaEditorPreferencePage_autoActivationTriggersForJavaDoc;
		addLabelledTextField(composite, label, PREF_CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVADOC, 100, 0, 20);
	}


	protected Text addLabelledTextField(Composite parent, String label, Key key, int textlimit, int indent) {
		return addLabelledTextField(parent, label, key, textlimit, textlimit, indent);
	}

	protected Text addLabelledTextField(Composite parent, String label, Key key, int modelTextLimit, int fieldTextLimit, int indent) {
		PixelConverter pixelConverter= new PixelConverter(parent);

		Label labelControl= new Label(parent, SWT.NONE);
		labelControl.setText(label);
		GridData data= new GridData();
		data.horizontalIndent= indent;
		labelControl.setLayoutData(data);

		Text textBox= new Text(parent, SWT.BORDER | SWT.SINGLE);
		textBox.setData(key);
		textBox.setLayoutData(new GridData());

		fLabels.put(textBox, labelControl);

		String currValue= getValue(key);
		if (currValue != null) {
			textBox.setText(currValue);
		}
		textBox.addModifyListener(getTextModifyListener());

		data= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		if (modelTextLimit != 0)
			textBox.setTextLimit(modelTextLimit);

		if (fieldTextLimit != 0) {
			data.widthHint= pixelConverter.convertWidthInCharsToPixels(fieldTextLimit + 1);
			data.horizontalAlignment = SWT.BEGINNING;
		} else {
			data.grabExcessHorizontalSpace = true;
		}

		data.horizontalSpan= 2;
		textBox.setLayoutData(data);

		fTextBoxes.add(textBox);
		return textBox;
	}

	private void addCompletionRadioButtons(Composite contentAssistComposite) {
		Composite completionComposite= new Composite(contentAssistComposite, SWT.NONE);
		GridData ccgd= new GridData();
		ccgd.horizontalSpan= 2;
		completionComposite.setLayoutData(ccgd);
		GridLayout ccgl= new GridLayout();
		ccgl.marginWidth= 0;
		ccgl.numColumns= 2;
		completionComposite.setLayout(ccgl);

		SelectionListener completionSelectionListener= new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				boolean insert= fCompletionInsertsRadioButton.getSelection();
				setValue(PREF_CODEASSIST_INSERT_COMPLETION, insert);
			}
		};

		fCompletionInsertsRadioButton= new Button(completionComposite, SWT.RADIO | SWT.LEFT);
		fCompletionInsertsRadioButton.setText(PreferencesMessages.JavaEditorPreferencePage_completionInserts);
		fCompletionInsertsRadioButton.setLayoutData(new GridData());
		fCompletionInsertsRadioButton.addSelectionListener(completionSelectionListener);

		fCompletionOverwritesRadioButton= new Button(completionComposite, SWT.RADIO | SWT.LEFT);
		fCompletionOverwritesRadioButton.setText(PreferencesMessages.JavaEditorPreferencePage_completionOverwrites);
		fCompletionOverwritesRadioButton.setLayoutData(new GridData());
		fCompletionOverwritesRadioButton.addSelectionListener(completionSelectionListener);

		Label label= new Label(completionComposite, SWT.NONE);
		String modifier= SWTKeySupport.getKeyFormatterForPlatform().format(AbstractJavaCompletionProposal.MODIFIER_TOGGLE_COMPLETION_MODE);
		label.setText(NLS.bind(PreferencesMessages.JavaEditorPreferencePage_completionToggleHint, modifier));
		GridData gd= new GridData();
		gd.horizontalIndent= LayoutUtil.getIndent();
		gd.horizontalSpan= 2;
		label.setLayoutData(gd);
	}

	public void initialize() {
		initializeFields();
	}

	private void initializeFields() {
		boolean value= getBooleanValue(PREF_CODEASSIST_INSERT_COMPLETION);
		fCompletionInsertsRadioButton.setSelection(value);
		fCompletionOverwritesRadioButton.setSelection(!value);
		value= getBooleanValue(PREF_CODEASSIST_GUESS_METHOD_ARGUMENTS);
		fInsertBestGuessRadioButton.setSelection(value);
		fInsertParameterNamesRadioButton.setSelection(!value);

		value= getBooleanValue(PREF_CODEASSIST_FILL_ARGUMENT_NAMES);
		fInsertParameterNamesRadioButton.setEnabled(value);
		fInsertBestGuessRadioButton.setEnabled(value);

		updateAutoactivationControls();
 	}

    private void updateAutoactivationControls() {
        boolean autoactivation= getBooleanValue(PREF_CODEASSIST_AUTOACTIVATION);
        setControlEnabled(PREF_CODEASSIST_AUTOACTIVATION_DELAY, autoactivation);
        setControlEnabled(PREF_CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA, autoactivation);
        setControlEnabled(PREF_CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVADOC, autoactivation);
    }


	@Override
	public void performDefaults() {
		super.performDefaults();
		initializeFields();
	}

	@Override
	protected String[] getFullBuildDialogStrings(boolean workspaceSettings) {
		return null;
	}

	/**
	 * Validates that the specified number is positive.
	 *
	 * @param number
	 *                   The number to validate
	 * @return The status of the validation
	 */
	protected static IStatus validatePositiveNumber(final String number) {

		final StatusInfo status= new StatusInfo();
		if (number.length() == 0) {
			status.setError(PreferencesMessages.SpellingPreferencePage_empty_threshold);
		} else {
			try {
				final int value= Integer.parseInt(number);
				if (value < 0) {
					status.setError(Messages.format(PreferencesMessages.SpellingPreferencePage_invalid_threshold, number));
				}
			} catch (NumberFormatException exception) {
				status.setError(Messages.format(PreferencesMessages.SpellingPreferencePage_invalid_threshold, number));
			}
		}
		return status;
	}

	@Override
	protected void validateSettings(Key key, String oldValue, String newValue) {
		if (key == null || PREF_CODEASSIST_AUTOACTIVATION_DELAY.equals(key))
			fContext.statusChanged(validatePositiveNumber(getValue(PREF_CODEASSIST_AUTOACTIVATION_DELAY)));
	}

	protected void setControlEnabled(Key key, boolean enabled) {
		Control control= getControl(key);
		control.setEnabled(enabled);
		Label label= fLabels.get(control);
		if (label != null)
			label.setEnabled(enabled);
	}

	private Control getControl(Key key) {
		for (int i= fComboBoxes.size() - 1; i >= 0; i--) {
			Control curr= fComboBoxes.get(i);
			ControlData data= (ControlData) curr.getData();
			if (key.equals(data.getKey())) {
				return curr;
			}
		}
		for (int i= fCheckBoxes.size() - 1; i >= 0; i--) {
			Control curr= fCheckBoxes.get(i);
			ControlData data= (ControlData) curr.getData();
			if (key.equals(data.getKey())) {
				return curr;
			}
		}
		for (int i= fTextBoxes.size() - 1; i >= 0; i--) {
			Control curr= fTextBoxes.get(i);
			Key currKey= (Key) curr.getData();
			if (key.equals(currKey)) {
				return curr;
			}
		}
		return null;
	}
}
