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

import static org.eclipse.linuxtools.docker.core.EnumDockerConnectionSettings.UNIX_SOCKET;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.typed.BeanProperties;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.validation.MultiValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
import org.eclipse.jface.databinding.wizard.WizardPageSupport;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.linuxtools.docker.core.DockerConnectionManager;
import org.eclipse.linuxtools.docker.core.DockerException;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
import org.eclipse.linuxtools.docker.ui.Activator;
import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
import org.eclipse.linuxtools.internal.docker.core.TCPConnectionSettings;
import org.eclipse.linuxtools.internal.docker.core.UnixSocketConnectionSettings;
import org.eclipse.linuxtools.internal.docker.ui.SWTImagesFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
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.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;

import org.mandas.docker.client.exceptions.DockerCertificateException;

/**
 * {@link WizardPage} to edit an {@link IDockerConnection}
 *
 */
public class EditDockerConnectionPage extends WizardPage {

	private final DataBindingContext dbc;

	private final EditDockerConnectionPageModel model;

	/**
	 * Constructor.
	 * 
	 * @param currentConnection
	 *            the {@link IDockerConnection} to edit
	 */
	public EditDockerConnectionPage(final IDockerConnection currentConnection) {
		super("DockerConnectionPage", //$NON-NLS-1$
				WizardMessages.getString("DockerConnectionPage.title"), //$NON-NLS-1$
				SWTImagesFactory.DESC_BANNER_REPOSITORY);
		setMessage(WizardMessages.getString("DockerConnectionPage.msg")); //$NON-NLS-1$
		this.model = new EditDockerConnectionPageModel(currentConnection);
		this.dbc = new DataBindingContext();
	}

	@Override
	public void createControl(final Composite parent) {
		final ScrolledComposite scrollTop = new ScrolledComposite(parent,
				SWT.H_SCROLL | SWT.V_SCROLL);
		scrollTop.setExpandVertical(true);
		scrollTop.setExpandHorizontal(true);

		final Composite container = new Composite(scrollTop, SWT.NONE);
		GridLayoutFactory.fillDefaults().numColumns(1).applyTo(container);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
				.applyTo(container);
		createConnectionSettingsContainer(container);
		// attach the Databinding context status to this wizard page.
		WizardPageSupport.create(this, this.dbc);

		scrollTop.setContent(container);
		Point point = container.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		scrollTop.setSize(point);
		scrollTop.setMinSize(point);
		setControl(container);
	}

	@Override
	public void dispose() {
		if (dbc != null) {
			dbc.dispose();
		}
		super.dispose();
	}

	/**
	 * Creates the connection settings container, where the user can choose how
	 * to connect to the docker daemon (using sockets or TCP with SSL - or not)
	 * 
	 * @param parent
	 *            the parent container (ie, the main container in the preference
	 *            page)
	 */
	private void createConnectionSettingsContainer(final Composite parent) {
		final int COLUMNS = 3;
		final int INDENT = 20;
		final Composite container = new Composite(parent, SWT.NONE);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(1, 1)
				.grab(true, false).applyTo(container);
		GridLayoutFactory.fillDefaults().numColumns(COLUMNS).margins(6, 6)
				.spacing(10, 2).applyTo(container);

		// Connection name
		final Label connectionNameLabel = new Label(container, SWT.NONE);
		connectionNameLabel.setText(
				WizardMessages.getString("DockerConnectionPage.nameLabel")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.applyTo(connectionNameLabel);
		final Text connectionNameText = new Text(container, SWT.BORDER);
		connectionNameText.setToolTipText(
				WizardMessages.getString("DockerConnectionPage.nameTooltip")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(true, false).span(2, 1).applyTo(connectionNameText);

		// Unix socket
		final Button unixSocketBindingModeButton = new Button(container,
				SWT.RADIO);
		unixSocketBindingModeButton.setText(
				WizardMessages.getString("DockerConnectionPage.unixSocket")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).indent(0, 10)
				.span(COLUMNS, 1).applyTo(unixSocketBindingModeButton);

		final Label unixSocketPathLabel = new Label(container, SWT.NONE);
		unixSocketPathLabel.setText(
				WizardMessages.getString("DockerConnectionPage.location")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.indent(INDENT, 0).applyTo(unixSocketPathLabel);
		final Text unixSocketPathText = new Text(container, SWT.BORDER);
		unixSocketPathText.setToolTipText(WizardMessages
				.getString("DockerConnectionPage.unixPathTooltip")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(true, false).applyTo(unixSocketPathText);

		final Button unixSocketPathBrowseButton = new Button(container,
				SWT.BUTTON1);
		unixSocketPathBrowseButton.setText(
				WizardMessages.getString("DockerConnectionPage.browseButton")); //$NON-NLS-1$
		unixSocketPathBrowseButton
				.addSelectionListener(onBrowseUnixSocketPath());
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.applyTo(unixSocketPathBrowseButton);

		// TCP connection
		final Button tcpConnectionBindingModeButton = new Button(container,
				SWT.RADIO);
		tcpConnectionBindingModeButton.setText(
				WizardMessages.getString("DockerConnectionPage.tcpConnection")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.span(COLUMNS, 1).applyTo(tcpConnectionBindingModeButton);

		final Label tcpHostLabel = new Label(container, SWT.NONE);
		tcpHostLabel.setText(
				WizardMessages.getString("DockerConnectionPage.hostLabel")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.indent(INDENT, 0).applyTo(tcpHostLabel);

		final Text tcpHostText = new Text(container, SWT.BORDER);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(2, 1)
				.grab(true, false).applyTo(tcpHostText);

		final Button tcpAuthButton = new Button(container, SWT.CHECK);
		tcpAuthButton.setText(
				WizardMessages.getString("DockerConnectionPage.tcpAuthButton")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.indent(INDENT, 0).span(3, 1).applyTo(tcpAuthButton);

		final Label tcpCertPathLabel = new Label(container, SWT.NONE);
		tcpCertPathLabel.setText(
				WizardMessages.getString("DockerConnectionPage.tcpPathLabel")); //$NON-NLS-1$
		tcpCertPathLabel.setToolTipText(WizardMessages
				.getString("DockerConnectionPage.tcpPathTooltip")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.indent(INDENT * 2, 0).applyTo(tcpCertPathLabel);
		final Text tcpCertPathText = new Text(container, SWT.BORDER);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.grab(true, false).applyTo(tcpCertPathText);
		final Button tcpCertPathBrowseButton = new Button(container,
				SWT.BUTTON1);
		tcpCertPathBrowseButton.setText(
				WizardMessages.getString("DockerConnectionPage.browseButton")); //$NON-NLS-1$
		tcpCertPathBrowseButton.addSelectionListener(onBrowseTcpCertPath());
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
				.applyTo(tcpCertPathBrowseButton);

		// the 'test connection' button
		final Button testConnectionButton = new Button(container, SWT.NONE);
		testConnectionButton.setText(WizardMessages
				.getString("DockerConnectionPage.testConnection")); //$NON-NLS-1$
		GridDataFactory.fillDefaults().span(3, 1)
				.indent(0, 10).align(SWT.END, SWT.CENTER)
				.applyTo(testConnectionButton);
		testConnectionButton
				.addSelectionListener(onTestConnectionButtonSelection());

		// observe
		final IObservableValue<String> connectionNameModelObservable = BeanProperties
				.value(EditDockerConnectionPageModel.class,
						EditDockerConnectionPageModel.CONNECTION_NAME, String.class)
				.observe(model);
		final IObservableValue<Boolean> unixSocketBindingModeModelObservable = BeanProperties
				.value(EditDockerConnectionPageModel.class,
						EditDockerConnectionPageModel.UNIX_SOCKET_BINDING_MODE, Boolean.class)
				.observe(model);
		final IObservableValue<String> unixSocketPathModelObservable = BeanProperties
				.value(EditDockerConnectionPageModel.class,
						EditDockerConnectionPageModel.UNIX_SOCKET_PATH, String.class)
				.observe(model);

		final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable = BeanProperties
				.value(EditDockerConnectionPageModel.class,
						EditDockerConnectionPageModel.TCP_CONNECTION_BINDING_MODE, Boolean.class)
				.observe(model);
		final IObservableValue<String> tcpCertPathModelObservable = BeanProperties
				.value(EditDockerConnectionPageModel.class,
						EditDockerConnectionPageModel.TCP_CERT_PATH, String.class)
				.observe(model);
		final IObservableValue<Boolean> tcpTlsVerifyModelObservable = BeanProperties
				.value(EditDockerConnectionPageModel.class,
						EditDockerConnectionPageModel.TCP_TLS_VERIFY, Boolean.class)
				.observe(model);
		final IObservableValue<String> tcpHostModelObservable = BeanProperties
				.value(EditDockerConnectionPageModel.class,
						EditDockerConnectionPageModel.TCP_HOST, String.class)
				.observe(model);

		// group controls to easily enable/disable them
		final Control[] unixSocketControls = new Control[] { unixSocketPathText,
				unixSocketPathLabel, unixSocketPathBrowseButton };
		final Control[] tcpConnectionControls = new Control[] { tcpHostText,
				tcpHostLabel, tcpAuthButton };
		final Control[] tcpAuthControls = new Control[] { tcpCertPathText,
				tcpCertPathLabel, tcpCertPathBrowseButton };
		unixSocketBindingModeModelObservable.addChangeListener(
				onUnixSocketBindingSelection(unixSocketControls));
		tcpConnectionBindingModeModelObservable.addChangeListener(
				onTcpConnectionBindingSelection(tcpConnectionControls,
						tcpAuthControls));
		tcpTlsVerifyModelObservable
				.addValueChangeListener(onTcpAuthSelection(tcpAuthControls));

		// bind controls to model
		dbc.bindValue(
				WidgetProperties.text(SWT.Modify).observe(connectionNameText),
				connectionNameModelObservable);
		dbc.bindValue(
				WidgetProperties.widgetSelection()
						.observe(unixSocketBindingModeButton),
				unixSocketBindingModeModelObservable);
		dbc.bindValue(
				WidgetProperties.text(SWT.Modify).observe(unixSocketPathText),
				unixSocketPathModelObservable);
		dbc.bindValue(
				WidgetProperties.widgetSelection()
						.observe(tcpConnectionBindingModeButton),
				tcpConnectionBindingModeModelObservable);
		dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(tcpHostText),
				tcpHostModelObservable);
		dbc.bindValue(WidgetProperties.widgetSelection().observe(tcpAuthButton),
				tcpTlsVerifyModelObservable);
		dbc.bindValue(
				WidgetProperties.text(SWT.Modify).observe(tcpCertPathText),
				tcpCertPathModelObservable);
		// validations will be performed when the user changes the value
		// only, not at the dialog opening
		dbc.addValidationStatusProvider(
				new ConnectionNameValidator(connectionNameModelObservable,
						this.model.getInitialConnectionName()));
		dbc.addValidationStatusProvider(
				new UnixSocketValidator(unixSocketBindingModeModelObservable,
						unixSocketPathModelObservable));
		dbc.addValidationStatusProvider(
				new TcpHostValidator(tcpConnectionBindingModeModelObservable,
						tcpHostModelObservable));
		dbc.addValidationStatusProvider(new TcpCertificatesValidator(
				tcpConnectionBindingModeModelObservable,
				tcpTlsVerifyModelObservable, tcpCertPathModelObservable));

		// give focus to connectionName text at first
		connectionNameText.setFocus();
		// set widgets initial state
		updateWidgetsState(unixSocketControls, tcpConnectionControls,
				tcpAuthControls);
	}

	private SelectionListener onBrowseUnixSocketPath() {
		return SelectionListener.widgetSelectedAdapter(e -> {
			final FileDialog fileDialog = new FileDialog(getShell());
			final String selectedPath = fileDialog.open();
			if (selectedPath != null) {
				model.setUnixSocketPath("unix://" + selectedPath); //$NON-NLS-1$
			}
		});
	}

	private SelectionListener onBrowseTcpCertPath() {
		return SelectionListener.widgetSelectedAdapter(e -> {
			final DirectoryDialog directoryDialog = new DirectoryDialog(
					getShell());
			directoryDialog.setFilterPath(model.getTcpCertPath());
			final String selectedPath = directoryDialog.open();
			if (selectedPath != null) {
				model.setTcpCertPath(selectedPath);
			}
		});
	}

	private void updateWidgetsState(final Control[] unixSocketControls,
			final Control[] tcpConnectionControls,
			final Control[] tcpAuthControls) {
		setWidgetsEnabled(
				model.isTcpConnectionBindingMode() && model.isTcpTLSVerify(),
				tcpAuthControls);
		setWidgetsEnabled(model.isTcpConnectionBindingMode(),
				tcpConnectionControls);
		setWidgetsEnabled(model.isUnixSocketBindingMode(), unixSocketControls);
	}

	private IChangeListener onUnixSocketBindingSelection(
			final Control[] unixSocketControls) {
		return event -> setWidgetsEnabled(model.isUnixSocketBindingMode(),
				unixSocketControls);
	}

	private IChangeListener onTcpConnectionBindingSelection(
			final Control[] tcpConnectionControls,
			final Control[] tcpAuthControls) {
		return event -> {
			setWidgetsEnabled(model.isTcpConnectionBindingMode()
					&& model.isTcpTLSVerify(), tcpAuthControls);
			// and give focus to the first given control (if applicable)
			setWidgetsEnabled(model.isTcpConnectionBindingMode(),
					tcpConnectionControls);
		};
	}

	private IValueChangeListener<Boolean> onTcpAuthSelection(
			final Control[] tcpAuthControls) {
		return event -> setWidgetsEnabled(
				model.isTcpConnectionBindingMode() && model.isTcpTLSVerify(),
				tcpAuthControls);
	}

	private void setWidgetsEnabled(final boolean enabled,
			final Control... controls) {
		for (Control control : controls) {
			control.setEnabled(enabled);
		}
		// set the focus on the fist element of the group.
		if (enabled) {
			for (Control control : controls) {
				if (control instanceof Text) {
					control.setFocus();
					break;
				}
			}
		}
	}

	/**
	 * Verifies that the given connection settings work by trying to connect to
	 * the target Docker daemon
	 * 
	 * @return
	 */
	private SelectionListener onTestConnectionButtonSelection() {
		return new SelectionAdapter() {

			@Override
			public void widgetSelected(final SelectionEvent event) {
				try {
					getWizard().getContainer().run(true, false, monitor -> {
						monitor.beginTask(WizardMessages.getString(
										"DockerConnectionPage.pingTask"), //$NON-NLS-1$
								IProgressMonitor.UNKNOWN);
						try (DockerConnection dockerConnection = getDockerConnection()) {
							dockerConnection.open(false);
							// ping succeeded
							displaySuccessDialog();
						} catch (DockerException e) {
							// only log if there's an underlying cause.
							if (e.getCause() != null) {
								displayErrorDialog(e.getCause());
							} else {
								displayErrorDialog(e);
							}
						}
					});
				} catch (InvocationTargetException | InterruptedException o_O) {
					Activator.log(o_O);
				}

			}

			private void displaySuccessDialog() {
				displayDialog(
						WizardMessages
								.getString("DockerConnectionPage.success"), //$NON-NLS-1$
						WizardMessages
								.getString("DockerConnectionPage.pingSuccess"), //$NON-NLS-1$
						SWT.ICON_INFORMATION, new String[] { WizardMessages
								.getString("DockerConnectionPage.ok") } //$NON-NLS-1$
				);
			}

			private void displayErrorDialog(final Throwable cause) {
				displayDialog(
						WizardMessages
								.getString("DockerConnectionPage.failure"), //$NON-NLS-1$
						WizardMessages
								.getFormattedString(
										"DockerConnectionPage.pingFailure", //$NON-NLS-1$
										cause.getMessage()),
						SWT.ICON_ERROR, new String[] { WizardMessages
								.getString("DockerConnectionPage.ok") } //$NON-NLS-1$
				);
			}

			private void displayDialog(final String dialogTitle,
					final String dialogMessage, final int icon,
					final String[] buttonLabels) {
				Display.getDefault()
						.syncExec(() -> new MessageDialog(
								PlatformUI.getWorkbench()
										.getActiveWorkbenchWindow().getShell(),
								dialogTitle, null, dialogMessage, icon,
								buttonLabels, 0).open());
			}

		};
	}

	/**
	 * Opens a new {@link DockerConnection} using the settings of this
	 * {@link EditDockerConnectionPage}.
	 * 
	 * @return
	 * @throws DockerCertificateException
	 */
	protected DockerConnection getDockerConnection() {
		if (model.getBindingMode() == UNIX_SOCKET) {
			return new DockerConnection.Builder()
					.name(model.getConnectionName())
					.unixSocketConnection(new UnixSocketConnectionSettings(
							model.getUnixSocketPath()));
		} else {
			return new DockerConnection.Builder()
					.name(model.getConnectionName())
					.tcpConnection(new TCPConnectionSettings(model.getTcpHost(),
							model.getTcpCertPath()));
		}
	}

	private static class ConnectionNameValidator extends MultiValidator {

		private final IObservableValue<String> connectionNameModelObservable;

		private final String initialConnectionName;

		public ConnectionNameValidator(
				final IObservableValue<String> connectionNameModelObservable,
				final String initialConnectionName) {
			this.connectionNameModelObservable = connectionNameModelObservable;
			this.initialConnectionName = initialConnectionName;
		}

		@Override
		public IObservableList<IObservable> getTargets() {
			WritableList<IObservable> targets = new WritableList<>();
			targets.add(connectionNameModelObservable);
			return targets;
		}

		@Override
		protected IStatus validate() {
			final String connectionName = this.connectionNameModelObservable
					.getValue();
			if (connectionName == null || connectionName.isEmpty()) {
				return ValidationStatus.error(WizardMessages.getString(
						"DockerConnectionPage.validation.missingConnectionName.msg")); //$NON-NLS-1$
			} else if (!connectionName.equals(this.initialConnectionName)
					&& DockerConnectionManager.getInstance()
							.findConnection(connectionName) != null) {
				return ValidationStatus.error(WizardMessages.getString(
						"DockerConnectionPage.validation.duplicateConnectionName.msg")); //$NON-NLS-1$
			}
			return ValidationStatus.ok();
		}
	}

	private static class UnixSocketValidator extends MultiValidator {

		private final IObservableValue<Boolean> unixSocketBindingModeModelObservable;
		private final IObservableValue<String> unixSocketPathModelObservable;

		public UnixSocketValidator(
				final IObservableValue<Boolean> unixSocketBindingModeModelObservable,
				final IObservableValue<String> unixSocketPathModelObservable) {
			this.unixSocketBindingModeModelObservable = unixSocketBindingModeModelObservable;
			this.unixSocketPathModelObservable = unixSocketPathModelObservable;
		}

		@Override
		public IObservableList<IObservable> getTargets() {
			WritableList<IObservable> targets = new WritableList<>();
			targets.add(unixSocketPathModelObservable);
			return targets;
		}

		@Override
		protected IStatus validate() {
			final Boolean unixSocketBindingMode = this.unixSocketBindingModeModelObservable
					.getValue();
			final String unixSocketPath = this.unixSocketPathModelObservable
					.getValue();
			if (unixSocketBindingMode) {
				if (unixSocketPath == null || unixSocketPath.isEmpty()) {
					return ValidationStatus.error(WizardMessages.getString(
							"DockerConnectionPage.validation.missingUnixSocket.msg")); //$NON-NLS-1$
				}
				try {
					final URI unixSocketURI = new URI(unixSocketPath);
					if (unixSocketURI.getScheme() != null
							&& !unixSocketURI.getScheme().equals("unix")) { //$NON-NLS-1$
						return ValidationStatus.error(WizardMessages.getString(
								"DockerConnectionPage.validation.invalidUnixSocketScheme.msg")); //$NON-NLS-1$
					}
					if (unixSocketURI.getPath() != null) {
						final File unixSocket = new File(
								unixSocketURI.getPath());
						if (!unixSocket.exists()) {
							return ValidationStatus
									.error(WizardMessages.getString(
											"DockerConnectionPage.validation.invalidUnixSocketPath.msg")); //$NON-NLS-1$
						} else if (!unixSocket.canRead()
								|| !unixSocket.canWrite()) {
							return ValidationStatus
									.error(WizardMessages.getString(
											"DockerConnectionPage.validation.unreadableUnixSocket.msg")); //$NON-NLS-1$
						}
					} else {
						return ValidationStatus.error(WizardMessages.getString(
								"DockerConnectionPage.validation.invalidUnixSocketPath.msg")); //$NON-NLS-1$

					}
				} catch (URISyntaxException e) {
					return ValidationStatus.error(WizardMessages.getString(
							"DockerConnectionPage.validation.invalidUnixSocketPath.msg")); //$NON-NLS-1$

				}
			}
			return ValidationStatus.ok();
		}

	}

	private static class TcpHostValidator extends MultiValidator {

		private final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable;
		private final IObservableValue<String> tcpHostModelObservable;

		public TcpHostValidator(
				final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable,
				final IObservableValue<String> tcpHostModelObservable) {
			this.tcpConnectionBindingModeModelObservable = tcpConnectionBindingModeModelObservable;
			this.tcpHostModelObservable = tcpHostModelObservable;
		}

		@Override
		public IObservableList<IObservable> getTargets() {
			WritableList<IObservable> targets = new WritableList<>();
			targets.add(tcpHostModelObservable);
			return targets;
		}

		@Override
		protected IStatus validate() {
			final Boolean tcpConnectionBindingMode = this.tcpConnectionBindingModeModelObservable
					.getValue();
			final String tcpHost = this.tcpHostModelObservable.getValue();
			if (tcpConnectionBindingMode) {
				if (tcpHost == null || tcpHost.isEmpty()) {
					return ValidationStatus.error(WizardMessages.getString(
							"DockerConnectionPage.validation.missingTcpConnectionURI.msg")); //$NON-NLS-1$
				}
				try {
					final URI uri = new URI(tcpHost);
					final String scheme = uri.getScheme() != null
							? uri.getScheme().toLowerCase() : null;
					final String host = uri.getHost();
					final int port = uri.getPort();
					if (scheme != null
							&& !(scheme.equals("tcp") || scheme.equals("http") //$NON-NLS-1$ //$NON-NLS-2$
									|| scheme.equals("https"))) { //$NON-NLS-1$
						return ValidationStatus.error(WizardMessages.getString(
								"DockerConnectionPage.validation.invalidTcpConnectionScheme.msg")); //$NON-NLS-1$
					} else if (host == null) {
						return ValidationStatus.error(WizardMessages.getString(
								"DockerConnectionPage.validation.invalidTcpConnectionHost.msg")); //$NON-NLS-1$

					} else if (port == -1) {
						return ValidationStatus.error(WizardMessages.getString(
								"DockerConnectionPage.validation.invalidTcpConnectionPort.msg")); //$NON-NLS-1$

					}
				} catch (URISyntaxException e) {
					// URI is not valid
					return ValidationStatus.error(WizardMessages.getString(
							"DockerConnectionPage.validation.invalidTcpConnectionURI.msg")); //$NON-NLS-1$
				}
			}
			return ValidationStatus.ok();
		}

	}

	private static class TcpCertificatesValidator extends MultiValidator {

		private final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable;
		private final IObservableValue<Boolean> tcpTlsVerifyModelObservable;
		private final IObservableValue<String> tcpCertPathModelObservable;

		public TcpCertificatesValidator(
				final IObservableValue<Boolean> tcpConnectionBindingModeModelObservable,
				final IObservableValue<Boolean> tcpTlsVerifyModelObservable,
				final IObservableValue<String> tcpCertPathModelObservable) {
			this.tcpConnectionBindingModeModelObservable = tcpConnectionBindingModeModelObservable;
			this.tcpTlsVerifyModelObservable = tcpTlsVerifyModelObservable;
			this.tcpCertPathModelObservable = tcpCertPathModelObservable;
		}

		@Override
		public IObservableList<IObservable> getTargets() {
			WritableList<IObservable> targets = new WritableList<>();
			targets.add(tcpCertPathModelObservable);
			return targets;
		}

		@Override
		protected IStatus validate() {
			final Boolean tcpConnectionBindingMode = this.tcpConnectionBindingModeModelObservable
					.getValue();
			final Boolean tcpTlsVerify = this.tcpTlsVerifyModelObservable
					.getValue();
			final String tcpCertPath = this.tcpCertPathModelObservable
					.getValue();
			if (tcpConnectionBindingMode && tcpTlsVerify) {
				if (tcpCertPath == null || tcpCertPath.isEmpty()) {
					return ValidationStatus.error(WizardMessages.getString(
							"DockerConnectionPage.validation.missingTcpCertPath.msg")); //$NON-NLS-1$
				}
				final File tcpCert = new File(tcpCertPath);
				if (!tcpCert.exists()) {
					return ValidationStatus.error(WizardMessages.getString(
							"DockerConnectionPage.validation.invalidTcpCertPath.msg")); //$NON-NLS-1$
				} else if (!tcpCert.canRead() || !tcpCert.canRead()) {
					return ValidationStatus.error(WizardMessages.getString(
							"DockerConnectionPage.validation.unreadableTcpCertPath.msg")); //$NON-NLS-1$
				}
			}
			return ValidationStatus.ok();
		}

	}

}
