/*******************************************************************************
 * Copyright (c) 2004, 2010 Intel 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:
 * Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.projectconverter;


import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.eclipse.cdt.managedbuilder.core.BuildException;
import org.eclipse.cdt.managedbuilder.core.IBuilder;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IConfigurationV2;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.IManagedProject;
import org.eclipse.cdt.managedbuilder.core.IOption;
import org.eclipse.cdt.managedbuilder.core.IProjectType;
import org.eclipse.cdt.managedbuilder.core.ITarget;
import org.eclipse.cdt.managedbuilder.core.ITargetPlatform;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.IToolChain;
import org.eclipse.cdt.managedbuilder.core.IToolReference;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin;
import org.eclipse.cdt.managedbuilder.internal.core.Builder;
import org.eclipse.cdt.managedbuilder.internal.core.ManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.internal.core.Tool;
import org.eclipse.cdt.managedbuilder.internal.core.ToolChain;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @noextend This class is not intended to be subclassed by clients.
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
class UpdateManagedProject20 {
	private static final String ID_SEPARATOR = ".";	//$NON-NLS-1$
	
	/**
	 * @param monitor the monitor to allow users to cancel the long-running operation
	 * @param project the <code>IProject</code> that needs to be upgraded
	 */
	static void doProjectUpdate(IProgressMonitor monitor, final IProject project) throws CoreException {
		String[] projectName = new String[]{project.getName()};
		IFile file = project.getFile(ManagedBuildManager.SETTINGS_FILE_NAME);
		File settingsFile = file.getLocation().toFile();
		if (!settingsFile.exists()) {
			monitor.done();
			return;
		}
		
		// Backup the file
		monitor.beginTask(ConverterMessages.getFormattedString("UpdateManagedProject20.0", projectName), 1); //$NON-NLS-1$
		IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(project);
		UpdateManagedProjectManager.backupFile(file, "_20backup", monitor, project); //$NON-NLS-1$
		
		try {
			// Load the old build file
			InputStream stream = new FileInputStream(settingsFile);
			DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document document = parser.parse(stream);
			
			// Clone the target based on the proper target definition
			NodeList targetNodes = document.getElementsByTagName(ITarget.TARGET_ELEMENT_NAME);
			// This is a guess, but typically the project has 1 target, 2 configs, and 6 tool defs
			int listSize = targetNodes.getLength();
			monitor.beginTask(ConverterMessages.getFormattedString("UpdateManagedProject20.1", projectName), listSize * 9); //$NON-NLS-1$
			for (int targIndex = 0; targIndex < listSize; ++targIndex) {
				Element oldTarget = (Element) targetNodes.item(targIndex);
				String oldTargetId = oldTarget.getAttribute(ITarget.ID);
				IManagedProject newProject = convertTarget(project, oldTarget, monitor);
			
				// Remove the old target
				if (newProject != null) {
					info.removeTarget(oldTargetId);
					monitor.worked(9);
				}
			}
			// Upgrade the version
			((ManagedBuildInfo)info).setVersion("2.1.0"); //$NON-NLS-1$
			info.setValid(true);
		}catch (CoreException e){
			throw e;
		}catch (Exception e) {
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
					e.getMessage(), e));
		} finally {
			// If the tree is locked spawn a job to this.
			IWorkspace workspace = project.getWorkspace();
//			boolean treeLock = workspace.isTreeLocked();
			ISchedulingRule rule = workspace.getRuleFactory().createRule(project);
//			if (treeLock) {
			//since the java synchronized mechanism is now used for the build info loadding,
			//initiate the job in all cases
				WorkspaceJob job = new WorkspaceJob(ConverterMessages.getResourceString("UpdateManagedProject.notice")) { //$NON-NLS-1$
					@Override
					public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
						ManagedBuildManager.saveBuildInfoLegacy(project, false);
						return Status.OK_STATUS;
					}
				};
				job.setRule(rule);
				job.schedule();
//			} else {
//				ManagedBuildManager.saveBuildInfo(project, false);
//			}
			monitor.done();
		}

	}
	
	protected static IManagedProject convertTarget(IProject project, Element oldTarget, IProgressMonitor monitor)
						throws CoreException{
		// What we want to create
		IManagedProject newProject = null;
		IProjectType newParent = null;
		
		// Get the parent
		String parentID = oldTarget.getAttribute(ITarget.PARENT);
		
		String targetID = oldTarget.getAttribute(ITarget.ID);
	
		// Get the new target definitions we need for the conversion
		newParent = ManagedBuildManager.getProjectType(parentID);
		
		if (newParent == null) {
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
					ConverterMessages.getFormattedString("UpdateManagedProject20.9",parentID), null)); //$NON-NLS-1$
		}

		try {
			// Create a new ManagedProject based on the new parent
			newProject = ManagedBuildManager.createManagedProject(project, newParent);
			
			// Create new configurations
			NodeList configNodes = oldTarget.getElementsByTagName(IConfigurationV2.CONFIGURATION_ELEMENT_NAME);
			for (int configIndex = 0; configIndex < configNodes.getLength(); ++configIndex) {
				try{
					convertConfiguration(newProject, newParent, (Element) configNodes.item(configIndex), monitor);
				}
				catch(CoreException e){
					//TODO: implement logging					
					//should we continue or fail ??
				}
			}
			
			IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(project);
			IConfiguration[] newConfigs = newProject.getConfigurations();
			if (newConfigs.length > 0) {
				info.setDefaultConfiguration(newConfigs[0]);
				info.setSelectedConfiguration(newConfigs[0]);
			}
			else{
				throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
						ConverterMessages.getFormattedString("UpdateManagedProject20.10",newProject.getName()), null)); //$NON-NLS-1$
			}
		} catch (BuildException e) {
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
					ConverterMessages.getFormattedString("UpdateManagedProject20.11",new String[]{project.getName(),e.getMessage()}), null)); //$NON-NLS-1$
		}
		
		monitor.worked(1);
		return newProject;
	}

	protected static void convertConfiguration(IManagedProject newProject, IProjectType newParent, Element oldConfig, IProgressMonitor monitor) 
								throws CoreException {
		IConfiguration newParentConfig = null;
		IConfiguration newConfig = null;

		// Figure out what the original parent of the config is
		String parentId = oldConfig.getAttribute(IConfigurationV2.PARENT);

		newParentConfig = newParent.getConfiguration(parentId);
		if (newParentConfig == null) {
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
					ConverterMessages.getFormattedString("UpdateManagedProject20.2", parentId), null)); //$NON-NLS-1$
		}		
		// Generate a random number for the new config id
		int randomElement = ManagedBuildManager.getRandomNumber();
		String newConfigId = parentId + ID_SEPARATOR + randomElement;
		// Create the new configuration
		newConfig = newProject.createConfiguration(newParentConfig, newConfigId);

		if(oldConfig.hasAttribute(IConfigurationV2.NAME))
			newConfig.setName(oldConfig.getAttribute(IConfigurationV2.NAME));

		Element targetEl = (Element)oldConfig.getParentNode();

		if(targetEl.hasAttribute(ITarget.ARTIFACT_NAME))
			newConfig.setArtifactName(targetEl.getAttribute(ITarget.ARTIFACT_NAME));

		if(targetEl.hasAttribute(ITarget.ERROR_PARSERS))
			newConfig.setErrorParserIds(targetEl.getAttribute(ITarget.ERROR_PARSERS));

		if(targetEl.hasAttribute(ITarget.CLEAN_COMMAND))
			newConfig.setCleanCommand(targetEl.getAttribute(ITarget.CLEAN_COMMAND));

		if(targetEl.hasAttribute(ITarget.EXTENSION))
			newConfig.setArtifactExtension(targetEl.getAttribute(ITarget.EXTENSION));
		
		// Convert the tool references
		
		IToolChain toolChain = newConfig.getToolChain();
		((ToolChain)toolChain).checkForMigrationSupport();

		if(targetEl.hasAttribute(ITarget.OS_LIST)){
			String oses = targetEl.getAttribute(ITarget.OS_LIST);
			String osList[] = oses.split(","); //$NON-NLS-1$
			for (int i = 0; i < osList.length; ++i) {
				osList[i]=osList[i].trim();
			}
			toolChain.setOSList(osList);
		}
		
		if(targetEl.hasAttribute(ITarget.ARCH_LIST)){
			String archs = targetEl.getAttribute(ITarget.ARCH_LIST);
			String archList[] = archs.split(","); //$NON-NLS-1$
			for (int i = 0; i < archList.length; ++i) {
				archList[i]=archList[i].trim();
			}
			toolChain.setArchList(archList);
		}
		
		if(targetEl.hasAttribute(ITarget.BINARY_PARSER)){
			String binaryParser = targetEl.getAttribute(ITarget.BINARY_PARSER);
			ITargetPlatform targetPlatform = toolChain.getTargetPlatform();
			if(targetPlatform.isExtensionElement()){
				int nnn = ManagedBuildManager.getRandomNumber();
				String subId = targetPlatform.getId() + "." + nnn;		//$NON-NLS-1$
				String builderName = targetPlatform.getName() + "." + newConfig.getName(); 	//$NON-NLS-1$				
				toolChain.createTargetPlatform(targetPlatform,subId,builderName,false);
			}
			targetPlatform.setBinaryParserList(new String[]{binaryParser});  // Older projects have only a single binary parser.
		}
		
		if(targetEl.hasAttribute(ITarget.MAKE_COMMAND)){
			String makeCommand =  targetEl.getAttribute(ITarget.MAKE_COMMAND);
			IBuilder builder = toolChain.getBuilder();
			if (builder.isExtensionElement()) {
				int nnn = ManagedBuildManager.getRandomNumber();
				String subId = builder.getId() + "." + nnn;		//$NON-NLS-1$
				String builderName = builder.getName() + "." + newConfig.getName(); 	//$NON-NLS-1$
				builder = toolChain.createBuilder(builder, subId, builderName, false);
			}
			builder.setCommand(makeCommand);
		}

		if(targetEl.hasAttribute(ITarget.MAKE_ARGS)){
			String makeArguments =  targetEl.getAttribute(ITarget.MAKE_ARGS);
			IBuilder builder = toolChain.getBuilder();
			if (builder.isExtensionElement()) {
				int nnn = ManagedBuildManager.getRandomNumber();
				String subId = builder.getId() + "." + nnn;		//$NON-NLS-1$
				String builderName = builder.getName() + "." + newConfig.getName(); 	//$NON-NLS-1$
				builder = toolChain.createBuilder(builder, subId, builderName, false);
			}
			builder.setArguments(makeArguments);
		}

//		 by now if a builder is going to be created, it will have been
		Builder builder = (Builder)toolChain.getBuilder();
		if (! builder.isExtensionElement()) {
			builder.checkForMigrationSupport();
		}

		NodeList toolRefNodes = oldConfig.getElementsByTagName(IConfigurationV2.TOOLREF_ELEMENT_NAME);
		for (int refIndex = 0; refIndex < toolRefNodes.getLength(); ++refIndex) {
			try{
				convertToolRef(toolChain, (Element) toolRefNodes.item(refIndex), monitor);
			}
			catch(CoreException e){
				newProject.removeConfiguration(newConfigId);
				throw e;
			}
		}

		monitor.worked(1);
	}
	
	protected static void convertToolRef(IToolChain toolChain, Element oldToolRef, IProgressMonitor monitor) 
							throws CoreException {
		if(!oldToolRef.hasAttribute(IToolReference.ID)) {
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
					ConverterMessages.getResourceString("UpdateManagedProject20.3"), null)); //$NON-NLS-1$
		}

		String toolId = oldToolRef.getAttribute(IToolReference.ID);
		IConfiguration configuration = toolChain.getParent();
		
		ITool tools[] = configuration.getTools();
		if(tools == null) {
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
					ConverterMessages.getResourceString("UpdateManagedProject20.4"), null)); //$NON-NLS-1$
		}
		
		ITool tool = null;
		for(int i = 0; i < tools.length; i++){
			ITool curTool = tools[i]; 
			ITool parent = curTool.getSuperClass();
			String curToolId = curTool.getId();
			
			while (parent != null) {
				String parentId = parent.getId();
				if(parentId.equals(toolId))
					break;
				parent = parent.getSuperClass();
			}
			if(parent == null)
				continue;

			try{
				Integer.decode(curToolId.substring(curToolId.lastIndexOf('.')+1));
			}
			catch(IndexOutOfBoundsException e){
				continue;
			}
			catch(NumberFormatException e){
				continue;
			}
			tool = curTool;
			break;
		}

		if(tool == null){
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
					ConverterMessages.getFormattedString("UpdateManagedProject20.5",toolId), null)); //$NON-NLS-1$
		}
			
		// Check for migration support 
		((Tool)tool).checkForMigrationSupport();
		
		//the tool found, proceed with conversion ...

		if(oldToolRef.hasAttribute(IToolReference.COMMAND))
			tool.setToolCommand(oldToolRef.getAttribute(IToolReference.COMMAND));

		if(oldToolRef.hasAttribute(IToolReference.OUTPUT_FLAG))
			tool.setOutputFlag(oldToolRef.getAttribute(IToolReference.OUTPUT_FLAG));

		if(oldToolRef.hasAttribute(IToolReference.OUTPUT_PREFIX))
			tool.setOutputPrefix(oldToolRef.getAttribute(IToolReference.OUTPUT_PREFIX));
			

		if(oldToolRef.hasAttribute(IToolReference.OUTPUTS)){
			String outputs = oldToolRef.getAttribute(IToolReference.OUTPUTS);
			tool.setOutputsAttribute(outputs);
		}

		NodeList optRefs = oldToolRef.getElementsByTagName(ITool.OPTION_REF);
		for (int refIndex = optRefs.getLength() - 1; refIndex >= 0; --refIndex) {
				convertOptionRef(toolChain, tool, (Element) optRefs.item(refIndex), monitor);
		}

		monitor.worked(1);
	}
	
	protected static void convertOptionRef(IToolChain toolChain, ITool tool, Element optRef, IProgressMonitor monitor) 
							throws CoreException {

		if(!optRef.hasAttribute(IOption.ID)){
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
					ConverterMessages.getResourceString("UpdateManagedProject20.6"), null)); //$NON-NLS-1$
		}
		
		String optId = optRef.getAttribute(IOption.ID);
		
		IConfiguration configuration = toolChain.getParent();
		
		IOption options[] = tool.getOptions();		
		IOption option = null;

		for(int i = 0; i < options.length; i++){
			IOption curOption = options[i]; 
			IOption parent = curOption.getSuperClass();
			if(parent == null)
				continue;
			
			for (;parent.getSuperClass()!=null;parent = parent.getSuperClass()) {
				// empty body, the loop is to find superclass
			}
			
			String parentId = parent.getId();
			if(!parentId.equals(optId))
				continue;
				
			option = curOption;
			break;
		}
		
		if(option == null)
			option = tool.getOptionById(optId);
		
		if (option != null) {		//  Ignore options that don't have a match
			try{
				int type = option.getValueType();
					
				switch(type){
					case IOption.BOOLEAN:{
						if(optRef.hasAttribute(IOption.DEFAULT_VALUE)){
							Boolean bool = new Boolean(optRef.getAttribute(IOption.DEFAULT_VALUE));
							configuration.setOption(tool,option,bool.booleanValue());
						}
						break;
					}
				case IOption.ENUMERATED:
				case IOption.TREE:
				case IOption.STRING:{
						if(optRef.hasAttribute(IOption.DEFAULT_VALUE))
							configuration.setOption(tool,option,optRef.getAttribute(IOption.DEFAULT_VALUE));
						break;
					}
				case IOption.STRING_LIST:
				case IOption.INCLUDE_PATH:
				case IOption.PREPROCESSOR_SYMBOLS:
				case IOption.LIBRARIES:
				case IOption.OBJECTS:{
						Vector<String> values = new Vector<String>();
						NodeList nodes = optRef.getElementsByTagName(IOption.LIST_VALUE);
						for (int j = 0; j < nodes.getLength(); ++j) {
							Node node = nodes.item(j);
							if (node.getNodeType() == Node.ELEMENT_NODE) {
								Boolean isBuiltIn = new Boolean(((Element)node).getAttribute(IOption.LIST_ITEM_BUILTIN));
								if (!isBuiltIn.booleanValue()) {
									values.add(((Element)node).getAttribute(IOption.LIST_ITEM_VALUE));
								}
							}
						}
						configuration.setOption(tool,option,values.toArray(new String[values.size()]));
						break;
					}
				default:
					break;
				}
			}
			catch(BuildException e){
				throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(), -1,
						ConverterMessages.getFormattedString("UpdateManagedProject20.8",e.getMessage()), e)); //$NON-NLS-1$
			}
		}
	}
}

