/*******************************************************************************
 *  Copyright (c) 2009, 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
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.engine;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus;
import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.engine.IProfile;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.query.IQuery;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.repository.IRepository;
import org.eclipse.equinox.p2.repository.IRepositoryReference;
import org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository;
import org.eclipse.equinox.p2.repository.spi.RepositoryReference;
import org.eclipse.osgi.util.NLS;

public class ProfileMetadataRepository extends AbstractMetadataRepository {

	private static final String DEFAULT_ARTIFACT_REPO_DIRECTORY = "org.eclipse.equinox.p2.core/cache"; //$NON-NLS-1$
	private static final String ARTIFACTS_XML = "artifacts.xml"; //$NON-NLS-1$
	private static final String FILE_SCHEME = "file"; //$NON-NLS-1$
	private static final String DOT_PROFILE = ".profile"; //$NON-NLS-1$
	public static final String TYPE = "org.eclipse.equinox.p2.engine.repo.metadataRepository"; //$NON-NLS-1$
	public static final Integer VERSION = 1;
	private IProfile profile;
	private HashSet<IRepositoryReference> repositories = new HashSet<>();

	public ProfileMetadataRepository(IProvisioningAgent agent, URI location, IProgressMonitor monitor) throws ProvisionException {
		super(agent, location.toString(), TYPE, VERSION.toString(), location, null, null, null);

		try {
			profile = getProfile(agent, location);
		} catch (RuntimeException e) {
			throw new ProvisionException(new Status(IStatus.ERROR, EngineActivator.ID, ProvisionException.REPOSITORY_FAILED_READ, e.getMessage(), e));
		}
		publishArtifactRepos();
	}

	private void publishArtifactRepos() {
		List<URI> artifactRepos = findArtifactRepos();

		IProvisioningEventBus bus = getProvisioningAgent().getService(IProvisioningEventBus.class);
		if (bus == null)
			return;
		for (URI repo : artifactRepos) {
			repositories.add(new RepositoryReference(repo, null, IRepository.TYPE_ARTIFACT, IRepository.ENABLED));
			bus.publishEvent(new RepositoryEvent(repo, IRepository.TYPE_ARTIFACT, RepositoryEvent.DISCOVERED, true));
		}
	}

	private List<URI> findArtifactRepos() {
		List<URI> artifactRepos = new ArrayList<>();
		File p2Directory = findP2Directory();

		// Add the profile registry's default agent artifact repository.
		// Currently this is used by the Native Touchpoint to store artifacts however
		// other touchpoints might use this as well.
		File agentArtifactRepository = findAgentArtifactRepositoryDirectory(p2Directory);
		if (agentArtifactRepository != null)
			artifactRepos.add(agentArtifactRepository.toURI());

		// bundle pool
		String bundlePool = profile.getProperty(IProfile.PROP_CACHE);
		if (bundlePool != null) {
			File bundlePoolFile = new File(bundlePool);
			if (bundlePoolFile.exists())
				artifactRepos.add(bundlePoolFile.toURI());
			else if (Boolean.parseBoolean(profile.getProperty(IProfile.PROP_ROAMING))) {
				// the profile has not been used yet but is a roaming profile
				// best effort to add "just" the default bundle pool
				bundlePoolFile = findDefaultBundlePool(p2Directory);
				if (bundlePoolFile != null)
					artifactRepos.add(bundlePoolFile.toURI());
				return artifactRepos;
			}
		}

		// shared bundle pool
		String sharedBundlePool = profile.getProperty(IProfile.PROP_SHARED_CACHE);
		if (sharedBundlePool != null)
			artifactRepos.add(new File(sharedBundlePool).toURI());

		// cache extensions
		// Currently set exclusively by dropins
		String dropinRepositories = profile.getProperty("org.eclipse.equinox.p2.cache.extensions"); //$NON-NLS-1$
		if (dropinRepositories != null) {
			StringTokenizer tokenizer = new StringTokenizer(dropinRepositories, "|"); //$NON-NLS-1$
			while (tokenizer.hasMoreTokens()) {
				String repoLocation = ""; //$NON-NLS-1$
				try {
					repoLocation = tokenizer.nextToken();
					artifactRepos.add(new URI(repoLocation));
				} catch (URISyntaxException e) {
					LogHelper.log(new Status(IStatus.WARNING, EngineActivator.ID, "invalid repo reference with location: " + repoLocation, e)); //$NON-NLS-1$
				}
			}
		}
		return artifactRepos;
	}

	private File findAgentArtifactRepositoryDirectory(File p2Directory) {
		if (p2Directory == null)
			return null;

		File agentArtifactRepositoryDirectory = new File(p2Directory, DEFAULT_ARTIFACT_REPO_DIRECTORY);
		if (!agentArtifactRepositoryDirectory.isDirectory())
			return null;

		return agentArtifactRepositoryDirectory;
	}

	private File findDefaultBundlePool(File p2Directory) {
		if (p2Directory == null)
			return null;

		File productDirectory = p2Directory.getParentFile();
		if (productDirectory == null || !(new File(productDirectory, ARTIFACTS_XML).exists()))
			return null;

		return productDirectory;
	}

	private File findP2Directory() {
		File target = new File(getLocation());
		if (target.isFile())
			target = target.getParentFile();

		// by default the profile registry is in {product}/p2/org.eclipse.equinox.p2.engine/profileRegistry
		// the default bundle pool is in the {product} folder
		File profileRegistryDirectory = target.getParentFile();
		if (profileRegistryDirectory == null)
			return null;

		File p2EngineDirectory = profileRegistryDirectory.getParentFile();
		if (p2EngineDirectory == null)
			return null;

		return p2EngineDirectory.getParentFile();
	}

	@Override
	public Collection<IRepositoryReference> getReferences() {
		return Collections.unmodifiableCollection(repositories);
	}

	@Override
	public void initialize(RepositoryState state) {
		// nothing to do
	}

	@Override
	public IQueryResult<IInstallableUnit> query(IQuery<IInstallableUnit> query, IProgressMonitor monitor) {
		return profile.query(query, monitor);
	}

	private static IProfile getProfile(IProvisioningAgent agent, URI location) throws ProvisionException {
		if (!FILE_SCHEME.equalsIgnoreCase(location.getScheme()))
			fail(location, ProvisionException.REPOSITORY_NOT_FOUND);

		File target = new File(location);
		if (!target.exists())
			fail(location, ProvisionException.REPOSITORY_NOT_FOUND);

		long timestamp = -1;
		int index = target.getName().lastIndexOf(DOT_PROFILE);
		if (index == -1)
			fail(location, ProvisionException.REPOSITORY_NOT_FOUND);
		String profileId = target.getName().substring(0, index);
		if (target.isFile()) {
			try {
				timestamp = Long.parseLong(profileId);
			} catch (NumberFormatException e) {
				fail(location, ProvisionException.REPOSITORY_FAILED_READ);
			}
			target = target.getParentFile();
			if (target == null)
				fail(location, ProvisionException.REPOSITORY_NOT_FOUND);
			index = target.getName().lastIndexOf(DOT_PROFILE);
			profileId = target.getName().substring(0, index);
		}
		profileId = SimpleProfileRegistry.unescape(profileId);

		File registryDirectory = target.getParentFile();
		if (registryDirectory == null)
			fail(location, ProvisionException.REPOSITORY_NOT_FOUND);
		SimpleProfileRegistry profileRegistry = new SimpleProfileRegistry(agent, registryDirectory, null, false);
		if (timestamp == -1) {
			long[] timestamps = profileRegistry.listProfileTimestamps(profileId);
			timestamp = timestamps[timestamps.length - 1];
		}
		IProfile profile = profileRegistry.getProfile(profileId, timestamp);
		if (profile == null)
			fail(location, ProvisionException.REPOSITORY_NOT_FOUND);

		return profile;
	}

	private static void fail(URI location, int code) throws ProvisionException {
		switch (code) {
			case ProvisionException.REPOSITORY_NOT_FOUND :
				String msg = NLS.bind(Messages.io_NotFound, location);
				throw new ProvisionException(new Status(IStatus.ERROR, EngineActivator.ID, ProvisionException.REPOSITORY_NOT_FOUND, msg, null));
			case ProvisionException.REPOSITORY_FAILED_READ :
				msg = NLS.bind(Messages.io_FailedRead, location);
				throw new ProvisionException(new Status(IStatus.ERROR, EngineActivator.ID, ProvisionException.REPOSITORY_NOT_FOUND, msg, null));
		}
	}
}
