/*******************************************************************************
 * Copyright (c) 2016, 2017 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 accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Mickael Istria, Sopot Cela (Red Hat Inc.)
 *******************************************************************************/
package org.eclipse.ui.genericeditor.tests;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Collections;
import java.util.Map;

import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import org.eclipse.core.runtime.Platform;

import org.eclipse.core.resources.IMarker;

import org.eclipse.text.tests.Accessor;

import org.eclipse.jface.text.AbstractInformationControl;
import org.eclipse.jface.text.AbstractInformationControlManager;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.tests.util.DisplayHelper;

import org.eclipse.ui.genericeditor.tests.contributions.AlrightyHoverProvider;
import org.eclipse.ui.genericeditor.tests.contributions.EnabledPropertyTester;
import org.eclipse.ui.genericeditor.tests.contributions.HelloHoverProvider;
import org.eclipse.ui.genericeditor.tests.contributions.MarkerResolutionGenerator;
import org.eclipse.ui.genericeditor.tests.contributions.WorldHoverProvider;

import org.eclipse.ui.workbench.texteditor.tests.ScreenshotTest;

import org.eclipse.ui.texteditor.AbstractTextEditor;

/**
 * @since 1.0
 */
public class HoverTest extends AbstratGenericEditorTest {

	@Rule
	public TestName testName= new TestName();

	@Before
	public void skipOnNonLinux() {
		Assume.assumeFalse("This test currently always fail on Windows (bug 505842), skipping", Platform.OS_WIN32.equals(Platform.getOS()));
		Assume.assumeFalse("This test currently always fail on macOS (bug 505842), skipping", Platform.OS_MACOSX.equals(Platform.getOS()));
	}

	@Test
	public void testSingleHover() throws Exception {
		Shell shell= getHoverShell(triggerCompletionAndRetrieveInformationControlManager(), true);
		assertNotNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL));
		assertNull(findControl(shell, StyledText.class, HelloHoverProvider.LABEL));
		assertNull(findControl(shell, StyledText.class, WorldHoverProvider.LABEL));
	}

	@Test
	public void testEnabledWhenHover() throws Exception {
		cleanFileAndEditor();
		EnabledPropertyTester.setEnabled(true);
		createAndOpenFile("enabledWhen.txt", "bar 'bar'");
		Shell shell= getHoverShell(triggerCompletionAndRetrieveInformationControlManager(), true);
		assertNotNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL));
		assertNull(findControl(shell, StyledText.class, WorldHoverProvider.LABEL));

		cleanFileAndEditor();
		EnabledPropertyTester.setEnabled(false);
		createAndOpenFile("enabledWhen.txt", "bar 'bar'");
		shell= getHoverShell(triggerCompletionAndRetrieveInformationControlManager(), true);
		assertNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL));
		assertNotNull(findControl(shell, StyledText.class, WorldHoverProvider.LABEL));
	}

	/**
	 * @throws Exception ex
	 * @since 1.1
	 */
	@Test
	public void testMultipleHover() throws Exception {
		cleanFileAndEditor();
		createAndOpenFile("bar.txt", "Hi");
		Shell shell= getHoverShell(triggerCompletionAndRetrieveInformationControlManager(), true);
		assertNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL));
		assertNotNull(findControl(shell, StyledText.class, WorldHoverProvider.LABEL));
		assertNotNull(findControl(shell, StyledText.class, HelloHoverProvider.LABEL));
	}

	@Test
	public void testProblemHover() throws Exception {
		String problemMessage= "Huston...";
		IMarker marker= null;
		try {
			marker= this.file.createMarker(IMarker.PROBLEM);
			marker.setAttribute(IMarker.LINE_NUMBER, 1);
			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
			marker.setAttribute(IMarker.CHAR_START, 0);
			marker.setAttribute(IMarker.CHAR_END, 5);
			marker.setAttribute(IMarker.MESSAGE, problemMessage);
			marker.setAttribute(MarkerResolutionGenerator.FIXME, true);
			AbstractInformationControlManager manager= triggerCompletionAndRetrieveInformationControlManager();
			Object hoverData= getHoverData(manager);
			assertTrue(hoverData instanceof Map);
			assertTrue(((Map<?, ?>) hoverData).containsValue(Collections.singletonList(marker)));
			assertTrue(((Map<?, ?>) hoverData).containsValue(AlrightyHoverProvider.LABEL));
			assertFalse(((Map<?, ?>) hoverData).containsValue(HelloHoverProvider.LABEL));
			// check dialog content
			Shell shell= getHoverShell(manager, true);
			assertNotNull(findControl(shell, Label.class, marker.getAttribute(IMarker.MESSAGE, "NONE")));
			assertNotNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL));
			assertNull(findControl(shell, StyledText.class, HelloHoverProvider.LABEL));
			// check quick-fix works
			Link link= findControl(shell, Link.class, MarkerResolutionGenerator.FIXME);
			assertNotNull(link);
			Event event= new Event();
			event.widget= link;
			event.display= link.getDisplay();
			event.doit= true;
			event.type= SWT.Selection;
			link.notifyListeners(SWT.Selection, event);
			final IMarker m= marker;
			new DisplayHelper() {
				@Override
				protected boolean condition() {
					return !m.exists();
				}
			}.waitForCondition(event.display, 1000);
			assertFalse(marker.exists());
		} finally {
			if (marker != null && marker.exists()) {
				marker.delete();
			}
		}
	}

	private Shell getHoverShell(AbstractInformationControlManager manager, boolean failOnError) {
		AbstractInformationControl[] control= { null };
		new DisplayHelper() {
			@Override
			protected boolean condition() {
				control[0]= (AbstractInformationControl) new Accessor(manager, AbstractInformationControlManager.class).get("fInformationControl");
				return control[0] != null;
			}
		}.waitForCondition(this.editor.getSite().getShell().getDisplay(), 5000);
		if (control[0] == null) {
			if (failOnError) {
				ScreenshotTest.takeScreenshot(getClass(), testName.getMethodName(), System.out);
				fail();
			} else {
				return null;
			}
		}
		Shell shell= (Shell) new Accessor(control[0], AbstractInformationControl.class).get("fShell");
		new DisplayHelper() {
			@Override
			protected boolean condition() {
				return shell.isVisible();
			}
		}.waitForCondition(this.editor.getSite().getShell().getDisplay(), 2000);
		if (failOnError) {
			assertTrue(shell.isVisible());
		}
		return shell;
	}

	private <T extends Control> T findControl(Control control, Class<T> controlType, String label) {
		if (control.getClass() == controlType) {
			@SuppressWarnings("unchecked")
			T res= (T) control;
			if (label == null) {
				return res;
			}
			String controlLabel= null;
			if (control instanceof Label) {
				controlLabel= ((Label) control).getText();
			} else if (control instanceof Link) {
				controlLabel= ((Link) control).getText();
			} else if (control instanceof Text) {
				controlLabel= ((Text) control).getText();
			} else if (control instanceof StyledText) {
				controlLabel= ((StyledText) control).getText();
			}
			if (controlLabel != null && controlLabel.contains(label)) {
				return res;
			}
		} else if (control instanceof Composite) {
			for (Control child : ((Composite) control).getChildren()) {
				T res= findControl(child, controlType, label);
				if (res != null) {
					return res;
				}
			}
		}
		return null;
	}

	private Object getHoverData(AbstractInformationControlManager manager) {
		Object hoverData= new Accessor(manager, AbstractInformationControlManager.class).get("fInformation");
		return hoverData;
	}

	private AbstractInformationControlManager triggerCompletionAndRetrieveInformationControlManager() {
		final int caretLocation= 2;
		this.editor.selectAndReveal(caretLocation, 0);
		final StyledText editorTextWidget= (StyledText) this.editor.getAdapter(Control.class);
		new DisplayHelper() {
			@Override
			protected boolean condition() {
				return editorTextWidget.isFocusControl() && editorTextWidget.getSelection().x == caretLocation;
			}
		}.waitForCondition(editorTextWidget.getDisplay(), 3000);
		// sending event to trigger hover computation
		editorTextWidget.getShell().forceActive();
		editorTextWidget.getShell().setActive();
		editorTextWidget.getShell().setFocus();
		editorTextWidget.getShell().getDisplay().wake();
		Event hoverEvent= new Event();
		hoverEvent.widget= editorTextWidget;
		hoverEvent.type= SWT.MouseHover;
		hoverEvent.x= editorTextWidget.getClientArea().x + 5;
		hoverEvent.y= editorTextWidget.getClientArea().y + 5;
		hoverEvent.display= editorTextWidget.getDisplay();
		hoverEvent.doit= true;
		editorTextWidget.getDisplay().setCursorLocation(editorTextWidget.toDisplay(hoverEvent.x, hoverEvent.y));
		editorTextWidget.notifyListeners(SWT.MouseHover, hoverEvent);
		ITextViewer viewer= (ITextViewer) new Accessor(editor, AbstractTextEditor.class).invoke("getSourceViewer", new Object[0]);
		AbstractInformationControlManager textHoverManager= (AbstractInformationControlManager) new Accessor(viewer, TextViewer.class).get("fTextHoverManager");
		// retrieving hover content
		new DisplayHelper() {
			@Override
			protected boolean condition() {
				return getHoverData(textHoverManager) != null;
			}
		}.waitForCondition(hoverEvent.display, 6000);
		return textHoverManager;
	}
}
