/*******************************************************************************
 * Copyright (c) 2008, 2017 IBM Corporation 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: IBM - Initial API and implementation
 *******************************************************************************/
package org.eclipse.pde.build.internal.tests.p2;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.equinox.internal.p2.metadata.License;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.artifact.IFileArtifactRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.pde.build.internal.tests.Utils;
import org.eclipse.pde.build.tests.BuildConfiguration;
import org.eclipse.pde.internal.build.site.BuildTimeFeature;
import org.eclipse.pde.internal.build.site.BuildTimeFeatureFactory;
import org.junit.Test;

public class LicenseTests extends P2TestCase {
	private static final int URL_EMPTY = 1;
	private static final int URL_NON_EMPTY = 2;
	private static final int URL_NONE = 0;
	private static final int DESC_EMPTY = 100;
	private static final int DESC_NON_EMPTY = 101;

	@Test
	public void testLicenseFeatureOldP2() throws Exception {
		IFolder buildFolder = newTest("testLicenseFeatureOldP2", "licenseFeature1");
		IFolder repo = Utils.createFolder(buildFolder, "repo");
		String repoLocation = "file:" + repo.getLocation().toOSString();

		Properties properties = BuildConfiguration.getBuilderProperties(buildFolder);
		properties.put("topLevelElementId", "F1");
		properties.put("generate.p2.metadata", "true");
		properties.put("p2.metadata.repo", repoLocation);
		properties.put("p2.artifact.repo", repoLocation);
		properties.put("p2.publish.artifacts", "true");
		Utils.storeBuildProperties(buildFolder, properties);
		runBuild(buildFolder);

		IMetadataRepository metadata = loadMetadataRepository(repoLocation);
		IArtifactRepository artifacts = loadArtifactRepository(repoLocation);

		IInstallableUnit iu = getIU(metadata, "F1.feature.group");
		License actualLicense = (License) iu.getLicenses().toArray()[0];

		// Check that root file ends up as an artifact
		IInstallableUnit rootFileIU = getIU(metadata, "org.eclipse.rootfiles.ANY.ANY.ANY");
		IArtifactKey rootFileKey = (IArtifactKey) rootFileIU.getArtifacts().toArray()[0];
		File actualRootFile = ((IFileArtifactRepository) artifacts).getArtifactFile(rootFileKey);

		Set<String> entries = new HashSet<>();
		entries.add("root.html");
		assertZipContents(actualRootFile, entries);

		// Check that all expected files are in zip
		entries = new HashSet<>();
		entries.add("feature.properties");
		entries.add("feature.xml");
		entries.add("feature_fr.properties");
		entries.add("license.html");
		entries.add("license_fr.html");

		assertZipContents(buildFolder, "repo/features/F1_1.0.0.jar", entries);

		// Check that feature.properties contains all original properties PLUS
		// properties from license
		IFile licensePropertyFile = buildFolder.getFile("features/L1/feature.properties");
		Properties licenseProperties = Utils.loadProperties(licensePropertyFile);

		IFile originalPropertyFile = buildFolder.getFile("features/F1/feature.properties");
		Properties originalProperties = Utils.loadProperties(originalPropertyFile);

		IFile actualPropertiesFile = buildFolder.getFile("checkProperties");
		Utils.extractFromZip(buildFolder, "I.TestBuild/F1-TestBuild.zip",
				"eclipse/features/F1_1.0.0/feature.properties", actualPropertiesFile);
		Properties actualProperties = Utils.loadProperties(actualPropertiesFile);

		assertEquals("Result feature.properties has incorrect number of properties", originalProperties.size() + 2,
				actualProperties.size());

		Enumeration<Object> keys = originalProperties.keys();
		while (keys.hasMoreElements()) {
			String key = (String) keys.nextElement();
			assertEquals(originalProperties.getProperty(key, "originalMissing"), actualProperties.getProperty(key));
		}

		keys = licenseProperties.keys();
		while (keys.hasMoreElements()) {
			String key = (String) keys.nextElement();
			assertEquals(licenseProperties.getProperty(key, "originalMissing"), actualProperties.getProperty(key));
		}

		// Check that license from L1 ends up int he meta data for the feature
		BuildTimeFeatureFactory factory = new BuildTimeFeatureFactory();

		IFile licenseFeatureFile = buildFolder.getFile("features/L1/feature.xml");
		BuildTimeFeature licenseFeature = factory.parseBuildFeature(licenseFeatureFile.getLocation().toPath());

		IFile originalFeatureFile = buildFolder.getFile("features/F1/feature.xml");
		BuildTimeFeature originalFeature = factory.parseBuildFeature(originalFeatureFile.getLocation().toPath());

		assertEquals(licenseFeature.getLicenseURL(), URIUtil.toUnencodedString(actualLicense.getLocation()));
		assertEquals(licenseFeature.getLicense(), actualLicense.getBody());

		// Check that license elements in feature.xml where changed.
		IFile actualFeatureFile = buildFolder.getFile("checkFeature.xml");
		Utils.extractFromZip(buildFolder, "I.TestBuild/F1-TestBuild.zip", "eclipse/features/F1_1.0.0/feature.xml",
				actualFeatureFile);
		BuildTimeFeature actualFeature = factory.parseBuildFeature(actualFeatureFile.getLocation().toPath());

		assertNotNull(actualFeature.getLicense());
		assertEquals(licenseFeature.getLicense(), actualFeature.getLicense());

		assertNotNull(actualFeature.getLicenseURL());
		assertEquals(licenseFeature.getLicenseURL(), actualFeature.getLicenseURL());

		assertEquals(originalFeature.getId(), actualFeature.getId());
		assertEquals(originalFeature.getDescription(), actualFeature.getDescription());
		assertEquals(originalFeature.getCopyright(), actualFeature.getCopyright());
	}

	// Test various combinations of empty and non-empty license and payload values
	@Test
	public void testEmptyCombosPDE() throws Exception {

		// Prepare
		IFolder buildFolder = newTest("testEmptyCombosPDE", "emptyLicenseTests");
		Properties properties = BuildConfiguration.getBuilderProperties(buildFolder);
		properties.put("topLevelElementId", "F1");
		properties.put("baseLocation", "");
		Utils.storeBuildProperties(buildFolder, properties);

		// Test URL combinations
		for (int licenseState = 0; licenseState < 3; licenseState++) {
			for (int payloadState = 0; payloadState < 3; payloadState++) {
				copyFeature(buildFolder, "L1", licenseState);
				copyFeature(buildFolder, "F1", payloadState);
				runBuild(buildFolder);
				checkBuiltFeature(buildFolder, getStateMessage(licenseState, payloadState));
				new File(buildFolder.getLocation().toFile(), "I.TestBuild/F1-TestBuild.zip").delete();
			}
		}

		// Test Description combinations
		for (int licenseState = 100; licenseState < 102; licenseState++) {
			for (int payloadState = 0; payloadState < 3; payloadState++) {
				copyFeature(buildFolder, "L1", licenseState);
				copyFeature(buildFolder, "F1", payloadState);
				runBuild(buildFolder);
				checkBuiltFeature(buildFolder, getStateMessage(licenseState, payloadState));
				new File(buildFolder.getLocation().toFile(), "I.TestBuild/F1-TestBuild.zip").delete();
			}
		}
	}

	@Test
	public void testRootUsedWithNoLicenseRefPDE() throws Exception {
		boolean expectedExceptionCaught = false;

		IFolder buildFolder = newTest("testRootUsedWithNoLicenseRefPDE", "rootNoRef");
		Properties properties = BuildConfiguration.getBuilderProperties(buildFolder);
		properties.put("topLevelElementId", "F1");
		Utils.storeBuildProperties(buildFolder, properties);
		try {
			runBuild(buildFolder);
		} catch (Exception e) {
			assertTrue("Build throws wrong exception", e.getMessage()
					.indexOf("uses 'license:' root keyword but does not reference a license feature") != -1);
			expectedExceptionCaught = true;
		}

		assertTrue("Build should have failed", expectedExceptionCaught);
	}

	@Test
	public void testLicenseFeaturePDE() throws Exception {
		IFolder buildFolder = newTest("testLicenseFeaturePDE", "licenseFeature1");
		Properties properties = BuildConfiguration.getBuilderProperties(buildFolder);
		properties.put("topLevelElementId", "F1");
		Utils.storeBuildProperties(buildFolder, properties);
		runBuild(buildFolder);

		// Check that all expected files are in zip
		Set<String> entries = new HashSet<>();
		entries.add("eclipse/features/F1_1.0.0/feature.properties");
		entries.add("eclipse/features/F1_1.0.0/feature.xml");
		entries.add("eclipse/features/F1_1.0.0/feature_fr.properties");
		entries.add("eclipse/features/F1_1.0.0/license.html");
		entries.add("eclipse/features/F1_1.0.0/license_fr.html");
		entries.add("eclipse/root.html");

		assertZipContents(buildFolder, "I.TestBuild/F1-TestBuild.zip", entries);

		// Check that feature.properties contains all original properties PLUS
		// properties from license
		IFile licensePropertyFile = buildFolder.getFile("features/L1/feature.properties");
		Properties licenseProperties = Utils.loadProperties(licensePropertyFile);

		IFile originalPropertyFile = buildFolder.getFile("features/F1/feature.properties");
		Properties originalProperties = Utils.loadProperties(originalPropertyFile);

		IFile actualPropertiesFile = buildFolder.getFile("checkProperties");
		Utils.extractFromZip(buildFolder, "I.TestBuild/F1-TestBuild.zip",
				"eclipse/features/F1_1.0.0/feature.properties", actualPropertiesFile);
		Properties actualProperties = Utils.loadProperties(actualPropertiesFile);

		assertEquals("Result feature.properties has incorrect number of properties", originalProperties.size() + 2,
				actualProperties.size());

		Enumeration<Object> keys = originalProperties.keys();
		while (keys.hasMoreElements()) {
			String key = (String) keys.nextElement();
			assertEquals(originalProperties.getProperty(key, "originalMissing"), actualProperties.getProperty(key));
		}

		keys = licenseProperties.keys();
		while (keys.hasMoreElements()) {
			String key = (String) keys.nextElement();
			assertEquals(licenseProperties.getProperty(key, "originalMissing"), actualProperties.getProperty(key));
		}

		checkBuiltFeature(buildFolder, "");
	}

	@Test
	public void testP2Gathering() throws Exception {
		IFolder buildFolder = newTest("licenseP2Gathering", "licenseFeature1");

		Utils.writeBuffer(buildFolder.getFile("features/L1/license.html"), new StringBuffer("I'm a license\n"));
		Properties properties = new Properties();
		properties.put("bin.includes", "feature.xml,feature.properties,license.html");
		Utils.storeBuildProperties(buildFolder.getFolder("features/L1"), properties);

		properties = BuildConfiguration.getBuilderProperties(buildFolder);
		properties.put("topLevelElementId", "F1");
		properties.put("baseLocation", "");
		properties.put("p2.gathering", "true");

		Utils.storeBuildProperties(buildFolder, properties);

		runBuild(buildFolder);

		Set<String> entries = new HashSet<>();
		entries.add("license.html");
		assertZipContents(buildFolder, "buildRepo/features/F1_1.0.0.jar", entries);

		// also try using the gather task directly
		IFile testXML = buildFolder.getFile("test.xml");
		IFolder repo2 = Utils.createFolder(buildFolder, "repo2");
		StringBuffer test = new StringBuffer();
		test.append(
				"<project default=\"publish\">                                                                    \n");
		test.append(
				"  <target name=\"publish\">                                                                      \n");
		test.append(
				"    <eclipse.gatherFeature metadataRepository=\"${repo}\" artifactRepository=\"${repo}\"         \n");
		test.append(
				"                        buildResultFolder=\"${folder}\" baseDirectory=\"${folder}\"              \n");
		test.append(
				"                        licenseDirectory=\"${license}\" />                                       \n");
		test.append(
				"  </target>                                                                                     \n");
		test.append(
				"</project>                                                                                      \n");
		Utils.writeBuffer(testXML, test);

		properties = new Properties();
		properties.put("repo", URIUtil.toUnencodedString(repo2.getLocationURI()));
		properties.put("folder", buildFolder.getFolder("features/F1").getLocation().toString());
		properties.put("license", buildFolder.getFolder("features/L1").getLocation().toString());
		runAntScript(testXML.getLocation().toOSString(), new String[] { "publish" },
				buildFolder.getLocation().toOSString(), properties);

		entries = new HashSet<>();
		entries.add("license.html");
		assertZipContents(repo2, "features/F1_1.0.0.jar", entries);
	}

	@Test
	public void testP2Gathering_Custom() throws Exception {
		IFolder buildFolder = newTest("licenseP2GatheringCustom", "licenseFeature1");

		Utils.writeBuffer(buildFolder.getFile("features/L1/rootFile.html"), new StringBuffer("I'm a license\n"));
		Utils.writeBuffer(buildFolder.getFile("features/L1/sub/license.html"), new StringBuffer("I'm a license\n"));
		Properties properties = new Properties();
		properties.put("bin.includes", "feature.xml,feature.properties,sub/license.html");
		Utils.storeBuildProperties(buildFolder.getFolder("features/L1"), properties);

		IFolder f = buildFolder.getFolder("features/F1");
		properties = new Properties();
		properties.put("bin.includes", "feature.xml");
		properties.put("customBuildCallbacks", "true");
		properties.put("root", "license:file:rootFile.html");
		Utils.storeBuildProperties(f, properties);

		StringBuffer buffer = new StringBuffer();
		buffer.append("<project name=\"customGather\" default=\"noDefault\">			\n");
		buffer.append("   <target name=\"pre.gather.bin.parts\">						\n");
		buffer.append("      <concat destfile=\"${feature.directory}/a.txt\">			\n");
		buffer.append("        Mary had a little lamb.									\n");
		buffer.append("      </concat>													\n");
		buffer.append("   </target>														\n");
		buffer.append("</project>														\n");
		Utils.writeBuffer(f.getFile("customBuildCallbacks.xml"), buffer);

		properties = BuildConfiguration.getBuilderProperties(buildFolder);
		properties.put("topLevelElementId", "F1");
		properties.put("baseLocation", "");
		properties.put("p2.gathering", "true");

		Utils.storeBuildProperties(buildFolder, properties);

		runBuild(buildFolder);

		Set<String> entries = new HashSet<>();
		entries.add("sub/license.html");
		entries.add("a.txt");
		assertZipContents(buildFolder, "buildRepo/features/F1_1.0.0.jar", entries);

		entries = new HashSet<>();
		entries.add("rootFile.html");
		assertZipContents(buildFolder, "buildRepo/binary/F1_root_1.0.0", entries);
	}

	@Test
	public void testBinaryLicense() throws Exception {
		IFolder buildFolder = newTest("binaryLicense", "licenseFeature1");

		Utils.writeBuffer(buildFolder.getFile("features/L1/META-INF/MANIFEST.MF"), new StringBuffer("manifest\n"));
		Utils.writeBuffer(buildFolder.getFile("features/L1/META-INF/ECLIPSEF.RSA"), new StringBuffer("rsa\n"));
		Utils.writeBuffer(buildFolder.getFile("features/L1/sub/license.html"), new StringBuffer("I'm a license\n"));
		Properties properties = new Properties();
		properties.put("bin.includes", "feature.xml,feature.properties,sub/license.html, META-INF/");
		Utils.storeBuildProperties(buildFolder.getFolder("features/L1"), properties);

		properties = BuildConfiguration.getBuilderProperties(buildFolder);
		properties.put("topLevelElementId", "L1");
		properties.put("baseLocation", "");
		properties.put("archivesFormat", "*,*,*-folder");
		Utils.storeBuildProperties(buildFolder, properties);

		runBuild(buildFolder);

		IFolder build2 = Utils.createFolder(buildFolder, "build2");
		Utils.copy(buildFolder.getFolder("features/F1").getLocation().toFile(),
				build2.getFolder("features/F1").getLocation().toFile());

		properties = BuildConfiguration.getBuilderProperties(build2);
		properties.put("topLevelElementId", "F1");
		properties.put("baseLocation", "");
		properties.put("pluginPath", buildFolder.getFolder("tmp/eclipse").getLocation().toString());
		properties.put("archivesFormat", "*,*,*-folder");
		Utils.storeBuildProperties(build2, properties);
		runBuild(build2);

		assertResourceFile(build2, "tmp/eclipse/features/F1_1.0.0/sub/license.html");
		assertFalse(build2.getFolder("tmp/eclipse/features/F1_1.0.0/META-INF").exists());

		IFolder build3 = Utils.createFolder(buildFolder, "build3");
		Utils.copy(buildFolder.getFolder("features/F1").getLocation().toFile(),
				build3.getFolder("features/F1").getLocation().toFile());

		properties = BuildConfiguration.getBuilderProperties(build3);
		properties.put("topLevelElementId", "F1");
		properties.put("baseLocation", "");
		properties.put("pluginPath", buildFolder.getFolder("tmp/eclipse").getLocation().toString());
		properties.put("p2.gathering", "true");
		Utils.storeBuildProperties(build3, properties);
		runBuild(build3);

		Set<String> entries = new HashSet<>();
		entries.add("sub/license.html");
		assertZipContents(build3, "buildRepo/features/F1_1.0.0.jar", entries);
		assertResourceFile(build2, "tmp/eclipse/features/F1_1.0.0/sub/license.html");
		assertFalse(build2.getFolder("tmp/eclipse/features/F1_1.0.0/META-INF").exists());
		assertResourceFile(build3, "buildRepo/features/F1_1.0.0.jar");
		assertFalse(Utils.extractFromZip(build3, "buildRepo/features/F1_1.0.0.jar", "META-INF/ECLIPSEF.RSA",
				build3.getFile("rsa.txt")));
	}

	private String getStateMessage(int state) {
		switch (state) {
		case URL_NONE:
			return "No URL";
		case URL_EMPTY:
			return "URL Empty";
		case URL_NON_EMPTY:
			return "URL Not Empty";
		case DESC_EMPTY:
			return "Description Empty";
		case DESC_NON_EMPTY:
			return "Description Not Empty";
		default:
			return "unknown";
		}
	}

	private String getStateMessage(int licenseState, int payloadState) {
		String lState = getStateMessage(licenseState);
		String pState = getStateMessage(payloadState);

		return "License State: " + lState + " Payload State: " + pState + ": ";
	}

	private void checkBuiltFeature(IFolder buildFolder, String errorMessage) throws Exception {
		// Check that license elements in feature.xml where changed.
		BuildTimeFeatureFactory factory = new BuildTimeFeatureFactory();

		// IFile actualFeatureFile =
		// buildFolder.getFile("tmp/eclipse/features/F1_1.0.0/feature.xml");//buildFolder.getFile("checkFeature.xml");
		IFile actualFeatureFile = buildFolder.getFile("checkFeature.xml");
		assertTrue(Utils.extractFromZip(buildFolder, "I.TestBuild/F1-TestBuild.zip",
				"eclipse/features/F1_1.0.0/feature.xml", actualFeatureFile));
		BuildTimeFeature actualFeature = factory.parseBuildFeature(actualFeatureFile.getLocation().toPath());

		IFile licenseFeatureFile = buildFolder.getFile("features/L1/feature.xml");
		BuildTimeFeature licenseFeature = factory.parseBuildFeature(licenseFeatureFile.getLocation().toPath());

		IFile originalFeatureFile = buildFolder.getFile("features/F1/feature.xml");
		BuildTimeFeature originalFeature = factory.parseBuildFeature(originalFeatureFile.getLocation().toPath());

		assertNotNull(errorMessage + "license was null", actualFeature.getLicense());
		assertEquals(errorMessage + "license text not equal", licenseFeature.getLicense(), actualFeature.getLicense());

		assertNotNull(errorMessage + "license url was null", actualFeature.getLicenseURL());
		assertEquals(errorMessage + "license url not equal", licenseFeature.getLicenseURL(),
				actualFeature.getLicenseURL());

		assertEquals(errorMessage + "feature ID corrupted", originalFeature.getId(), actualFeature.getId());
		assertEquals(errorMessage + "feature description corrupted", originalFeature.getDescription(),
				actualFeature.getDescription());
		assertEquals(errorMessage + "feature copyright corrupted", originalFeature.getCopyright(),
				actualFeature.getCopyright());
	}

	private void copyFeature(IFolder buildFolder, String featureID, int state) throws CoreException {
		String sourceFileName = null;

		switch (state) {
		case URL_EMPTY:
			sourceFileName = "emptyLicenseURLFeature.xml";
			break;
		case URL_NON_EMPTY:
			sourceFileName = "nonEmptyLicenseURLFeature.xml";
			break;
		case URL_NONE:
			sourceFileName = "noLicenseURLFeature.xml";
			break;
		case DESC_EMPTY:
			sourceFileName = "emptyLicenseDescFeature.xml";
			break;
		case DESC_NON_EMPTY:
			sourceFileName = "nonEmptyLicenseDescFeature.xml";
			break;
		}

		IFile source = buildFolder.getFile("features/" + featureID + "/" + sourceFileName);
		IFile dest = buildFolder.getFile("features/" + featureID + "/feature.xml");
		if (dest.exists()) {
			dest.delete(true, null);
		}
		source.copy(dest.getFullPath(), true, null);
	}

	@Test
	public void testBug338835_MissingLicenseSection() throws Exception {
		IFolder buildFolder = newTest("338835");
		IFolder featureFolder = Utils.createFolder(buildFolder, "feature");
		IFolder licenseFolder = Utils.createFolder(buildFolder, "license");

		StringBuffer buffer = new StringBuffer();
		buffer.append("<feature id=\"org.eclipse.ptp\" version=\"5.0.0.qualifier\"								\n");
		buffer.append(" license-feature=\"org.eclipse.ptp.license\" license-feature-version=\"1.0.0.qualifier\">\n");
		buffer.append("	<description url=\"http://eclipse.org/ptp\">%description</description> 					\n");
		buffer.append("	<copyright>%copyright</copyright>  														\n");
		buffer.append("	<url> 																					\n");
		buffer.append("		<update label=\"%updateSiteName\" url=\"https://download.eclipse.org/updates\"/> 	\n");
		buffer.append("	</url> 																					\n");
		buffer.append("</feature> 																				\n");
		Utils.writeBuffer(featureFolder.getFile("feature.xml"), buffer);

		buffer.append("<feature id=\"license\" version=\"5.0.0.qualifier\" >\n");
		buffer.append("	<license url=\"http://eclipse.org/license.html\"> 	\n");
		buffer.append("		This is legal stuff 							\n");
		buffer.append("	</license> 											\n");
		buffer.append("</feature> 											\n");
		Utils.writeBuffer(licenseFolder.getFile("feature.xml"), buffer);

		buffer = new StringBuffer();
		buffer.append(
				"<project name=\"build\" basedir=\".\" >														\n");
		buffer.append(
				"	<target name=\"test\">																	 	\n");
		buffer.append("		<eclipse.licenseReplacer featureFilePath=\"" + featureFolder.getLocation().toOSString()
				+ "\"	\n");
		buffer.append("			licenseFilePath=\"" + licenseFolder.getLocation().toOSString()
				+ "\" /> 					\n");
		buffer.append(
				"	</target>																					\n");
		buffer.append(
				"</project>																					\n");
		IFile buildXml = buildFolder.getFile("build.xml");
		Utils.writeBuffer(buildXml, buffer);

		runAntScript(buildXml.getLocation().toOSString(), new String[] { "test" },
				buildFolder.getLocation().toOSString(), null);
		BuildTimeFeature feature = new BuildTimeFeatureFactory()
				.parseBuildFeature(featureFolder.getFile("feature.xml").getLocation().toPath());

		assertEquals(feature.getLicense().trim(), "This is legal stuff");
	}
}
