/*******************************************************************************
 * Copyright (c) 2006, 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
 *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 507831
 *******************************************************************************/
package org.eclipse.pde.internal.ui.commands;

import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.core.commands.AbstractParameterValueConverter;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.IParameter;
import org.eclipse.core.commands.IParameterValues;
import org.eclipse.core.commands.ParameterType;
import org.eclipse.core.commands.ParameterValueConversionException;
import org.eclipse.core.commands.ParameterValuesException;
import org.eclipse.core.commands.Parameterization;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.CommandException;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.internal.ui.PDEPluginImages;
import org.eclipse.pde.internal.ui.PDEUIMessages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ImageHyperlink;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.handlers.IHandlerService;

public class CommandDetails {

	private final HashMap<IParameter, String> fParameterToValue = new HashMap<>();
	private final ArrayList<ObjectParameterControl> fObjectParamList = new ArrayList<>();
	private final ArrayList<ValuesParameterControl> fValueParamList = new ArrayList<>();
	private final ArrayList<TextParameterControl> fTextParamList = new ArrayList<>();

	private final CommandComposerPart fCCP;
	private final FormToolkit fToolkit;
	private Command fSelectedCommand;
	private ParameterizedCommand fPreSel;

	private Text fComIDT;
	private Text fComPrev;
	private Button fSurroundCopyText;
	private Combo fFilterCombo;
	private Composite fParamComposite;
	private Composite fParamParent;
	private Label fParamLabel;
	private ImageHyperlink fCopyLink;
	private ImageHyperlink fExecLink;

	public CommandDetails(CommandComposerPart cv, Composite parent) {
		fCCP = cv;
		fToolkit = cv.getToolkit();
		createCommandDetails(parent);
	}

	private void createCommandDetails(Composite parent) {
		Composite c = fCCP.createComposite(parent, GridData.FILL_BOTH, 1, true, 5);

		Section section = fToolkit.createSection(c, ExpandableComposite.TITLE_BAR);
		section.setText(PDEUIMessages.CommandDetails_groupName);
		section.setLayoutData(new GridData(GridData.FILL_BOTH));

		Composite comp = fCCP.createComposite(section);

		createBasicInfo(comp);

		if (fCCP.getFilterType() == CommandComposerPart.F_FILTER_NOT_SET)
			createPreviewLabelComp(comp);
		createParameters(comp);

		section.setClient(comp);

		createLinks(c);
	}

	private void createBasicInfo(Composite parent) {
		Composite comp = fCCP.createComposite(parent, GridData.FILL_HORIZONTAL, 2, false, 0);
		fToolkit.createLabel(comp, PDEUIMessages.CommandDetails_id);
		fComIDT = fToolkit.createText(comp, PDEUIMessages.CommandDetails_noComSelected, SWT.BORDER);
		fComIDT.setEditable(false);
		fComIDT.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
	}

	private void createParameters(Composite parent) {
		Composite comp = fCCP.createComposite(parent, GridData.FILL_HORIZONTAL, 1, false, 0);

		fParamLabel = fToolkit.createLabel(comp, PDEUIMessages.CommandDetails_noParameters);
		fParamLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		fParamParent = parent;
		createBlankParamComp();
	}

	private void createLinks(Composite parent) {
		Composite comp = fCCP.createComposite(parent, GridData.FILL_HORIZONTAL | GridData.HORIZONTAL_ALIGN_END, 1, false, 0);

		fExecLink = fToolkit.createImageHyperlink(comp, SWT.NONE);
		final Image execImage = PDEPluginImages.DESC_RUN_EXC.createImage();
		fExecLink.setImage(execImage);
		fExecLink.setText(PDEUIMessages.CommandDetails_executeText);
		fExecLink.setToolTipText(PDEUIMessages.CommandDetails_execute);
		fExecLink.addHyperlinkListener(new ExecCommand());
		fExecLink.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
		fExecLink.setVisible(false);
		fExecLink.addDisposeListener(e -> execImage.dispose());

		fCopyLink = fToolkit.createImageHyperlink(comp, SWT.NONE);
		final Image clipImage = PDEPluginImages.DESC_CLIPBOARD.createImage();
		fCopyLink.setImage(clipImage);
		fCopyLink.setText(PDEUIMessages.CommandDetails_copyToClipboard);
		fCopyLink.setToolTipText(PDEUIMessages.CommandDetails_copytooltip);
		fCopyLink.addHyperlinkListener(new CopyToClipboard());
		fCopyLink.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
		fCopyLink.setVisible(false);
		fCopyLink.addDisposeListener(e -> clipImage.dispose());
	}

	private void createPreviewLabelComp(Composite parent) {
		Composite preLabelComp = fCCP.createComposite(parent, GridData.FILL_HORIZONTAL, 3, false, 0);
		fToolkit.createLabel(preLabelComp, PDEUIMessages.CommandDetails_preview, SWT.NONE).setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		fSurroundCopyText = fToolkit.createButton(preLabelComp, PDEUIMessages.CommandDetails_includeMarkup, SWT.CHECK);
		fSurroundCopyText.setToolTipText(PDEUIMessages.CommandDetails_markupTooltip);
		fSurroundCopyText.addSelectionListener(widgetSelectedAdapter(e -> updatePreviewText()));

		fFilterCombo = new Combo(preLabelComp, SWT.READ_ONLY | SWT.DROP_DOWN);
		CommandCopyFilter[] filters = CommandCopyFilter.getFilters();
		for (CommandCopyFilter filter : filters)
			fFilterCombo.add(filter.getLabelText());
		fFilterCombo.select(CommandCopyFilter.indexOf(CommandCopyFilter.NONE));
		fFilterCombo.addSelectionListener(widgetSelectedAdapter(e -> updatePreviewText()));
		fToolkit.adapt(fFilterCombo, true, true);

		fComPrev = fToolkit.createText(parent, "", SWT.MULTI | SWT.V_SCROLL | SWT.BORDER | SWT.WRAP); //$NON-NLS-1$
		GridData gd = new GridData(GridData.FILL_HORIZONTAL);
		gd.heightHint = 60;
		fComPrev.setLayoutData(gd);
		fComPrev.setEditable(false);
	}

	protected ParameterizedCommand buildParameterizedCommand() {
		ArrayList<Parameterization> list = new ArrayList<>(fParameterToValue.size());
		for (Entry<IParameter, String> entry : fParameterToValue.entrySet()) {
			list.add(new Parameterization(entry.getKey(), entry.getValue()));
		}
		Parameterization[] parameterizations = list.toArray(new Parameterization[list.size()]);

		ParameterizedCommand pCommand = new ParameterizedCommand(fSelectedCommand, parameterizations);
		return pCommand;
	}

	private class ExecCommand extends HyperlinkAdapter {
		@Override
		public void linkActivated(HyperlinkEvent e) {
			ParameterizedCommand pCommand = buildParameterizedCommand();
			try {
				IHandlerService service = getGlobalHandlerService();
				IEvaluationContext context = fCCP.getSnapshotContext();
				Object obj = service.executeCommandInContext(pCommand, null, context);
				String resultString = null;
				if (obj instanceof String) {
					resultString = (String) obj;
				} else {
					ParameterType returnType = pCommand.getCommand().getReturnType();
					if (returnType != null && returnType.getValueConverter() != null)
						resultString = returnType.getValueConverter().convertToString(obj);
				}
				if (resultString != null) {
					MessageDialog.openInformation(fComIDT.getShell(), PDEUIMessages.CommandDetails_commandResult, resultString);
				}
			} catch (CommandException ex) {
				MessageDialog.openError(fComIDT.getShell(), PDEUIMessages.CommandDetails_execError, ex.toString());
			}
		}
	}

	private String getFilteredCommand() {
		ParameterizedCommand pCommand = buildParameterizedCommand();
		String serializedCommand = pCommand.serialize();
		String markupLabel;
		try {
			markupLabel = fSelectedCommand.getName();
		} catch (NotDefinedException ex) {
			markupLabel = null;
		}
		int index;
		boolean surroundWithMarkup = false;
		if (fCCP.getFilterType() == CommandComposerPart.F_FILTER_NOT_SET) {
			surroundWithMarkup = fSurroundCopyText.getSelection();
			index = fFilterCombo.getSelectionIndex();
		} else {
			index = fCCP.getFilterType();
		}
		CommandCopyFilter ccf = CommandCopyFilter.getFilter(index);
		return ccf.filter(serializedCommand, surroundWithMarkup, markupLabel);
	}

	private class CopyToClipboard extends HyperlinkAdapter {
		@Override
		public void linkActivated(HyperlinkEvent e) {
			String filteredCommand = getFilteredCommand();

			Object[] data = new Object[] {filteredCommand, /* htmlBuffer.toString() */};
			Transfer[] transfers = new Transfer[] {TextTransfer.getInstance(),/* HTMLTransfer.getInstance() */};

			Clipboard clipboard = new Clipboard(null);
			clipboard.setContents(data, transfers, DND.CLIPBOARD);
			clipboard.dispose();
		}
	}

	private class ValuesParameterControl extends SelectionAdapter implements ModifyListener {
		private final IParameter fParameter;
		private final Map<String, Object> fValues;
		private final Combo fValuesCombo;
		private final Button fClearButton;

		public ValuesParameterControl(IParameter parameter, Map<String, Object> values, Combo valuesCombo,
				Button clearButton) {
			fParameter = parameter;
			fValues = values;

			fValuesCombo = valuesCombo;
			fValuesCombo.addModifyListener(this);
			if (fPreSel != null && fValues != null) {
				Object obj = fPreSel.getParameterMap().get(parameter.getId());
				if (obj != null) {
					for (Entry<String, Object> entry : fValues.entrySet()) {
						if (obj.equals(entry.getValue())) {
							fValuesCombo.setText(entry.getKey());
							break;
						}
					}
				}
			}

			fClearButton = clearButton;
			fClearButton.addSelectionListener(this);
		}

		// clear button pressed
		@Override
		public void widgetSelected(SelectionEvent e) {
			fValuesCombo.deselectAll();
			fParameterToValue.remove(fParameter);
		}

		// values combo changed
		@Override
		public void modifyText(ModifyEvent e) {
			String key = fValuesCombo.getText();
			String value = (String) fValues.get(key);
			if (value == null)
				fParameterToValue.remove(fParameter);
			else
				fParameterToValue.put(fParameter, value);
			updatePreviewText();
		}

		protected void dispose() {
			if (!fValuesCombo.isDisposed())
				fValuesCombo.removeModifyListener(this);
			if (!fClearButton.isDisposed())
				fClearButton.removeSelectionListener(this);
		}
	}

	private class ObjectParameterControl implements ModifyListener {
		private final IParameter fParameter;
		private final AbstractParameterValueConverter fValueConverter;
		private final Text fParameterText;

		public ObjectParameterControl(IParameter parameter, AbstractParameterValueConverter valueConverter, Text parameterText, Object selectedObject) {
			fParameter = parameter;
			fValueConverter = valueConverter;

			fParameterText = parameterText;
			fParameterText.addModifyListener(this);

			if (selectedObject != null)
				setParameterText(selectedObject);

			if (fPreSel != null) {
				Object obj = fPreSel.getParameterMap().get(parameter.getId());
				if (obj != null)
					fParameterText.setText(obj.toString());
			}
		}

		@Override
		public void modifyText(ModifyEvent e) {
			String text = fParameterText.getText();
			if ((text == null) || (text.trim().equals(""))) //$NON-NLS-1$
				fParameterToValue.remove(fParameter);
			else
				fParameterToValue.put(fParameter, text);
			updatePreviewText();
			validate();
		}

		private void setParameterText(Object selectedObject) {
			try {
				String converted = fValueConverter.convertToString(selectedObject);
				if (converted != null)
					fParameterText.setText(converted);
			} catch (ParameterValueConversionException ex) {
				//
			}
		}

		protected void dispose() {
			if (!fParameterText.isDisposed())
				fParameterText.removeModifyListener(this);
		}

		private void validate() {
			String text = fParameterText.getText();
			String error = null;
			if (text.length() > 0) {
				try {
					fValueConverter.convertToObject(text);
				} catch (ParameterValueConversionException e1) {
					error = e1.getMessage();
				}
			}
			if (error == null)
				fCCP.setMessage(null, IMessageProvider.NONE);
			else
				fCCP.setMessage(NLS.bind(PDEUIMessages.CommandDetails_paramValueMessage, fParameter.getName(), error), IMessageProvider.WARNING);
		}
	}

	private class TextParameterControl implements ModifyListener {
		private final IParameter fParameter;
		private final Text fParameterText;

		public TextParameterControl(IParameter parameter, Text parameterText) {
			fParameter = parameter;
			fParameterText = parameterText;
			fParameterText.addModifyListener(this);

			if (fPreSel != null) {
				Object obj = fPreSel.getParameterMap().get(parameter.getId());
				if (obj != null)
					fParameterText.setText(obj.toString());
			}
		}

		@Override
		public void modifyText(ModifyEvent e) {
			String text = fParameterText.getText();
			if ((text == null) || (text.trim().equals(""))) //$NON-NLS-1$
				fParameterToValue.remove(fParameter);
			else
				fParameterToValue.put(fParameter, text);
			updatePreviewText();
		}

		public void dispose() {
			if (!fParameterText.isDisposed())
				fParameterText.removeModifyListener(this);
		}
	}

	protected void dispose() {
		for (int i = 0; i < fObjectParamList.size(); i++)
			fObjectParamList.get(i).dispose();
		for (int i = 0; i < fValueParamList.size(); i++)
			fValueParamList.get(i).dispose();
		for (int i = 0; i < fTextParamList.size(); i++)
			fTextParamList.get(i).dispose();
	}

	private void populateParams(Command command, Object selectedObject) throws NotDefinedException {

		createBlankParamComp();

		IParameter[] parameters = command.getParameters();
		if (parameters == null || parameters.length == 0) {
			fParamLabel.setText(PDEUIMessages.CommandDetails_noParameters);
		} else {
			fParamLabel.setText(PDEUIMessages.CommandDetails_numParams);
			Composite paramLine = fToolkit.createComposite(fParamComposite);

			GridLayout paramLineLayout = new GridLayout();
			paramLine.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
			paramLineLayout.numColumns = 3;
			paramLineLayout.marginHeight = 0;
			paramLine.setLayout(paramLineLayout);
			for (IParameter parameter : parameters) {
				String nameText = parameter.getName();
				if (!parameter.isOptional())
					nameText += '*';
				fToolkit.createLabel(paramLine, NLS.bind(PDEUIMessages.CommandDetails_param, nameText));

				IParameterValues parameterValues = getParameterValues(parameter);
				if (parameterValues != null) {
					Combo parameterValuesCombo = new Combo(paramLine, SWT.READ_ONLY | SWT.DROP_DOWN);
					parameterValuesCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
					fToolkit.adapt(parameterValuesCombo, true, true);

					Map<String, Object> values = parameterValues.getParameterValues();
					for (String key : values.keySet())
						parameterValuesCombo.add(key);

					Button clearButton = fToolkit.createButton(paramLine, PDEUIMessages.CommandDetails_clear, SWT.PUSH);

					fValueParamList.add(new ValuesParameterControl(parameter, values, parameterValuesCombo, clearButton));

					continue;
				}

				ParameterType parameterType = command.getParameterType(parameter.getId());
				if ((parameterType != null) && (parameterType.getValueConverter() != null)) {
					Text parameterText = fToolkit.createText(paramLine, "", SWT.SINGLE | SWT.BORDER); //$NON-NLS-1$
					GridData gd = new GridData(GridData.FILL_HORIZONTAL);
					gd.horizontalSpan = 2;
					parameterText.setLayoutData(gd);
					fObjectParamList.add(new ObjectParameterControl(parameter, parameterType.getValueConverter(), parameterText, selectedObject));

					continue;
				}

				Text parameterText = fToolkit.createText(paramLine, "", SWT.SINGLE | SWT.BORDER); //$NON-NLS-1$
				GridData gd = new GridData(GridData.FILL_HORIZONTAL);
				gd.horizontalSpan = 2;
				parameterText.setLayoutData(gd);
				fTextParamList.add(new TextParameterControl(parameter, parameterText));
			}
		}
		// only use preselected on the first details showing
		fPreSel = null;
		fParamParent.layout();
	}

	private IParameterValues getParameterValues(IParameter parameter) {
		try {
			return parameter.getValues();
		} catch (ParameterValuesException ex) {
			return null;
		}
	}

	public void showDetailsFor(Object object) {
		if (object instanceof ParameterizedCommand)
			object = (fPreSel = (ParameterizedCommand) object).getCommand();

		if (!(object instanceof Command)) {
			resetAllFields();
			return;
		}
		fSelectedCommand = (Command) object;
		fComIDT.setText(fSelectedCommand.getId());

		fParameterToValue.clear();
		fObjectParamList.clear();
		fValueParamList.clear();

		fExecLink.setVisible(fSelectedCommand.isEnabled());
		fCopyLink.setVisible(true);
		try {
			populateParams(fSelectedCommand, object);
		} catch (NotDefinedException e) {
			createNoParamComp();
		}
		updatePreviewText();
	}

	private void resetAllFields() {
		fSelectedCommand = null;
		fComIDT.setText(PDEUIMessages.CommandDetails_noComSelected);
		fParamLabel.setText(PDEUIMessages.CommandDetails_noParameters);

		if (fComPrev != null)
			fComPrev.setText(""); //$NON-NLS-1$

		fExecLink.setVisible(false);
		fCopyLink.setVisible(false);

		fParameterToValue.clear();
		fObjectParamList.clear();
		fValueParamList.clear();

		createNoParamComp();
	}

	private void createNoParamComp() {
		createBlankParamComp();
		fParamParent.layout();
	}

	private void createBlankParamComp() {
		if (fParamComposite != null)
			fParamComposite.dispose();
		fParamComposite = fCCP.createComposite(fParamParent, GridData.FILL_BOTH, 1, true, 0);
	}

	private void updatePreviewText() {
		if (fComPrev != null)
			fComPrev.setText(getFilteredCommand());
	}

	protected Command getCommand() {
		return fSelectedCommand;
	}

	public String getCommandName() {
		if (fSelectedCommand != null)
			try {
				return fSelectedCommand.getName();
			} catch (NotDefinedException e) {
				return fSelectedCommand.getId();
			}
		return null;
	}

	public String getSerializedString() {
		if (fSelectedCommand != null)
			return getFilteredCommand();
		return null;
	}

	public HashMap<IParameter, String> getParameters() {
		if (fSelectedCommand != null)
			return fParameterToValue;

		return null;
	}

	private IHandlerService getGlobalHandlerService() {
		return PlatformUI.getWorkbench().getService(IHandlerService.class);
	}
}
