/*******************************************************************************
 * Copyright (c) 2009, 2012 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *     Sonatype, Inc. - ongoing development
 *******************************************************************************/
package org.eclipse.equinox.p2.internal.repository.tools;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.equinox.internal.p2.engine.*;
import org.eclipse.equinox.internal.p2.engine.phases.Collect;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.engine.*;
import org.eclipse.equinox.p2.engine.spi.ProvisioningAction;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRequest;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;

/**
 * The transformer takes an existing p2 repository (local or remote), iterates over 
 * its list of IUs, and fetches all of the corresponding artifacts to a user-specified location. 
 * Once fetched, the artifacts will be in "runnable" form... that is directory-based bundles will be
 * extracted into folders and packed JAR files will be un-packed.
 * 
 * @since 1.0
 */
public class Repo2Runnable extends AbstractApplication implements IApplication {
	private static final String NATIVE_ARTIFACTS = "nativeArtifacts"; //$NON-NLS-1$
	private static final String NATIVE_TYPE = "org.eclipse.equinox.p2.native"; //$NON-NLS-1$
	private static final String PARM_OPERAND = "operand"; //$NON-NLS-1$
	private static final String PARM_PROFILE = "profile"; //$NON-NLS-1$

	protected class CollectNativesAction extends ProvisioningAction {
		public IStatus execute(Map<String, Object> parameters) {
			InstallableUnitOperand operand = (InstallableUnitOperand) parameters.get(PARM_OPERAND);
			IInstallableUnit installableUnit = operand.second();

			IArtifactRepositoryManager manager = getArtifactRepositoryManager();
			Collection<IArtifactKey> toDownload = installableUnit.getArtifacts();
			if (toDownload == null)
				return Status.OK_STATUS;

			@SuppressWarnings("unchecked")
			List<IArtifactRequest> artifactRequests = (List<IArtifactRequest>) parameters.get(NATIVE_ARTIFACTS);

			IProfile profile = (IProfile) parameters.get(PARM_PROFILE);
			String statsParameter = null;
			if (profile != null)
				statsParameter = profile.getProperty(IProfile.PROP_STATS_PARAMETERS);

			for (IArtifactKey keyToDownload : toDownload) {
				IArtifactRequest request = manager.createMirrorRequest(keyToDownload, destinationArtifactRepository, null, null, statsParameter);
				artifactRequests.add(request);
			}
			return Status.OK_STATUS;
		}

		public IStatus undo(Map<String, Object> parameters) {
			// nothing to do for now
			return Status.OK_STATUS;
		}
	}

	protected class CollectNativesPhase extends InstallableUnitPhase {
		public CollectNativesPhase(int weight) {
			super(NATIVE_ARTIFACTS, weight);
		}

		protected List<ProvisioningAction> getActions(InstallableUnitOperand operand) {
			IInstallableUnit unit = operand.second();
			if (unit.getTouchpointType().getId().equals(NATIVE_TYPE)) {
				return Collections.<ProvisioningAction> singletonList(new CollectNativesAction());
			}
			return null;
		}

		protected IStatus initializePhase(IProgressMonitor monitor, IProfile profile, Map<String, Object> parameters) {
			parameters.put(NATIVE_ARTIFACTS, new ArrayList<Object>());
			return null;
		}

		protected IStatus completePhase(IProgressMonitor monitor, IProfile profile, Map<String, Object> parameters) {
			@SuppressWarnings("unchecked")
			List<IArtifactRequest> artifactRequests = (List<IArtifactRequest>) parameters.get(NATIVE_ARTIFACTS);
			ProvisioningContext context = (ProvisioningContext) parameters.get(PARM_CONTEXT);
			IProvisioningAgent agent = (IProvisioningAgent) parameters.get(PARM_AGENT);
			DownloadManager dm = new DownloadManager(context, agent);
			for (IArtifactRequest request : artifactRequests) {
				dm.add(request);
			}
			return dm.start(monitor);
		}
	}

	// the list of IUs that we actually transformed... could have come from the repo 
	// or have been user-specified.
	private Collection<IInstallableUnit> processedIUs = new ArrayList<IInstallableUnit>();

	/*
	 * Perform the transformation.
	 */
	public IStatus run(IProgressMonitor monitor) throws ProvisionException {
		SubMonitor progress = SubMonitor.convert(monitor, 5);

		initializeRepos(progress);

		// ensure all the right parameters are set
		validate();

		// figure out which IUs we need to process
		collectIUs(progress.newChild(1));

		// call the engine with only the "collect" phase so all we do is download
		IProfile profile = createProfile();
		try {
			IEngine engine = (IEngine) agent.getService(IEngine.SERVICE_NAME);
			if (engine == null)
				throw new ProvisionException(Messages.exception_noEngineService);
			ProvisioningContext context = new ProvisioningContext(agent);
			context.setMetadataRepositories(getRepositories(true));
			context.setArtifactRepositories(getRepositories(false));
			IProvisioningPlan plan = engine.createPlan(profile, context);
			for (Iterator<IInstallableUnit> iterator = processedIUs.iterator(); iterator.hasNext();) {
				plan.addInstallableUnit(iterator.next());
			}
			IStatus result = engine.perform(plan, getPhaseSet(), progress.newChild(1));
			PhaseSet nativeSet = getNativePhase();
			if (nativeSet != null)
				engine.perform(plan, nativeSet, progress.newChild(1));

			// publish the metadata to a destination - if requested
			publishMetadata(progress.newChild(1));

			// return the resulting status
			return result;
		} finally {
			// cleanup by removing the temporary profile and unloading the repos which were new
			removeProfile(profile);
			finalizeRepositories();
		}
	}

	protected URI[] getRepositories(boolean metadata) {
		List<URI> repos = new ArrayList<URI>();
		for (RepositoryDescriptor repo : sourceRepositories) {
			if (metadata ? repo.isMetadata() : repo.isArtifact())
				repos.add(repo.getRepoLocation());
		}
		return repos.toArray(new URI[repos.size()]);
	}

	protected PhaseSet getPhaseSet() {
		return new PhaseSet(new Phase[] {new Collect(100)}) { /* nothing to override */};
	}

	protected PhaseSet getNativePhase() {
		return new PhaseSet(new Phase[] {new CollectNativesPhase(100)}) { /*nothing to override */};
	}

	/*
	 * Figure out exactly which IUs we have to process.
	 */
	private void collectIUs(IProgressMonitor monitor) throws ProvisionException {
		// if the user told us exactly which IUs to process, then just set it and return.
		if (sourceIUs != null && !sourceIUs.isEmpty()) {
			processedIUs = sourceIUs;
			return;
		}
		// get all IUs from the repos
		if (!hasMetadataSources())
			throw new ProvisionException(Messages.exception_needIUsOrNonEmptyRepo);

		Iterator<IInstallableUnit> itor = getAllIUs(getCompositeMetadataRepository(), monitor).iterator();
		while (itor.hasNext())
			processedIUs.add(itor.next());

		if (processedIUs.isEmpty())
			throw new ProvisionException(Messages.exception_needIUsOrNonEmptyRepo);
	}

	/*
	 * If there is a destination metadata repository set, then add all our transformed
	 * IUs to it. 
	 */
	private void publishMetadata(IProgressMonitor monitor) {
		// publishing the metadata is optional
		if (destinationMetadataRepository == null)
			return;
		destinationMetadataRepository.addInstallableUnits(processedIUs);
	}

	/*
	 * Return a collector over all the IUs contained in the given repository.
	 */
	private IQueryResult<IInstallableUnit> getAllIUs(IMetadataRepository repository, IProgressMonitor monitor) {
		SubMonitor progress = SubMonitor.convert(monitor, 2);
		try {
			return repository.query(QueryUtil.createIUAnyQuery(), progress.newChild(1));
		} finally {
			progress.done();
		}
	}

	/*
	 * Remove the given profile from the profile registry.
	 */
	private void removeProfile(IProfile profile) throws ProvisionException {
		IProfileRegistry registry = Activator.getProfileRegistry();
		registry.removeProfile(profile.getProfileId());
	}

	/*
	 * Create and return a new profile.
	 */
	private IProfile createProfile() throws ProvisionException {
		Map<String, String> properties = new HashMap<String, String>();
		properties.put(IProfile.PROP_CACHE, URIUtil.toFile(destinationArtifactRepository.getLocation()).getAbsolutePath());
		properties.put(IProfile.PROP_INSTALL_FOLDER, URIUtil.toFile(destinationArtifactRepository.getLocation()).getAbsolutePath());
		IProfileRegistry registry = Activator.getProfileRegistry();
		return registry.addProfile(System.currentTimeMillis() + "-" + Math.random(), properties); //$NON-NLS-1$
	}

	/* (non-Javadoc)
	 * @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext)
	 */
	public Object start(IApplicationContext context) throws Exception {
		String[] args = (String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS);
		processCommandLineArgs(args);
		// perform the transformation
		run(null);
		return IApplication.EXIT_OK;
	}

	/*
	 * Iterate over the command-line arguments and prepare the transformer for processing.
	 */
	private void processCommandLineArgs(String[] args) throws URISyntaxException {
		if (args == null)
			return;
		for (int i = 0; i < args.length; i++) {
			String option = args[i];
			if (i == args.length - 1 || args[i + 1].startsWith("-")) //$NON-NLS-1$
				continue;
			String arg = args[++i];

			if (option.equalsIgnoreCase("-source")) { //$NON-NLS-1$
				RepositoryDescriptor source = new RepositoryDescriptor();
				source.setLocation(URIUtil.fromString(arg));
				addSource(source);
			}

			if (option.equalsIgnoreCase("-destination")) { //$NON-NLS-1$
				RepositoryDescriptor destination = new RepositoryDescriptor();
				destination.setLocation(URIUtil.fromString(arg));
				addDestination(destination);
			}
		}
	}

	/*
	 * Ensure all mandatory parameters have been set. Throw an exception if there
	 * are any missing. We don't require the user to specify the artifact repository here,
	 * we will default to the ones already registered in the manager. (callers are free
	 * to add more if they wish)
	 */
	private void validate() throws ProvisionException {
		if (!hasMetadataSources() && sourceIUs == null)
			throw new ProvisionException(Messages.exception_needIUsOrNonEmptyRepo);
		if (destinationArtifactRepository == null)
			throw new ProvisionException(Messages.exception_needDestinationRepo);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.equinox.app.IApplication#stop()
	 */
	public void stop() {
		// nothing to do
	}
}
