/*******************************************************************************
 * Copyright (c) 2016, 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.views;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.linuxtools.docker.core.IDockerContainer;
import org.eclipse.linuxtools.docker.core.IDockerImage;
import org.eclipse.linuxtools.docker.core.IDockerImageHierarchyNode;
import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
import org.eclipse.linuxtools.internal.docker.ui.testutils.MockContainerFactory;
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.MockImageFactory;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ClearConnectionManagerRule;
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.DockerImageHierarchyViewAssertions;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.MenuAssertion;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.SWTUtils;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.TabDescriptorAssertions;
import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.TestLoggerRule;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.views.properties.PropertySheet;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;

import org.mandas.docker.client.DockerClient;
import org.mandas.docker.client.messages.Container;
import org.mandas.docker.client.messages.Image;

/**
 * Testing the {@link DockerImageHierarchyView} call from the
 * {@link DockerExplorerView}.
 */
public class DockerImageHierarchyViewSWTBotTest {

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

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

	@Rule
	public TestLoggerRule watcher = new TestLoggerRule();

	@Rule
	public ClearConnectionManagerRule clearConnectionManager = new ClearConnectionManagerRule();
	private DockerConnection connection;

	@Before
	public void setupViews() {
		this.bot = new SWTWorkbenchBot();
		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());
			}
		});
		this.dockerExplorerViewBot = bot.viewById(DockerExplorerView.VIEW_ID);
		// make sure that the Docker Image Hierarchy view is closed
		this.bot.views().stream().filter(v -> v.getReference().getId().equals(DockerImageHierarchyView.VIEW_ID))
				.forEach(v -> v.close());
	}

	@Before
	public void setupData() {
		// data is built as follows:
		// root_image
		// |- foo_image1
		// _|- foo_container1 (Up)
		// _|- foo_image2
		// __|- foo_container21 (Exited)
		// __|- foo_container22 (Paused)
		// |- bar_image11
		// _|- bar_container1

		final Image rootImage = MockImageFactory.id("sha256:root_image").name("root_image").build();
		final Image fooImage1 = MockImageFactory.id("sha256:foo_image1").name("foo_image1")
				.parentId("sha256:root_image").build();
		final Image fooImage2 = MockImageFactory.id("sha256:foo_image2").name("foo_image2", "foo_image2_alias")
				.parentId("sha256:foo_image1").build();
		final Container fooContainer1 = MockContainerFactory.id("sha256:foo_container1").name("foo_container1")
				.imageName("foo_image1").status("Up").build();
		final Container fooContainer21 = MockContainerFactory.id("sha256:foo_container21").name("foo_container21")
				.imageName("foo_image2").status("Exited").build();
		final Container fooContainer22 = MockContainerFactory.id("sha256:foo_container22").name("foo_container22")
				.imageName("foo_image2_alias").status("Up (Paused)").build();
		final Image barImage1 = MockImageFactory.id("sha256:bar_image1").name("bar_image1")
				.parentId("sha256:root_image").build();
		final Container barContainer1 = MockContainerFactory.id("sha256:bar_container1").name("bar_container1")
				.imageName("bar_image1").build();
		final DockerClient client = MockDockerClientFactory.image(rootImage).image(fooImage1).container(fooContainer1)
				.image(fooImage2).container(fooContainer21).container(fooContainer22).image(barImage1)
				.container(barContainer1).build();
		this.connection = MockDockerConnectionFactory.from("Test", client).withDefaultTCPConnectionSettings();
		this.connection.getImages(true);
		this.connection.getContainers(true);
		DockerConnectionManagerUtils.configureConnectionManager(connection);

	}

	@After
	public void hideMenu() {
		try {
			SWTUtils.hideMenu(dockerExplorerViewBot.bot().tree());
		} catch (WidgetNotFoundException e) {
			// ignore if widget is not found, that's probably because there's no
			// tree in the
			// Docker Explorer view for the test that just ran.
		}
	}

	private List<String> getChildrenElementIds(final IDockerImageHierarchyNode fooImageHierarchy) {
		return fooImageHierarchy.getChildren().stream().map(e -> {
			if (e.getElement() instanceof IDockerImage) {
				return ((IDockerImage) e.getElement()).id();
			}
			return ((IDockerContainer) e.getElement()).id();
		}).collect(Collectors.toList());
	}

	private DockerImageHierarchyView getDockerImageHierarchyView() {
		final SWTBotView hierarchyViewBot = bot.viewById(DockerImageHierarchyView.VIEW_ID);
		return (DockerImageHierarchyView) hierarchyViewBot.getViewReference().getView(true);
	}

	private static SWTBotTreeItem selectImageInTreeView(final SWTWorkbenchBot bot, final String... path) {
		final SWTBotView dockerImageHierarchyViewBot = bot.viewById(DockerImageHierarchyView.VIEW_ID);
		final DockerImageHierarchyView dockerImageHierarchyView = (DockerImageHierarchyView) (dockerImageHierarchyViewBot
				.getViewReference().getView(true));
		SWTUtils.asyncExec(() -> dockerImageHierarchyView.getCommonViewer().expandAll());
		// when a second call to expand the container is done (because the first
		// expandAll stopped with a "Loading..." job that retrieved the
		// containers)
		return SWTUtils.getTreeItem(dockerImageHierarchyViewBot, path).select();
	}

	@Test
	public void shouldDisplayImageHierarchyViewWhenSelectingImage() {
		// given
		SWTUtils.getTreeItem(dockerExplorerViewBot, "Test", "Images", "foo_image1").select();
		// when
		dockerExplorerViewBot.bot().tree().contextMenu("Open Image Hierarchy").click(); //$NON-NLS-1$
		// then the view should be visible and selection should be on
		// foo_image1
		DockerImageHierarchyViewAssertions.assertThat(getDockerImageHierarchyView())
				.hasSelectedElement(this.connection.getImage("sha256:foo_image1"));
	}

	@Test
	public void shouldDisplayImageHierarchyViewWhenSelectingImageAlias() {
		// given
		SWTUtils.getTreeItem(dockerExplorerViewBot, "Test", "Images", "foo_image2_alias").select();
		// when
		dockerExplorerViewBot.bot().tree().contextMenu("Open Image Hierarchy").click(); //$NON-NLS-1$
		// then the view should be visible and selection should be on
		// foo_image2
		DockerImageHierarchyViewAssertions.assertThat(getDockerImageHierarchyView())
				.hasSelectedElement(this.connection.getImage("sha256:foo_image2"));

	}

	@Test
	public void shouldDisplayImageHierarchyViewWhenSelectingContainer() {
		// given
		SWTUtils.getTreeItem(dockerExplorerViewBot, "Test", "Containers", "foo_container1").select();
		// when
		dockerExplorerViewBot.bot().tree().contextMenu("Open Image Hierarchy").click(); //$NON-NLS-1$
		// then the view should be visible and selection should be on
		// foo_container1

		DockerImageHierarchyViewAssertions.assertThat(SWTUtils.getView(this.bot, DockerImageHierarchyView.VIEW_ID))
				.hasSelectedElement(this.connection.getContainer("sha256:foo_container1"));
	}

	@Test
	public void shouldRetrieveImageHierarchyFromRootImage() {
		// given
		final IDockerImage rootImage = this.connection.getImage("sha256:root_image");
		// when
		final IDockerImageHierarchyNode rootImageHierarchy = this.connection.resolveImageHierarchy(rootImage);
		// then
		assertThat(rootImageHierarchy).isNotNull();
		assertThat(rootImageHierarchy.getElement()).isEqualTo(rootImage);
		// 2 direct children: foo_image1 and bar_image1
		assertThat(rootImageHierarchy.getChildren()).hasSize(2);
		assertThat(rootImageHierarchy.getParent()).isNull();
		final IDockerImageHierarchyNode fooImage1Hierarchy = rootImageHierarchy.getChild("sha256:foo_image1");
		assertThat(fooImage1Hierarchy.getParent()).isEqualTo(rootImageHierarchy);
		// 2 direct children: foo_image2/foo_image2_alias and foo_container1
		assertThat(fooImage1Hierarchy.getChildren()).hasSize(2);
	}

	@Test
	public void shouldRetrieveImageHierarchyFromIntermediateImage() {
		// given
		final IDockerImage fooImage1 = this.connection.getImage("sha256:foo_image1");
		// when
		final IDockerImageHierarchyNode fooImage1Hierarchy = this.connection.resolveImageHierarchy(fooImage1);
		// then
		assertThat(fooImage1Hierarchy).isNotNull();
		assertThat(fooImage1Hierarchy.getElement()).isEqualTo(fooImage1);
		assertThat(getChildrenElementIds(fooImage1Hierarchy)).contains("sha256:foo_container1", "sha256:foo_image2");
		final IDockerImage rootElement = (IDockerImage) fooImage1Hierarchy.getParent().getElement();
		assertThat(rootElement.id()).isEqualTo("sha256:root_image");
		// the parent only shows this child element, not its whole descendants
		assertThat(fooImage1Hierarchy.getParent().getChildren()).containsExactly(fooImage1Hierarchy);
		final IDockerImageHierarchyNode fooImage2HierarchyNode = fooImage1Hierarchy.getChild("sha256:foo_image2");
		assertThat(fooImage2HierarchyNode).isNotNull();
		// 2 child containers: foo_container21 and foo_container22
		assertThat(fooImage2HierarchyNode.getChildren()).hasSize(2);
		final IDockerImageHierarchyNode fooContainer1HierarchyNode = fooImage1Hierarchy
				.getChild("sha256:foo_container1");
		assertThat(fooContainer1HierarchyNode).isNotNull();
		assertThat(fooContainer1HierarchyNode.getChildren()).isEmpty();
	}

	@Test
	public void shouldRetrieveImageHierarchyFromLeafImage() {
		// given
		final IDockerImage fooImage2 = this.connection.getImage("sha256:foo_image2");
		// when
		final IDockerImageHierarchyNode fooImage2Hierarchy = this.connection.resolveImageHierarchy(fooImage2);
		// then
		assertThat(fooImage2Hierarchy).isNotNull();
		assertThat(fooImage2Hierarchy.getElement()).isEqualTo(fooImage2);
		// 2 containers: foo_container21 and foo_container22
		assertThat(fooImage2Hierarchy.getChildren()).hasSize(2);
		assertThat((IDockerContainer) fooImage2Hierarchy.getChild("sha256:foo_container21").getElement()).isNotNull();
		final IDockerImageHierarchyNode fooImage1Hierarchy = fooImage2Hierarchy.getParent();
		final IDockerImage fooImage = (IDockerImage) fooImage1Hierarchy.getElement();
		assertThat(fooImage.id()).isEqualTo("sha256:foo_image1");
		// in this case, intermediate images shows a single child
		assertThat(fooImage1Hierarchy.getChildren()).hasSize(1);
		assertThat(fooImage1Hierarchy.getChildren()).containsExactly(fooImage2Hierarchy);
	}

	@Test
	public void shouldRetrieveImageHierarchyFromContainerBasedOnIntermediateImage() {
		// given
		final IDockerContainer fooContainer1 = this.connection.getContainer("sha256:foo_container1");
		// when
		final IDockerImageHierarchyNode fooContainer1Hierarchy = this.connection.resolveImageHierarchy(fooContainer1);
		// then
		assertThat(fooContainer1Hierarchy).isNotNull();
		assertThat(fooContainer1Hierarchy.getElement()).isEqualTo(fooContainer1);
		assertThat(fooContainer1Hierarchy.getChildren()).isEmpty();
		final IDockerImage fooImage1 = (IDockerImage) fooContainer1Hierarchy.getParent().getElement();
		assertThat(fooImage1.id()).isEqualTo("sha256:foo_image1");
		// parent images hierarchy only shows the selected container as its
		// child.
		assertThat(fooContainer1Hierarchy.getParent().getChildren()).containsExactly(fooContainer1Hierarchy);
	}

	@Test
	public void shouldRetrieveImageHierarchyFromContainerBasedOnLeafImage() {
		// given
		// when
		final IDockerContainer barContainer1 = this.connection.getContainer("sha256:bar_container1");
		final IDockerImageHierarchyNode barContainer1Hierarchy = this.connection.resolveImageHierarchy(barContainer1);
		// then
		assertThat(barContainer1Hierarchy).isNotNull();
		assertThat(barContainer1Hierarchy.getElement()).isEqualTo(barContainer1);
		assertThat(barContainer1Hierarchy.getChildren()).isEmpty();
		final IDockerImage barImageElement = (IDockerImage) barContainer1Hierarchy.getParent().getElement();
		assertThat(barImageElement.id()).isEqualTo("sha256:bar_image1");
	}

	@Test
	public void shouldShowSelectedImageInPropertiesView() {
		// given
		final PropertySheet propertySheet = SWTUtils
				.syncExec(() -> SWTUtils.getView(bot, "org.eclipse.ui.views.PropertySheet", true));
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root");
		// show container info in Properties view
		SWTUtils.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Show In", "Properties")
				.click();
		// the properties view should be visible and filled with images details
		assertThat(propertySheet.getCurrentPage()).isInstanceOf(TabbedPropertySheetPage.class);
		final TabbedPropertySheetPage currentPage = (TabbedPropertySheetPage) propertySheet.getCurrentPage();
		TabDescriptorAssertions.assertThat(currentPage.getSelectedTab()).isNotNull()
				.hasId("org.eclipse.linuxtools.docker.ui.properties.image.info");
	}

	@Test
	public void shouldShowSelectedContainerInPropertiesView() {
		// given
		final PropertySheet propertySheet = SWTUtils
				.syncExec(() -> SWTUtils.getView(bot, "org.eclipse.ui.views.PropertySheet", true));
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_container1");
		// show container info in Properties view
		SWTUtils.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Show In", "Properties")
				.click();
		assertThat(propertySheet.getCurrentPage()).isInstanceOf(TabbedPropertySheetPage.class);
		final TabbedPropertySheetPage currentPage = (TabbedPropertySheetPage) propertySheet.getCurrentPage();
		TabDescriptorAssertions.assertThat(currentPage.getSelectedTab()).isNotNull()
				.hasId("org.eclipse.linuxtools.docker.ui.properties.container.info");
	}

	@Test
	public void shouldProvideEnabledRemoveCommandOnSelectedImage() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Remove");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledAddTagCommandOnSelectedImage() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Add Tag");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledPushCommandOnSelectedImage() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Push...");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledStartCommandOnSelectedContainer() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_image2", "foo_container21");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Start");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledPauseCommandOnSelectedContainer() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_container1");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Pause");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledUnpauseCommandOnSelectedContainer() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_image2", "foo_container22");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Unpause");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledKillCommandOnSelectedContainer() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_container1");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Kill");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledRemoveCommandOnSelectedContainer() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_image2", "foo_container21");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Remove");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledCommitCommandOnSelectedContainer() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_image2", "foo_container21");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Commit");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledDisplayLogCommandOnSelectedContainer() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_image2", "foo_container21");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Display Log");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

	@Test
	public void shouldProvideEnabledRemoveLogCommandOnSelectedContainer() {
		// given
		shouldDisplayImageHierarchyViewWhenSelectingImage();
		// when
		selectImageInTreeView(bot, "root", "foo_image1", "foo_image2", "foo_container21");
		final SWTBotMenu menu = SWTUtils
				.getContextMenu(bot.viewById(DockerImageHierarchyView.VIEW_ID).bot().tree(), "Remove Log");
		// then
		MenuAssertion.assertThat(menu).isVisible().isEnabled();
	}

}
