/*******************************************************************************
 * Copyright (c) 2007, 2008 Symbian Software Systems 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:
 * Andrew Ferguson (Symbian) - Initial implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core.pdom.export;

import java.io.File;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.IPDOMIndexerTask;
import org.eclipse.cdt.core.dom.IPDOMManager;
import org.eclipse.cdt.core.index.export.ExternalExportProjectProvider;
import org.eclipse.cdt.core.index.export.IExportProjectProvider;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.ProgressProvider;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;

/**
 * An eclipse application for generating PDOM's without starting the Workbench
 */
public class GeneratePDOMApplication implements IApplication {
	private static final String EXPORT_PROJECT_PROVIDER = "ExportProjectProvider"; //$NON-NLS-1$
	private static final String DEFAULT_PROJECT_PROVIDER = ExternalExportProjectProvider.class.getName();
	public static final String OPT_PROJECTPROVIDER= "-pprovider"; //$NON-NLS-1$
	public static final String OPT_TARGET= "-target"; //$NON-NLS-1$
	public static final String OPT_QUIET= "-quiet"; //$NON-NLS-1$
	public static final String OPT_INDEXER_ID= "-indexer"; //$NON-NLS-1$

	/**
	 * Applications needing to fail in an expected way (without stack dump), should throw
	 * CoreExceptions with this error code.
	 */
	public static final int ECODE_EXPECTED_FAILURE= 1;
	
	private static Map<String,IExportProjectProvider> projectInitializers;

	/**
	 * Starts this application
	 * @throws CoreException on an unexpected failure
	 */
	public Object start(IApplicationContext context) throws CoreException {
		Object result= IApplication.EXIT_OK;
		try {
			result= startImpl(context);
		} catch(CoreException ce) {
			IStatus s= ce.getStatus();
			if(s.getCode()==ECODE_EXPECTED_FAILURE) {
				output(s.getMessage());
			} else {
				throw ce;
			}
		}
		return result;
	}
	
	private Object startImpl(IApplicationContext context) throws CoreException {
		String[] appArgs= (String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS);
		Map<String,List<String>> arguments= CLIUtil.parseToMap(appArgs);
		output(Messages.GeneratePDOMApplication_Initializing);

		setupCLIProgressProvider();

		String pproviderFQN;
		if(!arguments.containsKey(OPT_PROJECTPROVIDER)) {
			output(MessageFormat.format(Messages.GeneratePDOMApplication_UsingDefaultProjectProvider, new Object[] {DEFAULT_PROJECT_PROVIDER}));
			pproviderFQN= DEFAULT_PROJECT_PROVIDER;
		} else {
			pproviderFQN= CLIUtil.getArg(arguments, OPT_PROJECTPROVIDER, 1).get(0);
		}
		String target= CLIUtil.getArg(arguments, OPT_TARGET, 1).get(0); 
		boolean quiet= arguments.get(OPT_QUIET)!=null;

		String indexerID= IPDOMManager.ID_FAST_INDEXER;
		List<String> indexerIDs= arguments.get(OPT_INDEXER_ID);
		if(indexerIDs!=null) {
			if(indexerIDs.size()==1) {
				indexerID= indexerIDs.get(0);
			} else if(indexerIDs.size()>1) {
				fail(MessageFormat.format(Messages.GeneratePDOMApplication_InvalidIndexerID, new Object[] {OPT_INDEXER_ID}));
			}
		}
		
		String[] oldvals= null;
		if(!quiet) {
			oldvals= new String[] {
					System.getProperty(IPDOMIndexerTask.TRACE_ACTIVITY),
					System.getProperty(IPDOMIndexerTask.TRACE_PROBLEMS),
					System.getProperty(IPDOMIndexerTask.TRACE_STATISTICS),
			};
			System.setProperty(IPDOMIndexerTask.TRACE_ACTIVITY, Boolean.TRUE.toString());
			System.setProperty(IPDOMIndexerTask.TRACE_PROBLEMS, Boolean.TRUE.toString());
			System.setProperty(IPDOMIndexerTask.TRACE_STATISTICS, Boolean.TRUE.toString());
		}
		try {
			IExportProjectProvider pprovider = getExportProjectProvider(pproviderFQN);
			if(pprovider==null) {
				fail(MessageFormat.format(Messages.GeneratePDOMApplication_CouldNotFindInitializer, new Object[]{pproviderFQN}));
			}
			File targetLocation = new File(target);

			GeneratePDOM generate = new GeneratePDOM(pprovider,	appArgs, targetLocation, indexerID);
			output(Messages.GeneratePDOMApplication_GenerationStarts);
			generate.run(); // CoreException handled in start method
			output(Messages.GeneratePDOMApplication_GenerationEnds);
		} finally {
			if (oldvals != null) {
				restoreSystemProperty(IPDOMIndexerTask.TRACE_ACTIVITY, oldvals[0]);
				restoreSystemProperty(IPDOMIndexerTask.TRACE_PROBLEMS, oldvals[1]);
				restoreSystemProperty(IPDOMIndexerTask.TRACE_STATISTICS, oldvals[2]);
			}
		}
		return null;
	}

	private void restoreSystemProperty(String key, String value) {
		if (value == null) {
			System.clearProperty(key);
		} else {
			System.setProperty(key, value);
		}
	}

	protected void output(String s) {
		System.out.println(s);
	}

	public void stop() {
		// do nothing
	}

	/**
	 * Causes the application to fail in a way that has been anticipated (e.g. a command-line or interface
	 * contract violation by a extension implementation)
	 * @param message
	 * @throws CoreException
	 */
	public static final void fail(String message) throws CoreException {
		IStatus status= new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, ECODE_EXPECTED_FAILURE, message, null);
		CCorePlugin.log(status);
		throw new CoreException(status);
	}
	
	/**
	 * Returns the IExportProjectProvider registered in the plug-in registry under the
	 * specified fully qualified class name
	 * May return null
	 * @param fqn
	 * @return
	 */
	private static synchronized IExportProjectProvider getExportProjectProvider(String fqn) {
		if(projectInitializers==null) {
			projectInitializers = new HashMap<String, IExportProjectProvider>();
			IExtensionRegistry registry = Platform.getExtensionRegistry();
			IExtensionPoint indexExtensions = registry.getExtensionPoint(CCorePlugin.INDEX_UNIQ_ID);
			IExtension[] extensions = indexExtensions.getExtensions();
			for (IExtension extension : extensions) {
				IConfigurationElement[] ce = extension.getConfigurationElements();

				for (IConfigurationElement element : ce) {
					if(element.getName().equals(EXPORT_PROJECT_PROVIDER)) {
						try {
							IExportProjectProvider epp = (IExportProjectProvider) element.createExecutableExtension("class"); //$NON-NLS-1$
							projectInitializers.put(epp.getClass().getName(), epp);
						} catch(CoreException cee) {
							CCorePlugin.log(cee);
						}
					}
				}
			}
		}

		IExportProjectProvider initer = projectInitializers.get(fqn);
		return initer;
	}

	/**
	 * In this application, the usual progress reports are redirected to standard out
	 */
	private void setupCLIProgressProvider() {
		ProgressProvider pp = new ProgressProvider() {
			class IndexingStreamProgressMonitor extends StreamProgressMonitor {
				public IndexingStreamProgressMonitor(PrintStream writer) {
					super(writer);
				}
				@Override
				protected boolean shouldOutput() {
					return taskName!=null && taskName.equals(CCorePlugin.getResourceString("pdom.indexer.task")); //$NON-NLS-1$
				}
			}
			@Override
			public IProgressMonitor createMonitor(Job job) {
				return new IndexingStreamProgressMonitor(System.out);
			}
			@Override
			public IProgressMonitor createMonitor(Job job,
					IProgressMonitor group, int ticks) {
				return new NullProgressMonitor();
			}
			@Override
			public IProgressMonitor createProgressGroup() {
				return new NullProgressMonitor();
			}
		};
		Job.getJobManager().setProgressProvider(pp);
	}

	static class StreamProgressMonitor implements IProgressMonitor {
		volatile boolean canceled;
		volatile int totalWork;
		volatile double worked;
		final PrintStream writer;
		volatile String taskName, subTask;
		Object mutex = new Object();

		StreamProgressMonitor(PrintStream writer) {
			this.writer = writer;
			this.totalWork = -1;
		}

		protected boolean shouldOutput() {
			return true;
		}

		public void done() {
		}
		public void worked(int work) {
			internalWorked(work);
		}
		public void beginTask(String name, int total) {
			this.taskName = name;
			this.totalWork = total;
		}
		public void internalWorked(double work) {
			synchronized(mutex) {
				worked += work;
				int pc = totalWork<1 ? 0 : (int) ((worked*100D)/totalWork);
				if(shouldOutput()) {
					writer.println(pc+"% "+subTask);  //$NON-NLS-1$
				}
			}
		}

		public boolean isCanceled() {
			return canceled;
		}

		public void setCanceled(boolean value) {
			canceled = value;
		}
		public void setTaskName(String name) {
			taskName = name;
		}
		public void subTask(String name) {
			subTask = name;
		}
	}	
}
