/*******************************************************************************
 * Copyright (c) 2018 SAP SE and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     SAP SE - initial version
 *******************************************************************************/
package org.eclipse.urischeme.internal.registration;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.urischeme.IOperatingSystemRegistration;
import org.eclipse.urischeme.IScheme;
import org.eclipse.urischeme.ISchemeInformation;
import org.hamcrest.core.IsNot;
import org.hamcrest.core.StringContains;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class TestUnitRegistrationLinux {

	private static final String PRODUCT_NAME = "myProduct";
	private static final String USER_HOME = System.getProperty("user.home");
	private static final String ECLIPSE_LAUNCHER = "eclipse.launcher";
	private static final String ECLIPSE_HOME_LOCATION = "eclipse.home.location";
	private static final String OTHER_APP_EXECUTABLE_PATH = "/home/myuser/otherApp/app";
	private static final String OTHER_APP_DESKTOP_FILE = "otherApp.desktop";
	private static final String PATH_OTHER_APP_DESKTOP_FILE = USER_HOME + "/.local/share/applications/"
			+ OTHER_APP_DESKTOP_FILE;

	private static final String OWN_EXECUTABLE_PATH = "/home/myuser/Eclipse/Eclipse";
	private static final String OWN_DESKTOP_FILE = "_home_myuser_Eclipse_.desktop";
	private static final String PATH_OWN_DESKTOP_FILE = USER_HOME + "/.local/share/applications/" + OWN_DESKTOP_FILE;

	private static final IScheme ADT_SCHEME = new Scheme("adt", "");

	private static final ISchemeInformation OTHER_SCHEME_INFO = new SchemeInformation("other", "");
	private static final ISchemeInformation ADT_SCHEME_INFO = new SchemeInformation("adt", "");

	private static String originalEclipseHomeLocation;
	private static String originalEclipseLauncher;

	private IOperatingSystemRegistration registration;

	private FileProviderMock fileProvider;
	private ProcessSpy processStub;

	@Before
	public void setup() {
		fileProvider = new FileProviderMock();
		processStub = new ProcessSpy();

		System.setProperty(ECLIPSE_HOME_LOCATION, "file:/home/myuser/Eclipse/");
		System.setProperty(ECLIPSE_LAUNCHER, "/home/myuser/Eclipse/Eclipse");

		registration = new RegistrationLinux(fileProvider, processStub, PRODUCT_NAME);
	}

	@BeforeClass
	public static void classSetup() {
		originalEclipseHomeLocation = System.getProperty(ECLIPSE_HOME_LOCATION, "");
		originalEclipseLauncher = System.getProperty(ECLIPSE_LAUNCHER, "");
	}

	@AfterClass
	public static void classTearDown() {
		System.setProperty(ECLIPSE_HOME_LOCATION, originalEclipseHomeLocation);
		System.setProperty(ECLIPSE_LAUNCHER, originalEclipseLauncher);
	}

	@Test
	public void handlesAddOnly() throws Exception {
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE, getFileLines(OWN_EXECUTABLE_PATH, ""));

		registration.handleSchemes(Arrays.asList(ADT_SCHEME_INFO), Collections.emptyList());

		assertFilePathIs(PATH_OWN_DESKTOP_FILE);

		assertMimeTypeInFileIs("x-scheme-handler/adt;");

		assertXdgMimeCalledFor("x-scheme-handler/adt");
	}

	@Test
	public void handlesAddAndRemoveAtOnce() throws Exception {
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE,
				getFileLines(OWN_EXECUTABLE_PATH, "MimeType=x-scheme-handler/adt;"));

		registration.handleSchemes(Arrays.asList(OTHER_SCHEME_INFO), Arrays.asList(ADT_SCHEME_INFO));

		assertFilePathIs(PATH_OWN_DESKTOP_FILE);

		assertMimeTypeInFileIs("x-scheme-handler/other;");

		assertXdgMimeCalledFor("x-scheme-handler/other");
	}

	@Test
	public void handlesRemoveOnly() throws Exception {
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE,
				getFileLines(OWN_EXECUTABLE_PATH, "MimeType=x-scheme-handler/adt;"));

		registration.handleSchemes(Collections.emptyList(), Arrays.asList(ADT_SCHEME_INFO));

		assertFilePathIs(PATH_OWN_DESKTOP_FILE);

		assertNoMimeTypeInFile();

		assertTrue(processStub.records.isEmpty());
	}

	@Test
	public void createsInitialDesktopFile() throws Exception {
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE, new IOException("desktop file not existing"));

		registration.handleSchemes(Arrays.asList(ADT_SCHEME_INFO), Collections.emptyList());

		assertEquals(PATH_OWN_DESKTOP_FILE, fileProvider.writePath);

		assertAppNameInFileIs(PRODUCT_NAME);

		assertMimeTypeInFileIs("x-scheme-handler/adt;");

		assertExecInFileIs("/home/myuser/Eclipse/Eclipse %u");

		assertXdgMimeCalledFor("x-scheme-handler/adt");
	}

	@Test
	public void callsXdgMimeOnceForAllSchemes() throws Exception {
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE, getFileLines(OWN_EXECUTABLE_PATH, ""));

		registration.handleSchemes(Arrays.asList(ADT_SCHEME_INFO, OTHER_SCHEME_INFO), Collections.emptyList());

		assertFilePathIs(PATH_OWN_DESKTOP_FILE);

		assertMimeTypeInFileIs("x-scheme-handler/adt;x-scheme-handler/other;");

		assertXdgMimeCalledFor("x-scheme-handler/adt", "x-scheme-handler/other");
	}

	@Test
	public void returnsFallbackIfEclipseLauncherIsNotSet() throws Exception {
		System.clearProperty(ECLIPSE_LAUNCHER);
		String eclipseLauncher = registration.getEclipseLauncher();
		assertEquals("/home/myuser/Eclipse/" + "eclipse", eclipseLauncher);
	}

	@Test
	public void givesSchemeInfoForHandledScheme() throws Exception {
		fileProvider.fileExistsAnswers.put(PATH_OWN_DESKTOP_FILE, true);
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE,
				getFileLines(OWN_EXECUTABLE_PATH, "MimeType=x-scheme-handler/adt;"));

		processStub.result = OWN_DESKTOP_FILE; // this is returned by xdg-mime query default
		// x-scheme-handler/adt

		List<ISchemeInformation> registeredSchemes = registration.getSchemesInformation(Arrays.asList(ADT_SCHEME));

		assertEquals(1, registeredSchemes.size());
		assertEquals("adt", registeredSchemes.get(0).getName());
		assertTrue("Scheme should be handled", registeredSchemes.get(0).isHandled());
	}

	@Test
	public void givesSchemeInfoForSchemeHandledByOtherApp() throws Exception {
		fileProvider.fileExistsAnswers.put(PATH_OTHER_APP_DESKTOP_FILE, true);
		fileProvider.readAnswers.put(PATH_OTHER_APP_DESKTOP_FILE,
				getFileLines(OTHER_APP_EXECUTABLE_PATH, "MimeType=x-scheme-handler/adt;"));

		processStub.result = OTHER_APP_DESKTOP_FILE; // this is returned by xdg-mime query default
														// x-scheme-handler/adt

		List<ISchemeInformation> infos = registration.getSchemesInformation(Arrays.asList(ADT_SCHEME));

		assertEquals(1, infos.size());
		assertEquals("adt", infos.get(0).getName());
		assertFalse(infos.get(0).isHandled());
		assertEquals(OTHER_APP_EXECUTABLE_PATH, infos.get(0).getHandlerInstanceLocation());
	}

	@Test
	public void givesSchemeInfoForSchemeHandledByOtherAppAndInOwnDesktopFile() throws Exception {
		fileProvider.fileExistsAnswers.put(PATH_OWN_DESKTOP_FILE, true);
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE,
				getFileLines(OWN_EXECUTABLE_PATH, "MimeType=x-scheme-handler/adt;"));

		fileProvider.fileExistsAnswers.put(PATH_OTHER_APP_DESKTOP_FILE, true);
		fileProvider.readAnswers.put(PATH_OTHER_APP_DESKTOP_FILE,
				getFileLines(OTHER_APP_EXECUTABLE_PATH, "MimeType=x-scheme-handler/adt;"));

		processStub.result = OTHER_APP_DESKTOP_FILE; // this is returned by xdg-mime query default
		// x-scheme-handler/adt

		List<ISchemeInformation> infos = registration.getSchemesInformation(Arrays.asList(ADT_SCHEME));

		assertEquals(1, infos.size());
		assertEquals("adt", infos.get(0).getName());
		assertFalse(infos.get(0).isHandled());
		assertEquals(OTHER_APP_EXECUTABLE_PATH, infos.get(0).getHandlerInstanceLocation());
	}

	@Test
	public void givesSchemeInfoForSchemeNotHandledAnymoreButInXdgMimeForOtherApp() throws Exception {
		fileProvider.fileExistsAnswers.put(PATH_OTHER_APP_DESKTOP_FILE, true);
		fileProvider.fileExistsAnswers.put(PATH_OWN_DESKTOP_FILE, true);
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE, getFileLines(OWN_EXECUTABLE_PATH, ""));
		fileProvider.readAnswers.put(PATH_OTHER_APP_DESKTOP_FILE, getFileLines(OTHER_APP_EXECUTABLE_PATH, ""));

		processStub.result = OTHER_APP_DESKTOP_FILE;

		List<ISchemeInformation> infos = registration.getSchemesInformation(Arrays.asList(ADT_SCHEME));

		assertEquals(1, infos.size());
		assertEquals("adt", infos.get(0).getName());
		assertFalse(infos.get(0).isHandled());
		assertEquals("", infos.get(0).getHandlerInstanceLocation());
	}

	@Test
	public void givesSchemeInfoForSchemeNotHandledAnymoreDesktopFileNoLongerExistsButInXdgMimeForOtherApp()
			throws Exception {
		fileProvider.fileExistsAnswers.put(PATH_OTHER_APP_DESKTOP_FILE, false);
		fileProvider.fileExistsAnswers.put(PATH_OWN_DESKTOP_FILE, false);

		processStub.result = OTHER_APP_DESKTOP_FILE;

		List<ISchemeInformation> infos = registration.getSchemesInformation(Arrays.asList(ADT_SCHEME));

		assertEquals(1, infos.size());
		assertEquals("adt", infos.get(0).getName());
		assertFalse(infos.get(0).isHandled());
		assertEquals("", infos.get(0).getHandlerInstanceLocation());
	}

	@Test
	public void givesSchemeInfoForSchemeNotHandledAnymoreButInXdgMimeForOwn() throws Exception {
		fileProvider.fileExistsAnswers.put(PATH_OWN_DESKTOP_FILE, true);
		fileProvider.readAnswers.put(PATH_OWN_DESKTOP_FILE, getFileLines(OWN_EXECUTABLE_PATH, ""));
		fileProvider.readAnswers.put(PATH_OTHER_APP_DESKTOP_FILE, getFileLines(OTHER_APP_EXECUTABLE_PATH, ""));

		processStub.result = OWN_DESKTOP_FILE;

		List<ISchemeInformation> infos = registration.getSchemesInformation(Arrays.asList(ADT_SCHEME));

		assertEquals(1, infos.size());
		assertEquals("adt", infos.get(0).getName());
		assertFalse(infos.get(0).isHandled());
		assertEquals("", infos.get(0).getHandlerInstanceLocation());
	}

	@Test
	public void givesSchemeInfoForSchemeNotHandledAnymoreDesktopFileNoLongerExistsButInXdgMimeForOwn()
			throws Exception {
		fileProvider.fileExistsAnswers.put(PATH_OWN_DESKTOP_FILE, false);
		fileProvider.fileExistsAnswers.put(PATH_OTHER_APP_DESKTOP_FILE, false);

		processStub.result = OWN_DESKTOP_FILE;

		List<ISchemeInformation> infos = registration.getSchemesInformation(Arrays.asList(ADT_SCHEME));

		assertEquals(1, infos.size());
		assertEquals("adt", infos.get(0).getName());
		assertFalse(infos.get(0).isHandled());
		assertEquals("", infos.get(0).getHandlerInstanceLocation());
	}

	private List<String> getFileLines(String exec, String mimeType) {
		return Arrays.asList("[Desktop Entry]", //
				"Exec=" + exec, //
				"NoDisplay=true", //
				mimeType, //
				"Type=Application");
	}

	private void assertFilePathIs(String filePath) {
		assertEquals(filePath, fileProvider.recordedReadPaths.get(0));
		assertEquals(filePath, fileProvider.writePath);
	}

	private void assertAppNameInFileIs(String name) {
		assertThat(new String(fileProvider.writtenBytes), new StringContains("Name=" + name));
	}

	private void assertMimeTypeInFileIs(String mimeType) {
		assertThat(new String(fileProvider.writtenBytes), new StringContains("MimeType=" + mimeType));
	}

	private void assertExecInFileIs(String exec) {
		assertThat(new String(fileProvider.writtenBytes), new StringContains("Exec=" + exec));
	}

	private void assertNoMimeTypeInFile() {
		assertThat(new String(fileProvider.writtenBytes),
				new IsNot<>(new StringContains("MimeType=x-scheme-handler/adt;")));
	}

	private void assertXdgMimeCalledFor(String... schemeHandler) {
		assertEquals(1, processStub.records.size());
		assertEquals("xdg-mime", processStub.records.get(0).process);
		String schemeHandlers = Arrays.stream(schemeHandler).collect(Collectors.joining(" "));
		assertArrayEquals(new String[] { "default", OWN_DESKTOP_FILE, schemeHandlers },
				processStub.records.get(0).args);
	}
}