/*******************************************************************************
 * Copyright (c) 2015, 2020 Red Hat Inc.
 *
 * 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 java.io.File;
import java.io.IOException;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
import org.eclipse.linuxtools.internal.docker.core.SystemUtils;
import org.eclipse.linuxtools.internal.docker.ui.testutils.MockDockerClientFactory;
import org.eclipse.linuxtools.internal.docker.ui.testutils.MockDockerConnectionFactory;
import org.eclipse.linuxtools.internal.docker.ui.testutils.MockDockerConnectionSettingsFinder;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ButtonAssertions;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.CheckBoxAssertions;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.CloseShellRule;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.CloseWelcomePageRule;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.DockerConnectionManagerUtils;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.RadioAssertion;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.SWTBotTreeItemAssertions;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.SWTUtils;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.TextAssertions;
import org.eclipse.linuxtools.internal.docker.ui.views.DockerContainersView;
import org.eclipse.linuxtools.internal.docker.ui.views.DockerExplorerView;
import org.eclipse.linuxtools.internal.docker.ui.views.DockerImagesView;
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.widgets.Display;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
import org.eclipse.swtbot.swt.finder.waits.Conditions;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarButton;
import org.eclipse.ui.PlatformUI;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.mandas.docker.client.DockerClient;

/**
 * Testing the {@link NewDockerConnection} {@link Wizard}
 */
@RunWith(SWTBotJunit4ClassRunner.class)
public class NewDockerConnectionSWTBotTest {

	private SWTWorkbenchBot bot = new SWTWorkbenchBot();
	private SWTBotToolbarButton addConnectionButton;
	private SWTBotView dockerExplorerViewBot;

	@ClassRule
	public static CloseWelcomePageRule closeWelcomePage = new CloseWelcomePageRule(
			CloseWelcomePageRule.DOCKER_PERSPECTIVE_ID);

	@Rule
	public CloseShellRule closeShell = new CloseShellRule(IDialogConstants.CANCEL_LABEL);

	@Before
	public void lookupDockerExplorerView() throws Exception {
		SWTUtils.asyncExec(() -> {
			try {
				PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
						.showView(DockerExplorerView.VIEW_ID);
			} catch (Exception e) {
				e.printStackTrace();
				Assert.fail("Failed to open Docker Explorer view: " + e.getMessage());
			}
		});
		dockerExplorerViewBot = bot.viewById(DockerExplorerView.VIEW_ID);
		dockerExplorerViewBot.show();
		bot.views().stream().filter(v -> v.getReference().getId().equals(DockerContainersView.VIEW_ID)
				|| v.getReference().getId().equals(DockerImagesView.VIEW_ID)).forEach(v -> v.close());
		dockerExplorerViewBot.setFocus();
		this.addConnectionButton = dockerExplorerViewBot.toolbarButton("&Add Connection");
	}

	@Before
	public void clearClipboards() {
		// Clear all clipboards
		Display.getDefault().syncExec(() -> {
			Clipboard clip = new Clipboard(Display.getCurrent());
			clip.clearContents(DND.CLIPBOARD);
			clip.clearContents(DND.SELECTION_CLIPBOARD);
		});
	}

	private IDockerConnection configureUnixSocketConnection(final String connectionName, final String pathToSocket) {
		final DockerClient client = MockDockerClientFactory.build();
		final DockerConnection dockerConnection = MockDockerConnectionFactory.from(connectionName, client)
				.withUnixSocketConnectionSettings(pathToSocket);
		DockerConnectionManagerUtils.configureConnectionManager(dockerConnection);
		return dockerConnection;
	}

	private IDockerConnection configureTCPConnection(final String connectionName, final String host) {
		final DockerClient client = MockDockerClientFactory.build();
		final DockerConnection dockerConnection = MockDockerConnectionFactory.from(connectionName, client)
				.withTCPConnectionSettings(host, null);
		DockerConnectionManagerUtils.configureConnectionManager(dockerConnection);
		return dockerConnection;
	}

	@Test
	public void shouldShowCustomUnixSocketSettingsWhenNoConnectionAvailable() {
		// given
		MockDockerConnectionSettingsFinder.noDockerConnectionAvailable();
		// when
		addConnectionButton.click();
		// then
		// Empty Connection name
		TextAssertions.assertThat(bot.text(0)).isEnabled().isEmpty();
		// "Use custom connection settings" should be enabled and checked
		CheckBoxAssertions.assertThat(bot.checkBox(0)).isEnabled().isChecked();
		// "Unix socket" radio should be enabled and selected
		RadioAssertion.assertThat(bot.radio(0)).isEnabled().isSelected();
		// "Unix socket path" text should be enabled and empty
		TextAssertions.assertThat(bot.text(1)).isEnabled().isEmpty();
		// "TCP Connection" radio should be enabled but unselected
		RadioAssertion.assertThat(bot.radio(1)).isEnabled().isNotSelected();
		// "URI" should be disabled but empty
		TextAssertions.assertThat(bot.text(2)).isNotEnabled().isEmpty();
		// "Enable Auth" checkbox should be unselected and disabled
		CheckBoxAssertions.assertThat(bot.checkBox(1)).isNotEnabled().isNotChecked();
		// "Path" for certs should be disabled and empty
		TextAssertions.assertThat(bot.text(3)).isNotEnabled().isEmpty();
	}

	@Test
	public void shouldShowDefaultUnixSocketConnectionSettingsWithValidConnectionAvailable() {
		// given
		MockDockerConnectionSettingsFinder.validUnixSocketConnectionAvailable();
		// when
		addConnectionButton.click();
		// then
		// Connection name
		TextAssertions.assertThat(bot.text(0)).isEnabled().textEquals("mock");
		// "Use custom connection settings" should be enabled but unchecked
		CheckBoxAssertions.assertThat(bot.checkBox(0)).isEnabled().isNotChecked();
		// "Unix socket" radio should be disabled and selected
		RadioAssertion.assertThat(bot.radio(0)).isNotEnabled().isSelected();
		// "Unix socket path" text should be disabled and not empty
		TextAssertions.assertThat(bot.text(1)).isNotEnabled().textEquals("unix:///var/run/docker.sock");
		// "TCP Connection" radio should be unselected and disabled
		RadioAssertion.assertThat(bot.radio(1)).isNotEnabled().isNotSelected();
		// "URI" should be disabled and empty
		TextAssertions.assertThat(bot.text(2)).isNotEnabled().isEmpty();
		// "Enable Auth" checkbox should be unselected and disabled
		CheckBoxAssertions.assertThat(bot.checkBox(1)).isNotEnabled().isNotChecked();
		// "Path" for certs should be disabled but not empty
		TextAssertions.assertThat(bot.text(3)).isNotEnabled().isEmpty();
	}

	@Test
	public void shouldShowDefaultTCPSettingsWithValidConnectionAvailable() {
		// given
		MockDockerConnectionSettingsFinder.validTCPConnectionAvailable();
		// when
		addConnectionButton.click();
		bot.waitUntil(Conditions.shellIsActive(WizardMessages.getString("NewDockerConnection.title"))); //$NON-NLS-1$
		// then
		// Connection name
		TextAssertions.assertThat(bot.text(0)).isEnabled().textEquals("mock");
		// "Use custom connection settings" should be enabled but unchecked
		CheckBoxAssertions.assertThat(bot.checkBox(0)).isEnabled().isNotChecked();
		// "Unix socket" radio should be disabled and unselected
		RadioAssertion.assertThat(bot.radio(0)).isNotEnabled().isNotSelected();
		// "Unix socket path" text should be disabled and not empty
		TextAssertions.assertThat(bot.text(1)).isNotEnabled().isEmpty();
		// "TCP Connection" radio should be selected but diabled
		RadioAssertion.assertThat(bot.radio(1)).isNotEnabled().isSelected();
		// "URI" should be disabled but not empty
		TextAssertions.assertThat(bot.text(2)).isNotEnabled().textEquals("https://1.2.3.4:1234");
		// "Enable Auth" checkbox should be selected but disabled
		CheckBoxAssertions.assertThat(bot.checkBox(1)).isNotEnabled().isChecked();
		// "Path" for certs should be disabled but not empty
		TextAssertions.assertThat(bot.text(3)).isNotEnabled().textEquals("/path/to/certs");
	}

	@Test
	public void shouldAddConnectionToDockerExplorerView() throws IOException {
		// given
		final File dockerSocketTmpFile = File.createTempFile("docker", ".sock");
		MockDockerConnectionSettingsFinder.validUnixSocketConnectionAvailable("Mock",
				"unix://" + dockerSocketTmpFile.getAbsolutePath());
		// when open wizard
		addConnectionButton.click();
		bot.waitUntil(Conditions.shellIsActive(WizardMessages.getString("NewDockerConnection.title"))); //$NON-NLS-1$
		// when click on "OK"
		bot.button("Finish").click();
		// then the Docker Explorer view should have a connection named "Mock"
		SWTBotTreeItemAssertions.assertThat(SWTUtils.getTreeItem(dockerExplorerViewBot.bot().tree(), "Mock"))
				.isNotNull();
	}

	@Test
	public void shouldNotAllowNewConnectionWithDifferentNameAndSameUnixSocketSettings() throws IOException {
		// given
		final String dockerSocketTmpPath = File.createTempFile("docker", ".sock").getAbsolutePath();
		MockDockerConnectionSettingsFinder.validUnixSocketConnectionAvailable("Mock", "unix://" + dockerSocketTmpPath);
		// add an existing connection based on the settings above
		configureUnixSocketConnection("Mock", dockerSocketTmpPath);
		// when open wizard
		addConnectionButton.click();
		bot.waitUntil(Conditions.shellIsActive(WizardMessages.getString("NewDockerConnection.title"))); //$NON-NLS-1$
		// when changing connection name
		bot.text(0).setText("foo");
		// then the wizard should not allow for completion because a connection
		// with the connection settings already exists.
		ButtonAssertions.assertThat(bot.button("Finish")).isNotEnabled();
	}

	@Test
	public void shouldNotAllowNewConnectionWithDifferentNameAndSameTCPSettings() {
		// given
		MockDockerConnectionSettingsFinder.validTCPConnectionAvailable("Mock", "https://foo:1234", null);
		// add an existing connection based on the settings above
		configureTCPConnection("Mock", "https://foo:1234");
		// when open wizard
		addConnectionButton.click();
		bot.waitUntil(Conditions.shellIsActive(WizardMessages.getString("NewDockerConnection.title"))); //$NON-NLS-1$
		// when changing connection name
		bot.text(0).setText("foo");
		// then the wizard should not allow for completion because a connection
		// with the connection settings already exists.
		ButtonAssertions.assertThat(bot.button("Finish")).isNotEnabled();
	}

	@Test
	public void shouldAllowNewConnectionWithDifferentNameAndUnixSettings() throws IOException {
		// given
		final String dockerSocketTmpPath = File.createTempFile("docker", ".sock").getAbsolutePath();
		configureUnixSocketConnection("Bar", dockerSocketTmpPath);
		MockDockerConnectionSettingsFinder.validUnixSocketConnectionAvailable("Mock", "unix://" + dockerSocketTmpPath);
		final String otherDockerSocketTmpPath = File.createTempFile("docker", ".sock").getAbsolutePath();
		// when open wizard
		addConnectionButton.click();
		bot.waitUntil(Conditions.shellIsActive(WizardMessages.getString("NewDockerConnection.title"))); //$NON-NLS-1$
		// when changing connection name
		bot.text(0).setText("foo");
		bot.checkBox(0).select();
		bot.text(1).setText(otherDockerSocketTmpPath);
		// then the wizard should not allow for completion because a connection
		// with the connection settings already exists.
		ButtonAssertions.assertThat(bot.button("Finish")).isEnabled();
	}

	@Test
	public void shouldAllowNewConnectionWithDifferentNameAndTCPSettings() {
		// given
		MockDockerConnectionSettingsFinder.validTCPConnectionAvailable("Mock", "https://foo:1234", null);
		// add an existing connection based on the settings above
		configureTCPConnection("Mock", "https://foo");
		// when open wizard
		addConnectionButton.click();
		bot.waitUntil(Conditions.shellIsActive(WizardMessages.getString("NewDockerConnection.title"))); //$NON-NLS-1$
		// when changing connection name
		bot.text(0).setText("foo");
		bot.checkBox(0).select();
		bot.text(2).setText("https://bar:1234");
		// then the wizard should not allow for completion because a connection
		// with the connection settings already exists.
		ButtonAssertions.assertThat(bot.button("Finish")).isEnabled();
	}

	@Test
	public void shouldPopulateConnectionWithClipboard() {
		verifyPopulateConnectionWithClipboard(DND.CLIPBOARD);

	}

	@Test
	public void shouldPopulateConnectionWithSelectionClipboard() {
		// SELECTION_CLIPBOARD does not seem to be supported on platforms other
		// than Linux (GTK/Motif)
		Assume.assumeTrue("This test only runs on Linux", SystemUtils.isLinux());
		verifyPopulateConnectionWithClipboard(DND.SELECTION_CLIPBOARD);
	}

	private void verifyPopulateConnectionWithClipboard(final int clipboardType) {
		// given
		final String[] connectionData = new String[] {
				"DOCKER_HOST=https://1.2.3.4:1234 DOCKER_CERT_PATH=/path/to/certs DOCKER_TLS_VERIFY=1" };
		Display.getDefault().syncExec(() -> {
			Clipboard clip = new Clipboard(Display.getCurrent());
			clip.setContents(connectionData, new Transfer[] { TextTransfer.getInstance() }, clipboardType);
		});
		// when
		addConnectionButton.click();
		// then
		// Connection name
		TextAssertions.assertThat(bot.text(0)).isEnabled().isEmpty();
		// "Use custom connection settings" should be enabled and checked
		CheckBoxAssertions.assertThat(bot.checkBox(0)).isEnabled().isChecked();
		// "Unix socket" radio should be enabled and unselected
		RadioAssertion.assertThat(bot.radio(0)).isEnabled().isNotSelected();
		// "Unix socket path" text should be disabled and empty
		TextAssertions.assertThat(bot.text(1)).isNotEnabled().isEmpty();
		// "TCP Connection" radio should be enabled and selected
		RadioAssertion.assertThat(bot.radio(1)).isEnabled().isSelected();
		// "URI" should be enabled and not empty
		TextAssertions.assertThat(bot.text(2)).isEnabled().textEquals("https://1.2.3.4:1234");
		// "Enable Auth" checkbox should be enabled and selected
		CheckBoxAssertions.assertThat(bot.checkBox(1)).isEnabled().isChecked();
		// "Path" for certs should be enabled and not empty
		TextAssertions.assertThat(bot.text(3)).isEnabled().textEquals("/path/to/certs");

		// Close wizard
		bot.button("Cancel").click();
	}

}
