/*******************************************************************************
 * Copyright (c) 2009, 2015 ARM Limited 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:
 * ARM Limited - Initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.debug.internal.ui.sourcelookup;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.debug.core.sourcelookup.MappingSourceContainer;
import org.eclipse.cdt.debug.internal.core.sourcelookup.MapEntrySourceContainer;
import org.eclipse.cdt.debug.internal.ui.CDebugImages;
import org.eclipse.cdt.debug.internal.ui.ICDebugHelpContextIds;
import org.eclipse.cdt.debug.internal.ui.SWTUtil;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.DialogCellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
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.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;

public class MappingSourceContainerDialog extends TitleAreaDialog {

	class EntryCellModifier implements ICellModifier {
		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ICellModifier#canModify(java.lang.Object, java.lang.String)
		 */
		@Override
		public boolean canModify(Object element, String property) {
			return (CP_COMPILATION_PATH.equals(property) || CP_FILE_SYSTEM_PATH.equals(property));
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ICellModifier#getValue(java.lang.Object, java.lang.String)
		 */
		@Override
		public Object getValue(Object element, String property) {
			if (element instanceof MapEntrySourceContainer) {
				MapEntrySourceContainer entry = (MapEntrySourceContainer) element;
				if (CP_COMPILATION_PATH.equals(property))
					return entry.getBackendPathStr();
				if (CP_FILE_SYSTEM_PATH.equals(property))
					return entry.getLocalPath().toOSString();
			}
			return null;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ICellModifier#modify(java.lang.Object, java.lang.String, java.lang.Object)
		 */
		@Override
		public void modify(Object element, String property, Object value) {
			MapEntrySourceContainer entry = (element instanceof Item)
					? (MapEntrySourceContainer) ((Item) element).getData()
					: (MapEntrySourceContainer) element;
			boolean isDirty = false;
			if (CP_COMPILATION_PATH.equals(property)) {
				entry.setBackendPathStr((String) value);
				isDirty = true;
			} else if (CP_FILE_SYSTEM_PATH.equals(property)) {
				entry.setLocalPath(new Path((String) value));
				isDirty = true;
			}
			if (isDirty)
				refresh();
		}
	}

	class DirectoryCellEditor extends DialogCellEditor {

		DirectoryCellEditor(Composite parent) {
			super(parent);
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.DialogCellEditor#openDialogBox(org.eclipse.swt.widgets.Control)
		 */
		@Override
		protected Object openDialogBox(Control cellEditorWindow) {
			TableItem[] selection = ((Table) cellEditorWindow.getParent()).getSelection();
			DirectoryDialog dialog = new DirectoryDialog(cellEditorWindow.getShell());
			dialog.setFilterPath(selection[0].getText(1));
			return dialog.open();
		}
	}

	class EntryLabelProvider extends LabelProvider implements ITableLabelProvider {

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int)
		 */
		@Override
		public Image getColumnImage(Object element, int columnIndex) {
			if (element instanceof MapEntrySourceContainer && columnIndex == 0) {
				return CDebugImages.get(CDebugImages.IMG_OBJS_PATH_MAP_ENTRY);
			}
			return null;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int)
		 */
		@Override
		public String getColumnText(Object element, int columnIndex) {
			if (element instanceof MapEntrySourceContainer) {
				MapEntrySourceContainer entry = (MapEntrySourceContainer) element;
				if (columnIndex == 0)
					return entry.getBackendPathStr();
				if (columnIndex == 1)
					return entry.getLocalPath().toOSString();
			}
			return null;
		}
	}

	// Column properties
	private static final String CP_COMPILATION_PATH = "compilationPath"; //$NON-NLS-1$
	private static final String CP_FILE_SYSTEM_PATH = "fileSystemPath"; //$NON-NLS-1$

	private MappingSourceContainer fOriginalContainer;
	private MappingSourceContainer fContainer;

	private Text fNameText;
	private Button fMappingWithBackendEnabledCheck;
	private TableViewer fViewer;
	private Button fAddButton;
	private Button fRemoveButton;
	private Button fUpButton;
	private Button fDownButton;

	private ControlListener fTableListener;

	private boolean fIsValid = true;

	public MappingSourceContainerDialog(Shell shell, MappingSourceContainer container) {
		super(shell);
		fOriginalContainer = container;
		fContainer = container.copy();
		fTableListener = new ControlListener() {
			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.ControlListener#controlMoved(org.eclipse.swt.events.ControlEvent)
			 */
			@Override
			public void controlMoved(ControlEvent e) {
				MappingSourceContainerDialog.this.controlMoved(e);
			}

			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.ControlListener#controlResized(org.eclipse.swt.events.ControlEvent)
			 */
			@Override
			public void controlResized(ControlEvent e) {
				MappingSourceContainerDialog.this.controlResized(e);
			}
		};
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.TrayDialog#close()
	 */
	@Override
	public boolean close() {
		fViewer.getTable().removeControlListener(fTableListener);
		fContainer.dispose();
		return super.close();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#isResizable()
	 */
	@Override
	protected boolean isResizable() {
		return true;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
	 */
	@Override
	protected void configureShell(Shell newShell) {
		super.configureShell(newShell);
		newShell.setText(SourceLookupUIMessages.PathMappingDialog_16);
		newShell.setToolTipText(SourceLookupUIMessages.MappingSourceContainerDialog_0);
		newShell.setImage(CDebugImages.get(CDebugImages.IMG_OBJS_PATH_MAPPING));
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#createContents(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	protected Control createContents(Composite parent) {
		Control control = super.createContents(parent);
		initialize();
		return control;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	protected Control createDialogArea(Composite parent) {
		Composite control = (Composite) super.createDialogArea(parent);
		setTitle(SourceLookupUIMessages.PathMappingDialog_0);
		setTitleImage(CDebugImages.get(CDebugImages.IMG_WIZBAN_PATH_MAPPING));

		Composite composite = new Composite(control, SWT.None);
		composite.setLayout(new GridLayout(2, false));
		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		createNameArea(composite);
		createViewer(composite);
		createViewerButtonBar(composite);
		createMappingWithBackendEnabledArea(composite);

		PlatformUI.getWorkbench().getHelpSystem().setHelp(getShell(),
				ICDebugHelpContextIds.SOURCE_PATH_MAP_ENTRY_DIALOG);
		return control;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#okPressed()
	 */
	@Override
	protected void okPressed() {
		fOriginalContainer.clear();
		fOriginalContainer.setName(fNameText.getText().trim());
		fOriginalContainer.setIsMappingWithBackendEnabled(fMappingWithBackendEnabledCheck.getSelection());
		try {
			fOriginalContainer.addMapEntries((MapEntrySourceContainer[]) fContainer.getSourceContainers());
		} catch (CoreException e) {
		}
		super.okPressed();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.TitleAreaDialog#setErrorMessage(java.lang.String)
	 */
	@Override
	public void setErrorMessage(String newErrorMessage) {
		fIsValid = (newErrorMessage == null);
		super.setErrorMessage(newErrorMessage);
	}

	public MappingSourceContainer getContainer() {
		return fOriginalContainer;
	}

	private void createNameArea(Composite parent) {
		Composite composite = new Composite(parent, SWT.None);
		composite.setLayout(new GridLayout(2, false));
		composite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 2, 1));

		Label label = new Label(composite, SWT.NONE);
		label.setText(SourceLookupUIMessages.PathMappingDialog_12);
		fNameText = new Text(composite, SWT.BORDER | SWT.SINGLE);
		GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
		data.widthHint = 200;
		fNameText.setLayoutData(data);
		fNameText.addModifyListener(new ModifyListener() {
			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
			 */
			@Override
			public void modifyText(ModifyEvent e) {
			}
		});
	}

	private void createMappingWithBackendEnabledArea(Composite parent) {
		Composite composite = new Composite(parent, SWT.None);
		composite.setLayout(new GridLayout(1, false));
		composite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 2, 1));

		fMappingWithBackendEnabledCheck = new Button(composite, SWT.CHECK);
		fMappingWithBackendEnabledCheck.setText(SourceLookupUIMessages.PathMappingDialog_MappingWithBackendEnabled);
		fMappingWithBackendEnabledCheck
				.setToolTipText(SourceLookupUIMessages.PathMappingDialog_MappingWithBackendEnabledTooltip);
		GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
		fMappingWithBackendEnabledCheck.setLayoutData(data);
	}

	private void createViewer(Composite parent) {
		Composite tableComp = new Composite(parent, SWT.NONE);
		tableComp.setLayout(new GridLayout());
		tableComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		fViewer = new TableViewer(tableComp, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
		Table table = fViewer.getTable();
		table.setLinesVisible(true);
		table.setHeaderVisible(true);
		GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
		data.widthHint = 500;
		data.heightHint = 200;
		table.setLayoutData(data);

		table.addControlListener(fTableListener);

		TableColumn nameColumn = new TableColumn(table, SWT.NULL);
		nameColumn.setResizable(true);
		nameColumn.setText(SourceLookupUIMessages.PathMappingDialog_1);
		nameColumn.setToolTipText(SourceLookupUIMessages.MappingSourceContainerDialog_1);

		TableColumn valueColumn = new TableColumn(table, SWT.NULL);
		valueColumn.setResizable(true);
		valueColumn.setText(SourceLookupUIMessages.PathMappingDialog_2);
		valueColumn.setToolTipText(SourceLookupUIMessages.MappingSourceContainerDialog_2);

		fViewer.setColumnProperties(new String[] { CP_COMPILATION_PATH, CP_FILE_SYSTEM_PATH, });

		fViewer.setContentProvider(new IStructuredContentProvider() {
			/* (non-Javadoc)
			 * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
			 */
			@Override
			public Object[] getElements(Object inputElement) {
				if (inputElement instanceof MappingSourceContainer) {
					try {
						return ((MappingSourceContainer) inputElement).getSourceContainers();
					} catch (CoreException e) {
					}
				}
				return new Object[0];
			}

			/* (non-Javadoc)
			 * @see org.eclipse.jface.viewers.IContentProvider#dispose()
			 */
			@Override
			public void dispose() {
			}

			/* (non-Javadoc)
			 * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
			 */
			@Override
			public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			}
		});

		fViewer.setCellEditors(new CellEditor[] { new TextCellEditor(table), new DirectoryCellEditor(table), });

		fViewer.setCellModifier(new EntryCellModifier());

		fViewer.setLabelProvider(new EntryLabelProvider());

		fViewer.addSelectionChangedListener(new ISelectionChangedListener() {
			/* (non-Javadoc)
			 * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
			 */
			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				updateViewerButtons();
			}
		});
	}

	private void createViewerButtonBar(Composite parent) {
		Composite buttonComp = new Composite(parent, SWT.NONE);
		buttonComp.setLayout(new GridLayout());
		buttonComp.setLayoutData(new GridData(SWT.RIGHT, SWT.FILL, false, false));

		fAddButton = createPushButton(buttonComp, SourceLookupUIMessages.MappingSourceContainerDialog_3);
		fAddButton.addSelectionListener(new SelectionAdapter() {
			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
			 */
			@Override
			public void widgetSelected(SelectionEvent event) {
				addPathEntry();
			}
		});

		fRemoveButton = createPushButton(buttonComp, SourceLookupUIMessages.PathMappingDialog_15);
		fRemoveButton.addSelectionListener(new SelectionAdapter() {
			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
			 */
			@Override
			public void widgetSelected(SelectionEvent event) {
				removePathEntries();
			}
		});

		fUpButton = createPushButton(buttonComp, SourceLookupUIMessages.MappingSourceContainerDialog_4);
		fUpButton.addSelectionListener(new SelectionAdapter() {
			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
			 */
			@Override
			public void widgetSelected(SelectionEvent event) {
				move(true);
			}
		});

		fDownButton = createPushButton(buttonComp, SourceLookupUIMessages.MappingSourceContainerDialog_5);
		fDownButton.addSelectionListener(new SelectionAdapter() {
			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
			 */
			@Override
			public void widgetSelected(SelectionEvent event) {
				move(false);
			}
		});
	}

	private void initialize() {
		fNameText.setText(fContainer.getName());
		fNameText.selectAll();
		fMappingWithBackendEnabledCheck.setSelection(fContainer.isMappingWithBackendEnabled());
		fViewer.setInput(fContainer);
		updateViewerButtons();
	}

	private Button createPushButton(Composite parent, String label) {
		Button button = new Button(parent, SWT.PUSH);
		button.setText(label);
		GridData gd = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
		gd.widthHint = SWTUtil.getButtonWidthHint(button);
		button.setLayoutData(gd);
		return button;
	}

	private MapEntrySourceContainer[] getSelectedEntries() {
		List<?> list = ((IStructuredSelection) fViewer.getSelection()).toList();
		return list.toArray(new MapEntrySourceContainer[list.size()]);
	}

	private void updateErrorMessage() {
		setErrorMessage(null);
		try {
			ISourceContainer[] containers = fContainer.getSourceContainers();
			if (containers.length == 0)
				return;
			for (ISourceContainer c : containers) {
				MapEntrySourceContainer entry = (MapEntrySourceContainer) c;
				String backendPath = entry.getBackendPathStr();
				if (backendPath.isEmpty()) {
					setErrorMessage(SourceLookupUIMessages.PathMappingDialog_5);
					break;
				}
				IPath localPath = entry.getLocalPath();
				if (localPath.isEmpty()) {
					setErrorMessage(SourceLookupUIMessages.PathMappingDialog_7);
					break;
				}
				if (!localPath.toFile().exists()) {
					setErrorMessage(SourceLookupUIMessages.PathMappingDialog_8);
					break;
				}
				if (!localPath.toFile().isDirectory()) {
					setErrorMessage(SourceLookupUIMessages.PathMappingDialog_9);
					break;
				}
				if (!localPath.toFile().isAbsolute()) {
					setErrorMessage(SourceLookupUIMessages.PathMappingDialog_10);
					break;
				}
			}
		} catch (CoreException e) {
			// ignore
		}
	}

	private void updateViewerButtons() {
		boolean ok = true;
		boolean remove = true;
		boolean up = true;
		boolean down = true;

		try {
			ISourceContainer[] allEntries = fContainer.getSourceContainers();
			MapEntrySourceContainer[] entries = getSelectedEntries();
			if (entries.length == 0) {
				remove = false;
			}

			if (entries.length != 1) {
				up = false;
				down = false;
			} else {
				up = (!entries[0].equals(allEntries[0]));
				down = (!entries[0].equals(allEntries[allEntries.length - 1]));
			}

			ok = (allEntries.length != 0 && fIsValid);
		} catch (CoreException e) {
			// ignore, shouldn't happen
		}
		getButton(IDialogConstants.OK_ID).setEnabled(ok);
		fRemoveButton.setEnabled(remove);
		fUpButton.setEnabled(up);
		fDownButton.setEnabled(down);
	}

	private void refresh() {
		ISelection s = fViewer.getSelection();
		fViewer.refresh();
		fViewer.setSelection(s);
		updateErrorMessage();
		updateViewerButtons();
	}

	void controlMoved(ControlEvent e) {
	}

	void controlResized(ControlEvent e) {
		// resize columns
		Table table = fViewer.getTable();
		int width = table.getSize().x;
		if (width > 0) {
			TableColumn[] columns = table.getColumns();
			int colWidth = width / columns.length;
			for (TableColumn col : columns) {
				if (col.getWidth() == 0) {
					col.setWidth(colWidth);
				}
			}
		}
	}

	void addPathEntry() {
		MapEntrySourceContainer entry = new MapEntrySourceContainer();
		fContainer.addMapEntry(entry);
		fViewer.refresh();
		fViewer.setSelection(new StructuredSelection(entry), true);
		updateViewerButtons();
		fViewer.editElement(entry, 0);
	}

	void removePathEntries() {
		MapEntrySourceContainer[] entries = getSelectedEntries();
		fContainer.removeMapEntries(entries);
		refresh();
	}

	void move(boolean up) {
		MapEntrySourceContainer[] selEntries = getSelectedEntries();
		if (selEntries.length != 1)
			return;
		MapEntrySourceContainer entry = selEntries[0];
		try {
			ISourceContainer[] containers = fContainer.getSourceContainers();
			List<MapEntrySourceContainer> list = new ArrayList<>(containers.length);
			for (ISourceContainer container : containers) {
				list.add(((MapEntrySourceContainer) container).copy());
			}
			int index = list.indexOf(entry);
			list.remove(index);
			index = (up) ? index - 1 : index + 1;
			list.add(index, entry);
			fContainer.clear();
			fContainer.addMapEntries(list.toArray(new MapEntrySourceContainer[list.size()]));
			refresh();
		} catch (CoreException e) {
		} catch (IndexOutOfBoundsException e) {
		}
	}
}
