/*******************************************************************************
 * Copyright (c) 2015, 2018 Red Hat Inc. and others.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Red Hat - Initial Contribution
 *******************************************************************************/
package org.eclipse.linuxtools.internal.docker.ui.launch;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.IBeanValueProperty;
import org.eclipse.core.databinding.beans.typed.BeanProperties;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.property.Properties;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.linuxtools.docker.ui.Activator;
import org.eclipse.linuxtools.internal.docker.ui.SWTImagesFactory;
import org.eclipse.linuxtools.internal.docker.ui.wizards.ContainerDataVolumeDialog;
import org.eclipse.linuxtools.internal.docker.ui.wizards.DataVolumeModel;
import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunResourceVolumesVariablesModel;
import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunResourceVolumesVariablesModel.MountType;
import org.eclipse.linuxtools.internal.docker.ui.wizards.WizardMessages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;

public class RunImageVolumesTab extends AbstractLaunchConfigurationTab {

	private static final String TAB_NAME = "RunVolumesTab.name"; //$NON-NLS-1$

	private static final int COLUMNS = 3;

	private final DataBindingContext dbc = new DataBindingContext();
	private ImageRunResourceVolumesVariablesModel model = null;

	public RunImageVolumesTab(ImageRunResourceVolumesVariablesModel model) {
		this.model = model;
	}

	public ImageRunResourceVolumesVariablesModel getModel() {
		return model;
	}

	@Override
	public void createControl(Composite parent) {
		final Composite container = new Composite(parent, SWT.NONE);
		GridLayoutFactory.fillDefaults().numColumns(COLUMNS).margins(6, 6)
				.applyTo(container);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
				.applyTo(container);
		if (model == null) {
			setErrorMessage(LaunchMessages.getString("NoConnectionError.msg"));
		} else {
			setErrorMessage(null);
			createVolumeSettingsContainer(container);
		}
		setControl(container);
	}

	private void createVolumeSettingsContainer(final Composite container) {
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(false, false).span(3, 1)
				.applyTo(new Label(container, SWT.NONE));
		final Label volumesLabel = new Label(container, SWT.NONE);
		volumesLabel.setText(WizardMessages
				.getString("ImageRunResourceVolVarPage.dataVolumesLabel")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(true, false).span(COLUMNS, 1).applyTo(volumesLabel);
		final CheckboxTableViewer dataVolumesTableViewer = createVolumesTable(
				container);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
				.grab(true, true).hint(200, 100)
				.applyTo(dataVolumesTableViewer.getTable());
		// update table content when selected image changes
		bind(dataVolumesTableViewer, model.getDataVolumes(),
				BeanProperties.values(DataVolumeModel.class,
						DataVolumeModel.CONTAINER_PATH, DataVolumeModel.MOUNT,
						DataVolumeModel.READ_ONLY_VOLUME));
		final IObservableSet selectedVolumesObservable = BeanProperties
				.set(ImageRunResourceVolumesVariablesModel.SELECTED_DATA_VOLUMES)
				.observe(model);
		dbc.bindSet(
				ViewersObservables.observeCheckedElements(
						dataVolumesTableViewer, DataVolumeModel.class),
				selectedVolumesObservable);
		dataVolumesTableViewer.addCheckStateListener(onCheckStateChanged());

		// initializes the checkboxes selection upon loading the model.
				// remove ?
				// selectedVolumesObservable.addChangeListener(new
				// IChangeListener() {
				//
				// @Override
				// public void handleChange(ChangeEvent event) {
				// final IObservableSet observable = (IObservableSet) event
				// .getObservable();
				// for (Iterator<?> iterator = observable.iterator(); iterator
				// .hasNext();) {
				// final DataVolumeModel volume = (DataVolumeModel) iterator
				// .next();
				// dataVolumesTableViewer.setChecked(volume, true);
				// }
				// updateLaunchConfigurationDialog();
				// }
				// });

		// buttons
		final Composite buttonsContainers = new Composite(container, SWT.NONE);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
				.grab(false, false).applyTo(buttonsContainers);
		GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0)
				.spacing(SWT.DEFAULT, 0).applyTo(buttonsContainers);

		final Button addButton = new Button(buttonsContainers, SWT.NONE);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
				.grab(true, false).applyTo(addButton);
		addButton.setText(WizardMessages
				.getString("ImageRunResourceVolVarPage.addButton")); //$NON-NLS-1$
		addButton.addSelectionListener(onAddDataVolume(dataVolumesTableViewer));
		final Button editButton = new Button(buttonsContainers, SWT.NONE);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
				.grab(true, false).applyTo(editButton);
		editButton.setText(WizardMessages
				.getString("ImageRunResourceVolVarPage.editButton")); //$NON-NLS-1$
		editButton
				.addSelectionListener(onEditDataVolume(dataVolumesTableViewer));
		editButton.setEnabled(false);
		final Button removeButton = new Button(buttonsContainers, SWT.NONE);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP)
				.grab(true, false).applyTo(removeButton);
		removeButton.setText(WizardMessages
				.getString("ImageRunResourceVolVarPage.removeButton")); //$NON-NLS-1$
		removeButton.addSelectionListener(
				onRemoveDataVolumes(dataVolumesTableViewer));
		removeButton.setEnabled(false);
		// disable the edit and removeButton if the table is empty
		dataVolumesTableViewer.addSelectionChangedListener(
				onSelectionChanged(editButton, removeButton));

	}

	private ICheckStateListener onCheckStateChanged() {
		return e -> {
			DataVolumeModel element = (DataVolumeModel) e.getElement();
			if (e.getChecked()) {
				model.getSelectedDataVolumes().add(element);
				element.setSelected(true);
			} else {
				model.getSelectedDataVolumes().remove(element);
				element.setSelected(false);
			}
			updateLaunchConfigurationDialog();
		};
	}

	/**
	 * Same as
	 * {@link org.eclipse.jface.databinding.viewers.ViewerSupport#bind(StructuredViewer, IObservableList, org.eclipse.core.databinding.property.value.IValueProperty[])
	 * but with a custom LabelProvider, DataVolumesLabelProvider
	 *
	 * @param viewer
	 * @param input
	 * @param labelProperties
	 */
	private void bind(final StructuredViewer viewer,
			final IObservableList input,
			final IBeanValueProperty[] labelProperties) {
		final ObservableListContentProvider contentProvider = new ObservableListContentProvider();
		if (viewer.getInput() != null) {
			viewer.setInput(null);
		}
		viewer.setContentProvider(contentProvider);
		viewer.setLabelProvider(
				new DataVolumesLabelProvider(Properties.observeEach(
						contentProvider.getKnownElements(), labelProperties)));
		if (input != null) {
			viewer.setInput(input);
		}

	}

	private ISelectionChangedListener onSelectionChanged(
			final Button... targetButtons) {
		return e -> {
			if (e.getSelection().isEmpty()) {
				setControlsEnabled(targetButtons, false);
				updateLaunchConfigurationDialog();
			} else {
				setControlsEnabled(targetButtons, true);
				updateLaunchConfigurationDialog();
			}
		};
	}

	private SelectionListener onAddDataVolume(
			final CheckboxTableViewer dataVolumesTableViewer) {
		return SelectionListener.widgetSelectedAdapter(e -> {
			final ContainerDataVolumeDialog dialog = new ContainerDataVolumeDialog(
					getShell(), model.getConnection());
			dialog.create();
			if (dialog.open() == IDialogConstants.OK_ID) {
				model.getDataVolumes().add(dialog.getDataVolume());
				model.getSelectedDataVolumes().add(dialog.getDataVolume());
				dialog.getDataVolume().setSelected(true);
				dataVolumesTableViewer.setChecked(dialog.getDataVolume(), true);
				updateLaunchConfigurationDialog();
			}
		});
	}

	private SelectionListener onEditDataVolume(
			final CheckboxTableViewer dataVolumesTableViewer) {
		return SelectionListener.widgetSelectedAdapter(e -> {
				final IStructuredSelection selection = (IStructuredSelection) dataVolumesTableViewer
						.getSelection();
				if (selection.isEmpty()) {
					return;
				}
				final DataVolumeModel selectedDataVolume = (DataVolumeModel) selection
						.getFirstElement();
				final ContainerDataVolumeDialog dialog = new ContainerDataVolumeDialog(
						getShell(), model.getConnection(), selectedDataVolume);
				dialog.create();
				if (dialog.open() == IDialogConstants.OK_ID) {
					final DataVolumeModel dialogDataVolume = dialog
							.getDataVolume();
					selectedDataVolume.setContainerMount(
							dialogDataVolume.getContainerMount());
					selectedDataVolume
							.setMountType(dialogDataVolume.getMountType());
					selectedDataVolume.setHostPathMount(
							dialogDataVolume.getHostPathMount());
					selectedDataVolume.setContainerMount(
							dialogDataVolume.getContainerMount());
					selectedDataVolume
							.setReadOnly(dialogDataVolume.isReadOnly());
					model.getSelectedDataVolumes().add(selectedDataVolume);
					dataVolumesTableViewer.setChecked(selectedDataVolume, true);
					updateLaunchConfigurationDialog();
				}
			}
		);
	}

	private SelectionListener onRemoveDataVolumes(
			final TableViewer dataVolumesTableViewer) {
		return SelectionListener.widgetSelectedAdapter(e -> {
			final IStructuredSelection selection = dataVolumesTableViewer
					.getStructuredSelection();
			for (@SuppressWarnings("unchecked")
			Iterator<DataVolumeModel> iterator = selection.iterator(); iterator
					.hasNext();) {
				final DataVolumeModel volume = iterator.next();
				model.removeDataVolume(volume);
				model.getSelectedDataVolumes().remove(volume);
			}
			updateLaunchConfigurationDialog();
		});
	}

	private CheckboxTableViewer createVolumesTable(final Composite container) {
		final Table table = new Table(container, SWT.CHECK | SWT.BORDER
				| SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL);
		final CheckboxTableViewer tableViewer = new CheckboxTableViewer(table);
		table.setHeaderVisible(true);
		table.setLinesVisible(true);
		addTableViewerColumn(tableViewer,
				WizardMessages.getString(
						"ImageRunResourceVolVarPage.containerPathColumn"), //$NON-NLS-1$
				150);
		addTableViewerColumn(tableViewer,
				WizardMessages
						.getString("ImageRunResourceVolVarPage.mountColumn"), //$NON-NLS-1$
				150);
		addTableViewerColumn(tableViewer,
				WizardMessages
						.getString("ImageRunResourceVolVarPage.readonlyColumn"), //$NON-NLS-1$
				60);
		return tableViewer;
	}

	private TableViewerColumn addTableViewerColumn(
			final TableViewer tableViewer,
			final String title, final int width) {
		final TableViewerColumn viewerColumn = new TableViewerColumn(
				tableViewer, SWT.NONE);
		final TableColumn column = viewerColumn.getColumn();
		if (title != null) {
			column.setText(title);
		}
		column.setWidth(width);
		return viewerColumn;
	}

	private static void setControlsEnabled(final Control[] controls,
			final boolean enabled) {
		for (Control control : controls) {
			control.setEnabled(enabled);
		}
	}

	private static final class DataVolumesLabelProvider
			extends ObservableMapLabelProvider {

		private Image CONTAINER_IMAGE = SWTImagesFactory.DESC_CONTAINER
				.createImage();
		private Image FOLDER_CLOSED_IMAGE = SWTImagesFactory.DESC_FOLDER_CLOSED
				.createImage();
		private Image FILE_IMAGE = SWTImagesFactory.DESC_FILE.createImage();

		public DataVolumesLabelProvider(final IObservableMap[] attributeMaps) {
			super(attributeMaps);
		}

		@Override
		public void dispose() {
			CONTAINER_IMAGE.dispose();
			FOLDER_CLOSED_IMAGE.dispose();
			FILE_IMAGE.dispose();
			super.dispose();
		}

		@Override
		public Image getColumnImage(Object element, int columnIndex) {
			final DataVolumeModel dataVolume = ((DataVolumeModel) element);
			if (dataVolume.getMountType() != null && columnIndex == 1) {
				switch (dataVolume.getMountType()) {
				case CONTAINER:
					return CONTAINER_IMAGE;
				case HOST_FILE_SYSTEM:
					final File hostFile = new File(dataVolume.getMount());
					if (!hostFile.exists() || hostFile.isDirectory()) {
						return FOLDER_CLOSED_IMAGE;
					} else {
						return FILE_IMAGE;
					}
				default:
					return null;
				}
			}
			return null;
		}

		@Override
		public String getColumnText(Object element, int columnIndex) {
			final DataVolumeModel dataVolume = ((DataVolumeModel) element);
			switch (columnIndex) {
			case 0:
				return dataVolume.getContainerPath();
			case 1:
				return dataVolume.getMount();
			case 2:
				if (dataVolume.getMountType() != MountType.HOST_FILE_SYSTEM) {
					return null;
				} else if (dataVolume.isReadOnly()) {
					return WizardMessages
							.getString("ImageRunResourceVolVarPage.true"); //$NON-NLS-1$
				}
				return WizardMessages
						.getString("ImageRunResourceVolVarPage.false"); //$NON-NLS-1$
			default:
				return null;
			}
		}
	}

	@Override
	public Image getImage() {
		return SWTImagesFactory.get(SWTImagesFactory.IMG_CONTAINER_VOLUME);
	}

	@Override
	public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
	}

	@Override
	public void initializeFrom(ILaunchConfiguration configuration) {
		if (model == null)
			return;
		final List<DataVolumeModel> volumes = new ArrayList<>();
		try {
			final List<String> volumesList = configuration.getAttribute(
					IRunDockerImageLaunchConfigurationConstants.DATA_VOLUMES,
					new ArrayList<String>());
			final Set<DataVolumeModel> selectedVolumes = new HashSet<>();
			for (String volume : volumesList) {
				DataVolumeModel volumeModel = DataVolumeModel
						.parseString(volume);
				volumes.add(volumeModel);
				if (volumeModel.getSelected()) {
					selectedVolumes.add(volumeModel);
				}
			}
			model.setDataVolumes(volumes);
			model.setSelectedDataVolumes(selectedVolumes);
		} catch (CoreException e) {
			Activator.logErrorMessage(
					LaunchMessages.getString(
							"RunDockerImageLaunchConfiguration.load.failure"), //$NON-NLS-1$
					e);
		}
		// update the underlying launch config working copy on model
		// changes.
		model.addPropertyChangeListener(
				new LaunchConfigurationChangeListener());
	}

	@Override
	public void performApply(ILaunchConfigurationWorkingCopy configuration) {
		if (model == null)
			return;
		WritableList<DataVolumeModel> volumes = model.getDataVolumes();
		Set<DataVolumeModel> selectedVolumes = model.getSelectedDataVolumes();

		ArrayList<String> binds = new ArrayList<>();
		ArrayList<String> volumesFrom = new ArrayList<>();
		ArrayList<String> volumesList = new ArrayList<>();
		Set<String> selectedVolumesSet = new TreeSet<>();

		for (DataVolumeModel volume : volumes) {
			StringBuilder buffer = new StringBuilder();
			volumesList.add(volume.toString());
			switch (volume.getMountType()) {
			case HOST_FILE_SYSTEM:
				buffer.append(volume.getHostPathMount() + "," + volume.getHostPathMount() + "," + volume.isReadOnly());
				if (selectedVolumes.contains(volume)) {
					selectedVolumesSet.add(volume.toString());
					String bind = LaunchConfigurationUtils.convertToUnixPath(volume.getHostPathMount()) + ':'
							+ volume.getContainerPath() + ':' + 'Z';
					if (volume.isReadOnly()) {
						bind += ",ro"; //$NON-NLS-1$
			}
					binds.add(bind);
				}
				break;
			case CONTAINER:
				if (selectedVolumes.contains(volume)) {
					selectedVolumesSet.add(volume.toString());
					volumesFrom.add(volume.getContainerMount());
		}
				break;
			default:
				break;
			}
		}
		configuration.setAttribute(
				IRunDockerImageLaunchConfigurationConstants.BINDS, binds);
		configuration.setAttribute(
				IRunDockerImageLaunchConfigurationConstants.VOLUMES_FROM,
				volumesFrom);
		configuration.setAttribute(
				IRunDockerImageLaunchConfigurationConstants.DATA_VOLUMES,
				volumesList);
	}

	@Override
	public String getName() {
		return LaunchMessages.getString(TAB_NAME);
	}

	private class LaunchConfigurationChangeListener
			implements PropertyChangeListener {

		@Override
		public void propertyChange(final PropertyChangeEvent evt) {
			updateLaunchConfigurationDialog();
		}
	}

}
