/*******************************************************************************
 * Copyright (c) 2000, 2016 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
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.preferences;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;

import org.eclipse.core.runtime.IStatus;

import org.eclipse.core.resources.IProject;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.window.Window;

import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;

import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jdt.core.JavaCore;

import org.eclipse.jdt.ui.JavaElementImageDescriptor;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.PreferenceConstants;

import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
import org.eclipse.jdt.internal.ui.wizards.IStatusChangeListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IListAdapter;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.SelectionButtonDialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.StringDialogField;


/**
 * The page for setting the organize import settings.
 */
public class ImportOrganizeConfigurationBlock extends OptionsConfigurationBlock {

	private static final Key PREF_IMPORTORDER= getJDTUIKey(PreferenceConstants.ORGIMPORTS_IMPORTORDER);
	private static final Key PREF_ONDEMANDTHRESHOLD= getJDTUIKey(PreferenceConstants.ORGIMPORTS_ONDEMANDTHRESHOLD);
	private static final Key PREF_IGNORELOWERCASE= getJDTUIKey(PreferenceConstants.ORGIMPORTS_IGNORELOWERCASE);
	private static final Key PREF_STATICONDEMANDTHRESHOLD= getJDTUIKey(PreferenceConstants.ORGIMPORTS_STATIC_ONDEMANDTHRESHOLD);

	private static final String DIALOGSETTING_LASTLOADPATH= JavaUI.ID_PLUGIN + ".importorder.loadpath"; //$NON-NLS-1$
	private static final String DIALOGSETTING_LASTSAVEPATH= JavaUI.ID_PLUGIN + ".importorder.savepath"; //$NON-NLS-1$

	private static Key[] getAllKeys() {
		return new Key[] {
			PREF_IMPORTORDER, PREF_ONDEMANDTHRESHOLD, PREF_STATICONDEMANDTHRESHOLD, PREF_IGNORELOWERCASE
		};
	}

	public static class ImportOrderEntry {

		public final String name;
		public final boolean isStatic;

		public ImportOrderEntry(String name, boolean isStatic) {
			this.name= name;
			this.isStatic= isStatic;
		}

		public String serialize() {
			return isStatic ? '#' + name : name;
		}

		public static ImportOrderEntry fromSerialized(String str) {
			if (str.length() > 0 && str.charAt(0) == '#') {
				return new ImportOrderEntry(str.substring(1), true);
			}
			return new ImportOrderEntry(str, false);
		}

	}


	private static class ImportOrganizeLabelProvider extends LabelProvider {

		private final Image PCK_ICON;
		private final Image STATIC_CLASS_ICON;

		public ImportOrganizeLabelProvider() {
			PCK_ICON= JavaPluginImages.get(JavaPluginImages.IMG_OBJS_PACKAGE);
			STATIC_CLASS_ICON= JavaElementImageProvider.getDecoratedImage(JavaPluginImages.DESC_MISC_PUBLIC, JavaElementImageDescriptor.STATIC, JavaElementImageProvider.SMALL_SIZE);
		}

		@Override
		public Image getImage(Object element) {
			return ((ImportOrderEntry) element).isStatic ? STATIC_CLASS_ICON : PCK_ICON;
		}

		@Override
		public String getText(Object element) {
			ImportOrderEntry entry= (ImportOrderEntry) element;
			String name= entry.name;
			if (name.length() > 0) {
				return BasicElementLabels.getJavaElementName(name);
			}
			if (entry.isStatic) {
				return PreferencesMessages.ImportOrganizeConfigurationBlock_other_static;
			}
			return PreferencesMessages.ImportOrganizeConfigurationBlock_other_normal;
		}
	}

	private class ImportOrganizeAdapter implements IListAdapter<ImportOrderEntry>, IDialogFieldListener {

		private boolean canEdit(ListDialogField<ImportOrderEntry> field) {
			List<ImportOrderEntry> selected= field.getSelectedElements();
			return selected.size() == 1;
		}

        @Override
		public void customButtonPressed(ListDialogField<ImportOrderEntry> field, int index) {
        	doButtonPressed(index);
        }

        @Override
		public void selectionChanged(ListDialogField<ImportOrderEntry> field) {
			fOrderListField.enableButton(IDX_EDIT, canEdit(field));
        }

        @Override
		public void dialogFieldChanged(DialogField field) {
        	doDialogFieldChanged(field);
        }

        @Override
		public void doubleClicked(ListDialogField<ImportOrderEntry> field) {
        	if (canEdit(field)) {
				doButtonPressed(IDX_EDIT);
        	}
        }
	}

	private static final int IDX_ADD= 0;
	private static final int IDX_ADD_STATIC= 1;
	private static final int IDX_EDIT= 2;
	private static final int IDX_REMOVE= 3;
	private static final int IDX_UP= 5;
	private static final int IDX_DOWN= 6;

	private ListDialogField<ImportOrderEntry> fOrderListField;
	private StringDialogField fThresholdField;
	private StringDialogField fStaticThresholdField;
	private SelectionButtonDialogField fIgnoreLowerCaseTypesField;
	private SelectionButtonDialogField fExportButton;
	private SelectionButtonDialogField fImportButton;

	private PixelConverter fPixelConverter;

	public ImportOrganizeConfigurationBlock(IStatusChangeListener context, IProject project, IWorkbenchPreferenceContainer container) {
		super(context, project, getAllKeys(), container);

		String[] buttonLabels= new String[] {
			PreferencesMessages.ImportOrganizeConfigurationBlock_order_add_button,
			PreferencesMessages.ImportOrganizeConfigurationBlock_order_add_static_button,
			PreferencesMessages.ImportOrganizeConfigurationBlock_order_edit_button,
			PreferencesMessages.ImportOrganizeConfigurationBlock_order_remove_button,
			/* 4 */  null,
			PreferencesMessages.ImportOrganizeConfigurationBlock_order_up_button,
			PreferencesMessages.ImportOrganizeConfigurationBlock_order_down_button,
		};

		ImportOrganizeAdapter adapter= new ImportOrganizeAdapter();

		fOrderListField= new ListDialogField<>(adapter, buttonLabels, new ImportOrganizeLabelProvider());
		fOrderListField.setDialogFieldListener(adapter);
		fOrderListField.setLabelText(PreferencesMessages.ImportOrganizeConfigurationBlock_order_label);
		fOrderListField.setUpButtonIndex(IDX_UP);
		fOrderListField.setDownButtonIndex(IDX_DOWN);
		fOrderListField.setRemoveButtonIndex(IDX_REMOVE);

		fOrderListField.enableButton(IDX_EDIT, false);

		fImportButton= new SelectionButtonDialogField(SWT.PUSH);
		fImportButton.setDialogFieldListener(adapter);
		fImportButton.setLabelText(PreferencesMessages.ImportOrganizeConfigurationBlock_order_load_button);

		fExportButton= new SelectionButtonDialogField(SWT.PUSH);
		fExportButton.setDialogFieldListener(adapter);
		fExportButton.setLabelText(PreferencesMessages.ImportOrganizeConfigurationBlock_order_save_button);

		fThresholdField= new StringDialogField();
		fThresholdField.setDialogFieldListener(adapter);
		fThresholdField.setLabelText(PreferencesMessages.ImportOrganizeConfigurationBlock_threshold_label);

		fStaticThresholdField= new StringDialogField();
		fStaticThresholdField.setDialogFieldListener(adapter);
		fStaticThresholdField.setLabelText(PreferencesMessages.ImportOrganizeConfigurationBlock_staticthreshold_label);

		fIgnoreLowerCaseTypesField= new SelectionButtonDialogField(SWT.CHECK);
		fIgnoreLowerCaseTypesField.setDialogFieldListener(adapter);
		fIgnoreLowerCaseTypesField.setLabelText(PreferencesMessages.ImportOrganizeConfigurationBlock_ignoreLowerCase_label);

		updateControls();
	}

	@Override
	protected Control createContents(Composite parent) {
		setShell(parent.getShell());

		fPixelConverter= new PixelConverter(parent);

		Composite composite= new Composite(parent, SWT.NONE);
		composite.setFont(parent.getFont());

		GridLayout layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginWidth= 0;
		layout.marginHeight= 0;

		composite.setLayout(layout);

		fOrderListField.doFillIntoGrid(composite, 3);
		LayoutUtil.setHorizontalSpan(fOrderListField.getLabelControl(null), 2);
		LayoutUtil.setWidthHint(fOrderListField.getLabelControl(null), fPixelConverter.convertWidthInCharsToPixels(60));
		LayoutUtil.setHorizontalGrabbing(fOrderListField.getListControl(null));

		Composite importExportComp= new Composite(composite, SWT.NONE);
		importExportComp.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, 2, 1));
		layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginWidth= 0;
		layout.marginHeight= 0;

		importExportComp.setLayout(layout);

		fImportButton.doFillIntoGrid(importExportComp, 1);
		fExportButton.doFillIntoGrid(importExportComp, 1);

		fThresholdField.doFillIntoGrid(composite, 2);
		((GridData) fThresholdField.getTextControl(null).getLayoutData()).grabExcessHorizontalSpace= false;
		fStaticThresholdField.doFillIntoGrid(composite, 2);
		fIgnoreLowerCaseTypesField.doFillIntoGrid(composite, 2);

		Dialog.applyDialogFont(composite);

		return composite;
	}

	private boolean doThresholdChanged(String thresholdString) {
		StatusInfo status= new StatusInfo();
		try {
			int threshold= Integer.parseInt(thresholdString);
			if (threshold < 0) {
				status.setError(PreferencesMessages.ImportOrganizeConfigurationBlock_error_invalidthreshold);
			}
		} catch (NumberFormatException e) {
			status.setError(PreferencesMessages.ImportOrganizeConfigurationBlock_error_invalidthreshold);
		}
		updateStatus(status);
		return status.isOK();
	}

	private void doButtonPressed(int index) {
		if (index == IDX_ADD || index == IDX_ADD_STATIC) { // add new
			List<ImportOrderEntry> existing= fOrderListField.getElements();
			ImportOrganizeInputDialog dialog= new ImportOrganizeInputDialog(getShell(), existing, index == IDX_ADD_STATIC);
			if (dialog.open() == Window.OK) {
				List<ImportOrderEntry> selectedElements= fOrderListField.getSelectedElements();
				if (selectedElements.size() == 1) {
					int insertionIndex= fOrderListField.getIndexOfElement(selectedElements.get(0)) + 1;
					fOrderListField.addElement(dialog.getResult(), insertionIndex);
				} else {
					fOrderListField.addElement(dialog.getResult());
				}
			}
		} else if (index == IDX_EDIT) { // edit
			List<ImportOrderEntry> selected= fOrderListField.getSelectedElements();
			if (selected.isEmpty()) {
				return;
			}
			ImportOrderEntry editedEntry= selected.get(0);

			List<ImportOrderEntry> existing= fOrderListField.getElements();
			existing.remove(editedEntry);

			ImportOrganizeInputDialog dialog= new ImportOrganizeInputDialog(getShell(), existing, editedEntry.isStatic);
			dialog.setInitialSelection(editedEntry);
			if (dialog.open() == Window.OK) {
				fOrderListField.replaceElement(editedEntry, dialog.getResult());
			}
		}
	}


	/*
	 * The import order file is a property file. The keys are
	 * "0", "1" ... last entry. The values must be valid package names.
	 */
	private List<ImportOrderEntry> loadFromProperties(Properties properties) {
		ArrayList<ImportOrderEntry> res= new ArrayList<>();
		int nEntries= properties.size();
		for (int i= 0 ; i < nEntries; i++) {
			String curr= properties.getProperty(String.valueOf(i));
			if (curr != null) {
				ImportOrderEntry entry= ImportOrderEntry.fromSerialized(curr);
				if (entry.name.length() == 0 || !JavaConventions.validatePackageName(entry.name, JavaCore.VERSION_1_3, JavaCore.VERSION_1_5).matches(IStatus.ERROR)) {
					res.add(entry);
				} else {
					return null;
				}
			} else {
				return res;
			}
		}
		return res;
	}

	private List<ImportOrderEntry> loadImportOrder() {
		IDialogSettings dialogSettings= JavaPlugin.getDefault().getDialogSettings();

		FileDialog dialog= new FileDialog(getShell(), SWT.OPEN | SWT.SHEET);
		dialog.setText(PreferencesMessages.ImportOrganizeConfigurationBlock_loadDialog_title);
		dialog.setFilterExtensions(new String[] {"*.importorder", "*.*"}); //$NON-NLS-1$ //$NON-NLS-2$
		String lastPath= dialogSettings.get(DIALOGSETTING_LASTLOADPATH);
		if (lastPath != null) {
			dialog.setFilterPath(lastPath);
		}
		String fileName= dialog.open();
		if (fileName != null) {
			dialogSettings.put(DIALOGSETTING_LASTLOADPATH, dialog.getFilterPath());

			Properties properties= new Properties();
			FileInputStream fis= null;
			try {
				fis= new FileInputStream(fileName);
				properties.load(fis);
				List<ImportOrderEntry> res= loadFromProperties(properties);
				if (res != null) {
					return res;
				}
			} catch (IOException e) {
				JavaPlugin.log(e);
			} finally {
				if (fis != null) {
					try { fis.close(); } catch (IOException e) {}
				}
			}
			String title= PreferencesMessages.ImportOrganizeConfigurationBlock_loadDialog_error_title;
			String message= PreferencesMessages.ImportOrganizeConfigurationBlock_loadDialog_error_message;
			MessageDialog.openError(getShell(), title, message);
		}
		return null;
	}

	private void saveImportOrder(List<ImportOrderEntry> elements) {
		IDialogSettings dialogSettings= JavaPlugin.getDefault().getDialogSettings();

		FileDialog dialog= new FileDialog(getShell(), SWT.SAVE | SWT.SHEET);
		dialog.setText(PreferencesMessages.ImportOrganizeConfigurationBlock_saveDialog_title);
		dialog.setFilterExtensions(new String[] {"*.importorder", "*.*"}); //$NON-NLS-1$ //$NON-NLS-2$
		dialog.setFileName("example"); //$NON-NLS-1$
		String lastPath= dialogSettings.get(DIALOGSETTING_LASTSAVEPATH);
		if (lastPath != null) {
			dialog.setFilterPath(lastPath);
		}
		String fileName= dialog.open();
		if (fileName != null) {
			dialogSettings.put(DIALOGSETTING_LASTSAVEPATH, dialog.getFilterPath());

			Properties properties= new Properties();
			for (int i= 0; i < elements.size(); i++) {
				ImportOrderEntry entry= elements.get(i);
				properties.setProperty(String.valueOf(i), entry.serialize());
			}
			FileOutputStream fos= null;
			try {
				fos= new FileOutputStream(fileName);
				properties.store(fos, "Organize Import Order"); //$NON-NLS-1$
			} catch (IOException e) {
				JavaPlugin.log(e);
				String title= PreferencesMessages.ImportOrganizeConfigurationBlock_saveDialog_error_title;
				String message= PreferencesMessages.ImportOrganizeConfigurationBlock_saveDialog_error_message;
				MessageDialog.openError(getShell(), title, message);
			} finally {
				if (fos != null) {
					try { fos.close(); } catch (IOException e) {}
				}
			}
		}
	}

	private void updateStatus(IStatus status) {
		fContext.statusChanged(status);
	}

	@Override
	protected void validateSettings(Key changedKey, String oldValue, String newValue) {
		// no validation
	}

	@Override
	protected void updateControls() {
		ImportOrderEntry[] importOrder= getImportOrderPreference();
		int threshold= getImportNumberThreshold(PREF_ONDEMANDTHRESHOLD);
		int staticThreshold= getImportNumberThreshold(PREF_STATICONDEMANDTHRESHOLD);
		boolean ignoreLowerCase= Boolean.parseBoolean(getValue(PREF_IGNORELOWERCASE));

		fOrderListField.removeAllElements();
		for (ImportOrderEntry i : importOrder) {
			fOrderListField.addElement(i);
		}
		fThresholdField.setText(String.valueOf(threshold));
		fStaticThresholdField.setText(String.valueOf(staticThreshold));
		fIgnoreLowerCaseTypesField.setSelection(ignoreLowerCase);
	}


	protected final void doDialogFieldChanged(DialogField field) {
		// set values in working copy
		if (field == fOrderListField) {
	  		setValue(PREF_IMPORTORDER, packOrderList(fOrderListField.getElements()));
		} else if (field == fThresholdField) {
	  		if (doThresholdChanged(fThresholdField.getText())) {
		  		setValue(PREF_ONDEMANDTHRESHOLD, fThresholdField.getText());
	  		}
		} else if (field == fStaticThresholdField) {
	  		if (doThresholdChanged(fStaticThresholdField.getText())) {
		  		setValue(PREF_STATICONDEMANDTHRESHOLD, fStaticThresholdField.getText());
	  		}
		} else if (field == fIgnoreLowerCaseTypesField) {
	  		setValue(PREF_IGNORELOWERCASE, fIgnoreLowerCaseTypesField.isSelected());
		} else if (field == fImportButton) {
			List<ImportOrderEntry> order= loadImportOrder();
			if (order != null) {
				fOrderListField.setElements(order);
			}
		} else if (field == fExportButton) {
			saveImportOrder(fOrderListField.getElements());
		}
	}


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

	private static ImportOrderEntry[] unpackOrderList(String str) {
		ArrayList<ImportOrderEntry> res= new ArrayList<>();
		int start= 0;
		do {
			int end= str.indexOf(';', start);
			if (end == -1) {
				end= str.length();
			}
			res.add(ImportOrderEntry.fromSerialized(str.substring(start, end)));
			start= end + 1;
		} while (start < str.length());

		return res.toArray(new ImportOrderEntry[res.size()]);
	}

	private static String packOrderList(List<ImportOrderEntry> orderList) {
		StringBuilder buf= new StringBuilder();
		for (ImportOrderEntry entry : orderList) {
			buf.append(entry.serialize());
			buf.append(';');
		}
		return buf.toString();
	}

	private ImportOrderEntry[] getImportOrderPreference() {
		String str= getValue(PREF_IMPORTORDER);
		if (str != null) {
			return unpackOrderList(str);
		}
		return new ImportOrderEntry[0];
	}

	private int getImportNumberThreshold(Key key) {
		String thresholdStr= getValue(key);
		try {
			int threshold= Integer.parseInt(thresholdStr);
			if (threshold < 0) {
				threshold= Integer.MAX_VALUE;
			}
			return threshold;
		} catch (NumberFormatException e) {
			return Integer.MAX_VALUE;
		}
	}

}


