/*******************************************************************************
 * Copyright (c) 2005, 2007 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.internal.core.envvar;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.eclipse.cdt.core.cdtvariables.CdtVariableException;
import org.eclipse.cdt.core.cdtvariables.ICdtVariable;
import org.eclipse.cdt.core.envvar.EnvironmentVariable;
import org.eclipse.cdt.core.envvar.IContributedEnvironment;
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.envvar.IEnvironmentVariableManager;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.internal.core.cdtvariables.DefaultVariableContextInfo;
import org.eclipse.cdt.internal.core.cdtvariables.EnvironmentVariableSupplier;
import org.eclipse.cdt.internal.core.cdtvariables.ICoreVariableContextInfo;
import org.eclipse.cdt.utils.cdtvariables.ICdtVariableSupplier;
import org.eclipse.cdt.utils.cdtvariables.IVariableContextInfo;
import org.eclipse.cdt.utils.cdtvariables.IVariableSubstitutor;
import org.eclipse.cdt.utils.cdtvariables.SupplierBasedCdtVariableSubstitutor;
import org.eclipse.cdt.utils.envvar.EnvVarOperationProcessor;


/**
 * This class implements the IEnvironmentVariableProvider interface and provides all
 * build environment funvtionality to the MBS
 * 
 * @since 3.0
 *
 */
public class EnvironmentVariableManager implements
		IEnvironmentVariableManager {

	private static final String DELIMITER_WIN32 = ";";  //$NON-NLS-1$
	private static final String DELIMITER_UNIX = ":";  //$NON-NLS-1$
	
	private static EnvironmentVariableManager fInstance = null;

//	private EnvVarVariableSubstitutor fVariableSubstitutor;
	
	public static final UserDefinedEnvironmentSupplier fUserSupplier = new UserDefinedEnvironmentSupplier();
	public static final BuildSystemEnvironmentSupplier fExternalSupplier = new BuildSystemEnvironmentSupplier();
	public static final EclipseEnvironmentSupplier fEclipseSupplier = new EclipseEnvironmentSupplier();
	
	private ContributedEnvironment fContributedEnvironment;

	public class EnvVarVariableSubstitutor extends SupplierBasedCdtVariableSubstitutor {
		private String fDefaultDelimiter;
/*		public EnvVarMacroSubstitutor(int contextType, Object contextData, String inexistentMacroValue, String listDelimiter){
			super(contextType,contextData,inexistentMacroValue,listDelimiter);
			fDefaultDelimiter = listDelimiter;
		}
*/
		public EnvVarVariableSubstitutor(IVariableContextInfo contextInfo, String inexistentMacroValue, String listDelimiter){
			super(contextInfo, inexistentMacroValue, listDelimiter, null ,inexistentMacroValue);
			fDefaultDelimiter = listDelimiter;
		}
		
		public IEnvironmentVariable resolveVariable(EnvVarDescriptor var) throws CdtVariableException {
			String value;
			if(var == null || (value = var.getValue()) == null || value.length() == 0 || var.getOperation() == IEnvironmentVariable.ENVVAR_REMOVE)
				return var;

			String listDelimiter = var.getDelimiter();
			if(listDelimiter == null)
				listDelimiter = fDefaultDelimiter;
			setListDelimiter(listDelimiter);
			ICdtVariable macro = EnvironmentVariableSupplier.getInstance().createBuildMacro(var);
			IVariableContextInfo varMacroInfo = getVarMacroContextInfo(var);
			int varSupplierNum = getVarMacroSupplierNum(var,varMacroInfo);
			value = resolveToString(new MacroDescriptor(macro,varMacroInfo,varSupplierNum));
			removeResolvedMacro(var.getName());
			return new EnvironmentVariable(var.getName(),value,var.getOperation(),var.getDelimiter());
		}
		
		protected IVariableContextInfo getVarMacroContextInfo(EnvVarDescriptor var){
			IEnvironmentContextInfo info = var.getContextInfo();
			if(info != null)
				return getMacroContextInfoForContext(info.getContext());
			return null;
		}
		
		protected int getVarMacroSupplierNum(EnvVarDescriptor var, IVariableContextInfo varMacroInfo){
			int varSupplierNum = -1;
			ICdtVariableSupplier macroSuppliers[] = varMacroInfo.getSuppliers();
			for(int i = 0; i < macroSuppliers.length; i++){
				if(macroSuppliers[i] instanceof EnvironmentVariableSupplier){
					varSupplierNum = i;
					break;
				}
			}
			return varSupplierNum;
		}
	}

	protected EnvironmentVariableManager(){
		fContributedEnvironment = new ContributedEnvironment(this);
	}

	public static EnvironmentVariableManager getDefault(){
		if(fInstance == null)
			fInstance = new EnvironmentVariableManager();
		return fInstance;
	}
	
	/*
	 * returns a variable of a given name or null
	 * the context information is taken from the contextInfo passed
	 * @see org.eclipse.cdt.managedbuilder.internal.envvar.IContextInfo
	 */
	public static EnvVarDescriptor getVariable(String variableName,
			IEnvironmentContextInfo contextInfo, boolean includeParentLevels){

		if(contextInfo == null)
			return null;
		if((variableName = EnvVarOperationProcessor.normalizeName(variableName)) == null) 
			return null;


		IEnvironmentContextInfo infos[] = getAllContextInfos(contextInfo);
		
		if(!includeParentLevels){
			ICoreEnvironmentVariableSupplier suppliers[] = infos[0].getSuppliers();
			boolean bVarFound = false;
			for (ICoreEnvironmentVariableSupplier supplier : suppliers) {
				if(supplier.getVariable(variableName,infos[0].getContext()) != null){
					bVarFound = true;
					break;
				}
			}
			if(!bVarFound)
				return null;
		}

		IEnvironmentVariable variable = null;
		IEnvironmentContextInfo varContextInfo = null;
		int varSupplierNum = -1;
		ICoreEnvironmentVariableSupplier varSupplier = null;
		
		for(int i = infos.length-1 ; i >=0 ; i-- ) {
			IEnvironmentContextInfo info = infos[i];
			ICoreEnvironmentVariableSupplier suppliers[] = info.getSuppliers();
			
			for(int j = suppliers.length-1 ; j >= 0 ; j-- ) {
				ICoreEnvironmentVariableSupplier supplier = suppliers[j];
				IEnvironmentVariable var = supplier.getVariable(variableName,info.getContext());
				
				if(var == null)
					continue;
				
				varContextInfo = info;
				varSupplierNum = j;
				varSupplier = supplier;
					
				if(variable == null)
					variable = var;
				else
					variable = EnvVarOperationProcessor.performOperation(variable,var);
			}
		}
	
		if(variable != null){
//			if(variable.getOperation() == IEnvironmentVariable.ENVVAR_REMOVE)
//				return null;
			return new EnvVarDescriptor(variable,varContextInfo,varSupplierNum,varSupplier);
		}
		return null;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.cdt.managedbuilder.envvar.IEnvironmentVariableProvider#getVariable()
	 */
	public IEnvironmentVariable getVariable(String variableName,
			ICConfigurationDescription cfg, boolean resolveMacros) {
		if(variableName == null || "".equals(variableName)) //$NON-NLS-1$
			return null;
		
		IEnvironmentContextInfo info = getContextInfo(cfg);
		EnvVarDescriptor var = getVariable(variableName,info,true);
		
		if(var != null && var.getOperation() != IEnvironmentVariable.ENVVAR_REMOVE){
			return resolveMacros ? calculateResolvedVariable(var,info) : var;
		}
		return null;
	}
	
	IEnvironmentContextInfo getDefaultContextInfo(Object level){
		DefaultEnvironmentContextInfo info = new DefaultEnvironmentContextInfo(level);
		if(info.getSuppliers() == null)
			return null;
		return info;
	}
	
	/*
	 * returns the context info that should be used for the given level
	 * or null if the the given level is not supported
	 */
	public IEnvironmentContextInfo getContextInfo(Object level){
		if(level instanceof ICConfigurationDescription)
			return fContributedEnvironment.appendEnvironment((ICConfigurationDescription)level) ?
					getDefaultContextInfo(level) : fContributedEnvironment.getContextInfo(level);
		return getDefaultContextInfo(level);
	}
	
	/*
	 * returns a list of defined variables.
	 * the context information is taken from the contextInfo passed
	 * @see org.eclipse.cdt.managedbuilder.internal.envvar.IContextInfo
	 */
	public static EnvVarCollector getVariables(IEnvironmentContextInfo contextInfo,
			boolean includeParentLevels) {
		if(contextInfo == null)
			return null;
		
		IEnvironmentContextInfo infos[] = getAllContextInfos(contextInfo);
		HashSet<String> set = null;
		
		if(!includeParentLevels){
			ICoreEnvironmentVariableSupplier suppliers[] = infos[0].getSuppliers();
			set = new HashSet<String>();
			for(int i = 0; i < suppliers.length; i++){
				IEnvironmentVariable vars[] = suppliers[i].getVariables(infos[0].getContext());
				if(vars != null){
					for (IEnvironmentVariable var : vars) {
						String name = EnvVarOperationProcessor.normalizeName(var.
								getName());
						if(name != null)
							set.add(name);
					}
				}
				if(!suppliers[i].appendEnvironment(infos[0].getContext()))
					break;
			}
			if(set.size() == 0)
				return new EnvVarCollector();
		}
		
		EnvVarCollector envVarSet = new EnvVarCollector();
		
		for(int i = infos.length-1 ; i >=0 ; i-- ) {
			IEnvironmentContextInfo info = infos[i];
			ICoreEnvironmentVariableSupplier suppliers[] = info.getSuppliers();
			
			for(int j = suppliers.length-1 ; j >= 0 ; j-- ) {
				ICoreEnvironmentVariableSupplier supplier = suppliers[j];
				if(!supplier.appendEnvironment(info.getContext())){
					envVarSet.clear();
				}

				IEnvironmentVariable vars[] = null;
				if(set != null){
					List<IEnvironmentVariable> varList = new ArrayList<IEnvironmentVariable>();
					Iterator<String> iter = set.iterator();
					
					while(iter.hasNext()){
						IEnvironmentVariable var = supplier.getVariable(iter.next(),info.getContext());
						if(var != null)
							varList.add(var);
					}
					vars = varList.toArray(new IEnvironmentVariable[varList.size()]);
				}
				else{
					 vars = supplier.getVariables(info.getContext());
				}
				envVarSet.add(vars,info,j, supplier);
			}
		}

		return envVarSet;		
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.managedbuilder.envvar.IEnvironmentVariableProvider#getVariables()
	 */
	public IEnvironmentVariable[] getVariables(ICConfigurationDescription cfg, boolean resolveMacros) {
		
		
		IEnvironmentContextInfo info = getContextInfo(cfg);
		EnvVarCollector varSet = getVariables(info,true);
		
		EnvVarDescriptor vars[] = varSet != null ? varSet.toArray(false) : null;

		if(vars != null){
			if(!resolveMacros)
				return vars;
			
			IEnvironmentVariable resolved[] = new IEnvironmentVariable[vars.length];
			for(int i = 0; i < vars.length; i++)
				resolved[i] = calculateResolvedVariable(vars[i], info);
			return resolved;
		}
		return new EnvVarDescriptor[0];
	}
	
	/*
	 * returns an array of the IContextInfo that holds the context informations
	 * starting from the one passed to this method and including all subsequent parents
	 */
	public static IEnvironmentContextInfo[] getAllContextInfos(IEnvironmentContextInfo contextInfo){
		if(contextInfo == null)
			return null;
			
		List<IEnvironmentContextInfo> list = new ArrayList<IEnvironmentContextInfo>();
	
		list.add(contextInfo);
			
		while((contextInfo = contextInfo.getNext()) != null)
			list.add(contextInfo);
		
		return list.toArray(new IEnvironmentContextInfo[list.size()]);
	}
	
	private boolean isWin32(){
		String os = System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$
		if (os.startsWith("windows ")) //$NON-NLS-1$
			return true;
		return false;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.cdt.managedbuilder.envvar.IEnvironmentVariableProvider#getDefaultDelimiter()
	 */
	public String getDefaultDelimiter() {
		return isWin32() ? DELIMITER_WIN32 : DELIMITER_UNIX;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.managedbuilder.envvar.IEnvironmentVariableProvider#isVariableCaseSensitive()
	 */
	public boolean isVariableCaseSensitive() {
		return !isWin32();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.cdt.managedbuilder.envvar.IEnvironmentVariableProvider#getSuppliers()
	 */
	public ICoreEnvironmentVariableSupplier[] getSuppliers(Object level) {
		IEnvironmentContextInfo info = getContextInfo(level);
		if(info != null)
			return info.getSuppliers();
		return null;
	}
	
	/*
	 * returns true if the first passed contextInfo is the child of the second one
	 */
	public boolean checkParentContextRelation(IEnvironmentContextInfo child, IEnvironmentContextInfo parent){
		if(child == null || parent == null)
			return false;

		IEnvironmentContextInfo enumInfo = child;
		do{
			if(parent.getContext() == enumInfo.getContext())
				return true;
		}while((enumInfo = enumInfo.getNext()) != null);
		return false;
	}
	
	public IEnvironmentVariable calculateResolvedVariable(EnvVarDescriptor des, IEnvironmentContextInfo info){
		if(des == null || info == null)
			return null;

		return calculateResolvedVariable(des,getVariableSubstitutor(getMacroContextInfoForContext(info.getContext()),""," ")); //$NON-NLS-1$ //$NON-NLS-2$ 
	}

	public IEnvironmentVariable calculateResolvedVariable(EnvVarDescriptor des, IVariableSubstitutor sub){
		if(des == null)
			return null;
		IEnvironmentVariable var = des;

		try{
			if(sub instanceof EnvVarVariableSubstitutor)
				var = ((EnvVarVariableSubstitutor)sub).resolveVariable(des);
			else if(des.getOperation() != IEnvironmentVariable.ENVVAR_REMOVE){
				String name = des.getName();
				var = new EnvironmentVariable(name,sub.resolveToString(name),des.getOperation(),des.getDelimiter());
			}
		} catch (CdtVariableException e){
		}
		return var;
		
	}
	
	protected int getMacroContextTypeFromContext(Object context){
		if(context instanceof ICConfigurationDescription)
			return ICoreVariableContextInfo.CONTEXT_CONFIGURATION;
		else
			return ICoreVariableContextInfo.CONTEXT_WORKSPACE;
	}
	
	public ICoreVariableContextInfo getMacroContextInfoForContext(Object context){
		return new DefaultVariableContextInfo(getMacroContextTypeFromContext(context),context);
	}
	
	public IVariableSubstitutor getVariableSubstitutor(IVariableContextInfo info, String inexistentMacroValue, String listDelimiter){
		return new EnvVarVariableSubstitutor(info,inexistentMacroValue,listDelimiter);
//		if(fVariableSubstitutor == null)
//			fVariableSubstitutor = new EnvVarVariableSubstitutor(info,inexistentMacroValue,listDelimiter);
//		else {
//			try {
//				fVariableSubstitutor.setMacroContextInfo(info);
//				fVariableSubstitutor.setInexistentMacroValue(inexistentMacroValue);
//				fVariableSubstitutor.setListDelimiter(listDelimiter);
//			} catch (CdtVariableException e){
//				fVariableSubstitutor = new EnvVarVariableSubstitutor(info,inexistentMacroValue,listDelimiter);
//			}
//		}
//		return fVariableSubstitutor;
	}

	public IContributedEnvironment getContributedEnvironment() {
		return fContributedEnvironment;
	}
	
	
}
