/*******************************************************************************
 * Copyright (c) 2014, 2020 Red Hat.
 *
 * 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.wizards;

import static org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants.MB;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.linuxtools.docker.core.DockerException;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
import org.eclipse.linuxtools.docker.core.IDockerHostConfig;
import org.eclipse.linuxtools.docker.core.IDockerImage;
import org.eclipse.linuxtools.docker.core.IDockerPortBinding;
import org.eclipse.linuxtools.internal.docker.core.DockerContainerConfig;
import org.eclipse.linuxtools.internal.docker.core.DockerContainerConfig.Builder;
import org.eclipse.linuxtools.internal.docker.core.DockerHostConfig;
import org.eclipse.linuxtools.internal.docker.core.DockerPortBinding;
import org.eclipse.linuxtools.internal.docker.ui.launch.IRunDockerImageLaunchConfigurationConstants;
import org.eclipse.linuxtools.internal.docker.ui.launch.LaunchConfigurationUtils;
import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunResourceVolumesVariablesModel.MountType;
import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunSelectionModel.ContainerLinkModel;
import org.eclipse.linuxtools.internal.docker.ui.wizards.ImageRunSelectionModel.ExposedPortModel;

/**
 * Wizard to 'docker run' a given {@link IDockerImage}.
 */
public class ImageRun extends Wizard {

	private final ImageRunSelectionPage imageRunSelectionPage;
	private final ImageRunResourceVolumesVariablesPage imageRunResourceVolumesPage;
	private final ImageRunNetworkPage imageRunNetworkPage;

	/**
	 * Constructor when an {@link IDockerConnection} has been selected to run an
	 * {@link IDockerImage}.
	 *
	 * @param connection
	 *            the {@link IDockerConnection} pointing to a specific Docker
	 *            daemon/host.
	 * @throws DockerException
	 */
	public ImageRun(final IDockerConnection connection) throws DockerException {
		super();
		setWindowTitle(WizardMessages.getString("ImageRun.title")); //$NON-NLS-1$
		setNeedsProgressMonitor(true);
		this.imageRunSelectionPage = new ImageRunSelectionPage(connection);
		this.imageRunResourceVolumesPage = new ImageRunResourceVolumesVariablesPage(
				connection);
		this.imageRunNetworkPage = new ImageRunNetworkPage(connection);
	}

	/**
	 * Full constructor with a selected {@link IDockerImage} to run.
	 *
	 * @param selectedImage
	 *            the {@link IDockerImage} to use to fill the wizard pages
	 * @throws DockerException
	 * @throws CoreException
	 */
	public ImageRun(final IDockerImage selectedImage)
			throws DockerException, CoreException {
		setWindowTitle(WizardMessages.getString("ImageRun.title")); //$NON-NLS-1$
		setNeedsProgressMonitor(true);
		// attempt to find the last "Image Run" launch configuration for this
		// image
		final ILaunchConfiguration lastLaunchConfiguration = LaunchConfigurationUtils
				.getLaunchConfigurationByImageName(
						LaunchConfigurationUtils.getLaunchConfigType(
								IRunDockerImageLaunchConfigurationConstants.CONFIG_TYPE_ID),
						selectedImage.repoTags().get(0));
		this.imageRunSelectionPage = new ImageRunSelectionPage(selectedImage,
				lastLaunchConfiguration);
		this.imageRunResourceVolumesPage = new ImageRunResourceVolumesVariablesPage(
				selectedImage, lastLaunchConfiguration);
		this.imageRunNetworkPage = new ImageRunNetworkPage(selectedImage,
				lastLaunchConfiguration);
	}

	@Override
	public void addPages() {
		addPage(imageRunSelectionPage);
		addPage(imageRunResourceVolumesPage);
		addPage(imageRunNetworkPage);
	}

	@Override
	public IWizardPage getNextPage(final IWizardPage page) {
		if (page.equals(imageRunSelectionPage)) {
			imageRunResourceVolumesPage.getModel().setSelectedImage(
					imageRunSelectionPage.getModel().getSelectedImage());
		}
		return super.getNextPage(page);
	}

	@Override
	public boolean performFinish() {
		return true;
	}

	public String getDockerContainerName() {
		return this.imageRunSelectionPage.getModel().getContainerName();
	}

	public boolean removeWhenExits() {
		return this.imageRunSelectionPage.getModel().isRemoveWhenExits();
	}

	public IDockerHostConfig getDockerHostConfig(List<String> unusedPorts) {
		final ImageRunSelectionModel selectionModel = this.imageRunSelectionPage
				.getModel();
		final ImageRunResourceVolumesVariablesModel resourcesModel = this.imageRunResourceVolumesPage
				.getModel();
		final ImageRunNetworkModel networkModel = this.imageRunNetworkPage
				.getModel();

		final DockerHostConfig.Builder hostConfigBuilder = new DockerHostConfig.Builder();

		if (selectionModel.isPublishAllPorts()) {
			hostConfigBuilder.publishAllPorts(true);
		} else {
			final Map<String, List<IDockerPortBinding>> portBindings = new HashMap<>();
			for (ExposedPortModel exposedPort : selectionModel.getExposedPorts()) {
				// only selected Ports in the CheckboxTableViewer are exposed.
				if (!selectionModel.getSelectedPorts().contains(exposedPort)) {
					StringBuilder b = new StringBuilder();
					b.append(exposedPort.getContainerPort());
					b.append("/");
					b.append(exposedPort.getPortType());
					b.append(":"); //$NON-NLS-1$
					if (exposedPort.getHostAddress() != null) {
						b.append(exposedPort.getHostAddress());
					}
					b.append(":"); //$NON-NLS-1$
					b.append(exposedPort.getHostPort());
					unusedPorts.add(b.toString());
					continue;
				}
				final DockerPortBinding portBinding = new DockerPortBinding(exposedPort.getHostAddress(),
						exposedPort.getHostPort());
				portBindings.put(
						exposedPort.getContainerPort()
								+ "/" + exposedPort.getPortType(), //$NON-NLS-1$
						Arrays.<IDockerPortBinding> asList(portBinding));
			}
			hostConfigBuilder.portBindings(portBindings);
		}
		// container links
		final List<String> links = new ArrayList<>();
		for (ContainerLinkModel link : selectionModel.getLinks()) {
			links.add(link.getContainerName() + ':' + link.getContainerAlias());
		}
		hostConfigBuilder.links(links);

		// data volumes
		final List<String> volumesFrom = new ArrayList<>();
		final List<String> binds = new ArrayList<>();
		for (DataVolumeModel dataVolume : resourcesModel.getDataVolumes()) {
			// only data volumes selected in the CheckBoxTableViewer are
			// included.
			if (!resourcesModel.getSelectedDataVolumes().contains(dataVolume)) {
				continue;
			}

			switch (dataVolume.getMountType()) {
			case HOST_FILE_SYSTEM:
				String bind = LaunchConfigurationUtils.convertToUnixPath(dataVolume.getHostPathMount()) + ':'
						+ dataVolume.getContainerPath() + ":Z"; //$NON-NLS-1$ //$NON-NLS-2$
				if (dataVolume.isReadOnly()) {
					bind += ",ro"; //$NON-NLS-1$
				}
				binds.add(bind);
				break;
			case CONTAINER:
				volumesFrom.add(dataVolume.getContainerMount());
				break;
			default:
				break;

			}
		}
		hostConfigBuilder.binds(binds);
		hostConfigBuilder.volumesFrom(volumesFrom);
		hostConfigBuilder.privileged(selectionModel.isPrivileged());
		// if user has asked for basic security, use a readonly rootfs,
		// make /tmp and /run use tmpfs, and drop all capabilities
		if (selectionModel.isBasicSecurity()) {
			hostConfigBuilder.readonlyRootfs(true);
			Map<String, String> tmpfsValues = new HashMap<>();
			tmpfsValues.put("/run", "rw,exec"); //$NON-NLS-1$ //$NON-NLS-2$
			tmpfsValues.put("/tmp", "rw,exec"); //$NON-NLS-1$ //$NON-NLS-2$
			hostConfigBuilder.tmpfs(tmpfsValues);
			List<String> capDropList = new ArrayList<>();
			capDropList.add("all"); //$NON-NLS-1$
			hostConfigBuilder.capDrop(capDropList);
		}
		if (selectionModel.isUnconfined()) {
			List<String> seccomp = new ArrayList<>();
			seccomp.add("seccomp:unconfined"); //$NON-NLS-1$
			hostConfigBuilder.securityOpt(seccomp);
		}
		String networkMode = networkModel.getNetworkModeString();
		// if network mode is not default, set it in host config
		if (networkMode != null
				&& !networkMode.equals(ImageRunNetworkModel.DEFAULT_MODE))
			hostConfigBuilder.networkMode(networkMode);
		// memory constraints (in bytes)
		if (resourcesModel.isEnableResourceLimitations()) {
			hostConfigBuilder.memory(resourcesModel.getMemoryLimit() * MB);
			hostConfigBuilder.cpuShares(resourcesModel.getCpuShareWeight());
		}
		return hostConfigBuilder.build();
	}

	public DockerContainerConfig getDockerContainerConfig() {
		final ImageRunSelectionModel selectionModel = this.imageRunSelectionPage
				.getModel();
		final ImageRunResourceVolumesVariablesModel resourcesModel = this.imageRunResourceVolumesPage
				.getModel();

		final Builder config = new DockerContainerConfig.Builder()
				.cmd(selectionModel.getCommand())
				.entryPoint(selectionModel.getEntrypoint())
				.image(selectionModel.getSelectedImageName())
				.tty(selectionModel.isAllocatePseudoTTY())
				.openStdin(selectionModel.isInteractiveMode());
		if (resourcesModel.isEnableResourceLimitations()) {
			// memory limit must be converted from MB to bytes
			config.memory(resourcesModel.getMemoryLimit() * MB);
			config.cpuShares(resourcesModel.getCpuShareWeight());
		}
		// environment variables
		final List<String> environmentVariables = new ArrayList<>();
		for (EnvironmentVariableModel var : resourcesModel.getEnvironmentVariables()) {
			environmentVariables.add(var.getName() + "=" + var.getValue()); //$NON-NLS-1$
		}
		config.env(environmentVariables);

		// container labels
		final Map<String, String> labelVariables = new HashMap<>();
		for (LabelVariableModel var : resourcesModel.getLabelVariables()) {
			labelVariables.put(var.getName(), var.getValue()); // $NON-NLS-1$
		}
		config.labels(labelVariables);

		if (!selectionModel.isPublishAllPorts()) {
			final Set<String> exposedPorts = new HashSet<>();
			for (ExposedPortModel exposedPort : selectionModel.getExposedPorts()) {
				// only selected Ports in the CheckboxTableViewer are exposed.
				if (!selectionModel.getSelectedPorts().contains(exposedPort)) {
					continue;
				}
				exposedPorts.add(exposedPort.getContainerPort()
						+ "/" + exposedPort.getPortType()); //$NON-NLS-1$
			}
			config.exposedPorts(exposedPorts);
		}

		Map<String, String> emptyMap = new HashMap<>();
		@SuppressWarnings("rawtypes")
		Map<String, Map> volumes = new HashMap<>();
		for (DataVolumeModel dataVolume : resourcesModel.getDataVolumes()) {
			// only data volumes selected in the CheckBoxTableViewer are
			// included.
			if (!resourcesModel.getSelectedDataVolumes().contains(dataVolume)) {
				continue;
			}
			if (dataVolume.getMountType() == MountType.NONE) {
				volumes.put(dataVolume.getContainerPath(), emptyMap);
			}
		}
		if (!volumes.isEmpty()) {
			config.volumes(volumes);
		}

		return config.build();
	}


}
