/*******************************************************************************
 * Copyright (c) 2007, 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 Corporation - initial API and implementation
 *     Pascal Rapicault - Support for bundled macosx http://bugs.eclipse.org/57349
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.touchpoint.eclipse;

import java.io.File;
import java.net.*;
import java.util.List;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.frameworkadmin.BundleInfo;
import org.eclipse.equinox.internal.p2.update.*;
import org.eclipse.equinox.internal.provisional.frameworkadmin.LauncherData;
import org.eclipse.equinox.internal.provisional.frameworkadmin.Manipulator;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.osgi.service.environment.Constants;
import org.eclipse.osgi.util.NLS;

/**
 * This class provides a wrapper for reading and writing platform.xml.
 * 
 * Only a minimal set of operations is exposed.
 */
public class PlatformConfigurationWrapper {

	private Configuration configuration = null;
	private Site poolSite = null;
	private File configFile;
	private URI poolURI;
	private Manipulator manipulator;

	private static String FEATURES = "features/"; //$NON-NLS-1$

	/*
	 * Use the given manipulator to calculate the OSGi install location. We can't
	 * just use the Location service here because we may not be installing into
	 * ourselves. (see https://bugs.eclipse.org/354552)
	 * 
	 * First try and calculate the location based relative to the data provided in
	 * the manipulator's launcher data. If that doesn't work then calculate it based
	 * on the location of known JARs. If that still doesn't work then return null.
	 */
	private static URL getOSGiInstallArea(Manipulator manipulator) {

		// first see if the launcher home is set
		LauncherData launcherData = manipulator.getLauncherData();
		File home = launcherData.getHome();
		if (home != null) {
			try {
				return home.toURI().toURL();
			} catch (MalformedURLException e) {
				// ignore - shouldn't happen
			}
		}

		// next try and calculate the value based on the location of the framework
		// (OSGi) jar.
		File fwkJar = launcherData.getFwJar();
		if (fwkJar != null) {
			try {
				return fromOSGiJarToOSGiInstallArea(fwkJar.getAbsolutePath()).toURI().toURL();
			} catch (MalformedURLException e) {
				// ignore - shouldn't happen
			}
		}

		// finally calculate the value based on the location of the launcher executable
		// itself
		File launcherFile = launcherData.getLauncher();
		if (launcherFile != null) {
			if (Constants.OS_MACOSX.equals(launcherData.getOS())) {
				// the equinox launcher will look 3 levels up on the mac when going from
				// executable to launcher.jar
				// see org.eclipse.equinox.executable/library/eclipse.c : findStartupJar();
				IPath launcherPath = new Path(launcherFile.getAbsolutePath());
				if (launcherPath.segmentCount() > 2) {
					// removing "Eclipse.app/Contents/MacOS/eclipse"
					launcherPath = launcherPath.removeLastSegments(2);
					try {
						return launcherPath.toFile().toURI().toURL();
					} catch (MalformedURLException e) {
						// ignore - shouldn't happen
					}
				}
			}
			try {
				return launcherFile.getParentFile().toURI().toURL();
			} catch (MalformedURLException e) {
				// ignore - shouldn't happen
			}
		}

		// we couldn't calculate it based on the info in the launcher data, so
		// try to do it based on the location of known JARs.
		final String OSGI = "org.eclipse.osgi"; //$NON-NLS-1$
		BundleInfo[] bis = manipulator.getConfigData().getBundles();
		String searchFor = "org.eclipse.equinox.launcher"; //$NON-NLS-1$
		for (int i = 0; i < bis.length; i++) {
			if (bis[i].getSymbolicName().equals(searchFor)) {
				if (bis[i].getLocation() != null) {
					try {
						if (bis[i].getLocation().getScheme().equals("file")) //$NON-NLS-1$
							return fromOSGiJarToOSGiInstallArea(bis[i].getLocation().getPath()).toURI().toURL();
					} catch (MalformedURLException e) {
						// do nothing
					}
				}
				if (searchFor.equals(OSGI))
					return null;
				searchFor = OSGI;
				i = -1;
			}
		}
		return null;
	}

	private static File fromOSGiJarToOSGiInstallArea(String path) {
		IPath parentFolder = new Path(path).removeLastSegments(1);
		if (parentFolder.lastSegment().equals("plugins")) //$NON-NLS-1$
			return parentFolder.removeLastSegments(1).toFile();
		return parentFolder.toFile();
	}

	public PlatformConfigurationWrapper(File configDir, URI featurePool, Manipulator manipulator) {
		this.configuration = null;
		this.configFile = new File(configDir, "/org.eclipse.update/platform.xml"); //$NON-NLS-1$
		this.poolURI = featurePool;
		this.manipulator = manipulator;
	}

	private void loadDelegate() {
		if (configuration != null)
			return;

		try {
			if (configFile.exists()) {
				configuration = Configuration.load(configFile, getOSGiInstallArea(manipulator));
			} else {
				configuration = new Configuration();
			}
		} catch (ProvisionException pe) {
			// TODO: Make this a real message
			throw new IllegalStateException(Messages.error_parsing_configuration);
		}
		if (poolURI == null)
			throw new IllegalStateException("Error creating platform configuration. No bundle pool defined."); //$NON-NLS-1$

		poolSite = getSite(poolURI);
		if (poolSite == null) {
			poolSite = createSite(poolURI, getDefaultPolicy());
			configuration.add(poolSite);
		}
	}

	/*
	 * Return the default policy to use when creating a new site. If there are any
	 * sites with the MANAGED-ONLY policy, then that is the default. Otherwise the
	 * default is USER-EXCLUDE.
	 */
	private String getDefaultPolicy() {
		for (Site site : configuration.getSites()) {
			if (Site.POLICY_MANAGED_ONLY.equals(site.getPolicy()))
				return Site.POLICY_MANAGED_ONLY;
		}
		return Site.POLICY_USER_EXCLUDE;
	}

	/*
	 * Create and return a site object based on the given location.
	 */
	private static Site createSite(URI location, String policy) {
		Site result = new Site();
		result.setUrl(location.toString());
		result.setPolicy(policy);
		result.setEnabled(true);
		return result;
	}

	/*
	 * Look in the configuration and return the site object whose location matches
	 * the given URL. Return null if there is no match.
	 */
	private Site getSite(URI url) {
		List<Site> sites = configuration.getSites();
		File file = URIUtil.toFile(url);
		for (Site nextSite : sites) {
			try {
				File nextFile = URIUtil.toFile(new URI(nextSite.getUrl()));
				if (nextFile == null)
					continue;
				if (nextFile.equals(file))
					return nextSite;
			} catch (URISyntaxException e) {
				// ignore incorrectly formed site
			}
		}
		return null;
	}

	/*
	 * Look in the configuration and return the site which contains the feature with
	 * the given identifier and version. Return null if there is none.
	 */
	private Site getSite(String id, String version) {
		List<Site> sites = configuration.getSites();
		for (Site site : sites) {
			Feature[] features = site.getFeatures();
			for (Feature feature : features) {
				if (id.equals(feature.getId()) && version.equals(feature.getVersion())) {
					return site;
				}
			}
		}
		return null;
	}

	public IStatus addFeatureEntry(File file, String id, String version, String pluginIdentifier, String pluginVersion,
			boolean primary, String application, URL[] root, String linkFile) {
		loadDelegate();
		if (configuration == null)
			return new Status(IStatus.WARNING, Activator.ID, Messages.platform_config_unavailable, null);

		URI fileURL = null;
		File featureDir = file.getParentFile();
		if (featureDir == null || !featureDir.getName().equals("features")) //$NON-NLS-1$
			return new Status(IStatus.ERROR, Activator.ID,
					NLS.bind(Messages.parent_dir_features, file.getAbsolutePath()), null);
		File locationDir = featureDir.getParentFile();
		if (locationDir == null)
			return new Status(IStatus.ERROR, Activator.ID,
					NLS.bind(Messages.cannot_calculate_extension_location, file.getAbsolutePath()), null);

		fileURL = locationDir.toURI();
		Site site = getSite(fileURL);
		if (site == null) {
			site = createSite(fileURL, getDefaultPolicy());
			if (linkFile != null)
				site.setLinkFile(linkFile);
			configuration.add(site);
		} else {
			// check to see if the feature already exists in this site
			if (site.getFeature(id, version) != null)
				return Status.OK_STATUS;
		}
		Feature addedFeature = new Feature(site);
		addedFeature.setId(id);
		addedFeature.setVersion(version);
		addedFeature.setUrl(makeFeatureURL(id, version));
		addedFeature.setApplication(application);
		addedFeature.setPluginIdentifier(pluginIdentifier);
		addedFeature.setPluginVersion(pluginVersion);
		addedFeature.setRoots(root);
		addedFeature.setPrimary(primary);
		site.addFeature(addedFeature);
		return Status.OK_STATUS;
	}

	public IStatus removeFeatureEntry(String id, String version) {
		loadDelegate();
		if (configuration == null)
			return new Status(IStatus.WARNING, Activator.ID, Messages.platform_config_unavailable, null);

		Site site = getSite(id, version);
		if (site == null)
			site = poolSite;
		site.removeFeature(makeFeatureURL(id, version));
		// if we weren't able to remove the feature from the site because it
		// didn't exist, then someone already did our job for us and it is ok.
		return Status.OK_STATUS;
	}

	public boolean containsFeature(URI siteURI, String featureId, String featureVersion) {
		loadDelegate();
		if (configuration == null)
			return false;

		Site site = getSite(siteURI);
		if (site == null)
			return false;

		return site.getFeature(featureId, featureVersion) != null;
	}

	public void save() throws ProvisionException {
		if (configuration != null) {
			configFile.getParentFile().mkdirs();
			configuration.save(configFile, getOSGiInstallArea(manipulator));
		}
	}

	private static String makeFeatureURL(String id, String version) {
		return FEATURES + id + "_" + version + "/"; //$NON-NLS-1$ //$NON-NLS-2$ ;
	}

}
