/*******************************************************************************
 * Copyright (c) 2005, 2008 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.cdtvariables;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.cdt.core.cdtvariables.CdtVariableException;
import org.eclipse.cdt.core.cdtvariables.ICdtVariable;
import org.eclipse.cdt.core.cdtvariables.IStorableCdtVariables;
import org.eclipse.cdt.core.settings.model.ICStorageElement;
import org.eclipse.cdt.internal.core.cdtvariables.UserDefinedVariableSupplier.VarKey;
import org.eclipse.cdt.internal.core.settings.model.ExceptionFactory;
import org.eclipse.cdt.utils.cdtvariables.CdtVariableResolver;

/**
 * This class represents the set of Build Macros that could be loaded
 * and stored in XML
 * 
 * @since 3.0
 *
 */
public class StorableCdtVariables implements IStorableCdtVariables {
	public static final String MACROS_ELEMENT_NAME = "macros"; //$NON-NLS-1$
	private HashMap<String, ICdtVariable> fMacros;
	private boolean fIsDirty = false;
	private boolean fIsChanged = false;
	private boolean fIsReadOnly;
	
	private HashMap<String, ICdtVariable> getMap(){
		if(fMacros == null)
			fMacros = new HashMap<String, ICdtVariable>();
		return fMacros;
	}
	
	public StorableCdtVariables(boolean readOnly) {
		fIsReadOnly = readOnly;
	}

	@SuppressWarnings("unchecked")
	public StorableCdtVariables(StorableCdtVariables base, boolean readOnly) {
		fMacros = (HashMap<String, ICdtVariable>)base.getMap().clone();
		fIsReadOnly = readOnly;
	}

	public StorableCdtVariables(ICdtVariable vars[], boolean readOnly) {
		fMacros = new HashMap<String, ICdtVariable>(vars.length);
		for (ICdtVariable var : vars) {
			addMacro(var);
		}
		fIsReadOnly = readOnly;
	}

	public StorableCdtVariables(ICStorageElement element, boolean readOnly) {
		load(element);
		fIsReadOnly = readOnly;
	}
	
	private void load(ICStorageElement element){
//		fExpandInMakefile = TRUE.equals(element.getAttribute(EXPAND_ENVIRONMENT_MACROS));
		
		ICStorageElement nodeList[] = element.getChildren();
		for (int i = 0; i < nodeList.length; ++i) {
			ICStorageElement node = nodeList[i];
			String name = node.getName(); 
			if (StorableCdtVariable.STRING_MACRO_ELEMENT_NAME.equals(name)) {
				addMacro(new StorableCdtVariable(node));
			}
			else if (StorableCdtVariable.STRINGLIST_MACRO_ELEMENT_NAME.equals(name)) {
				addMacro(new StorableCdtVariable(node));
			}
		}
		fIsDirty = false;
		fIsChanged = false;
	}
	
	public void serialize(ICStorageElement element){
		if(fMacros != null){
			for (ICdtVariable v : fMacros.values()){
				StorableCdtVariable macro = (StorableCdtVariable)v;
				ICStorageElement macroEl;
				if(CdtVariableResolver.isStringListVariable(macro.getValueType()))
					macroEl = element.createChild(StorableCdtVariable.STRINGLIST_MACRO_ELEMENT_NAME);
				else
					macroEl = element.createChild(StorableCdtVariable.STRING_MACRO_ELEMENT_NAME);
				macro.serialize(macroEl);
			}
		}
		fIsDirty = false;
	}

	private void addMacro(ICdtVariable macro){
		String name = macro.getName();
		if(name == null)
			return;
		
		getMap().put(name,macro);
	}

	public ICdtVariable createMacro(String name, int type, String value){
		if(name == null || "".equals(name = name.trim()) || CdtVariableResolver.isStringListVariable(type)) //$NON-NLS-1$
			return null;

		ICdtVariable macro = checkMacro(name, type, value);
		if(macro == null){
			macro = new StorableCdtVariable(name, type, value);
			addMacro(macro);
			fIsDirty = true;
			fIsChanged = true;
		}
		return macro;
	}
	
	public ICdtVariable checkMacro(String name, int type, String value){
		if(fIsReadOnly)
			throw ExceptionFactory.createIsReadOnlyException();
		ICdtVariable macro = getMacro(name);
		if(macro != null){
			if(macro.getName().equals(name)
					&& macro.getValueType() == type){
				try {
					String val = macro.getStringValue();
					if((val != null
								&& val.equals(value))
							|| val == value){
						return macro;
					}
				} catch (CdtVariableException e) {
				}
			}
		}
		return null;
	}
	
	public ICdtVariable checkMacro(String name, int type, String value[]){
		if(fIsReadOnly)
			throw ExceptionFactory.createIsReadOnlyException();
		ICdtVariable macro = getMacro(name);
		if(macro != null){
			if(macro.getName().equals(name)
					&& macro.getValueType() == type){
				try {
					String val[] = macro.getStringListValue();
					if(val != null){
						if(value != null && value.length == val.length){
							int i;
							for(i = 0; i < val.length; i++){
								if(!value[i].equals(val[i]))
									break;
							}
							if(i == value.length)
								return macro;
						}
					} else if (value == val){
						return macro;
					}
				} catch (CdtVariableException e) {
				}
			}
		}
		return null;
	}
	
	/*
	 * sets the storable macros to hold the geven number of macros
	 * all macros that are present in the store but not included in the given array
	 * will be removed 
	 */
	public void setMacros(ICdtVariable macros[]){
		if(fIsReadOnly)
			throw ExceptionFactory.createIsReadOnlyException();
		if(macros == null || macros.length == 0)
			deleteAll();
		else{
			if (getMap().size() != 0) {
				for (ICdtVariable m : getMap().values()){
					int i;
					for(i = 0 ; i < macros.length; i++){
						if(m.getName().equals(macros[i].getName()))
							break;
					}
					if(i == macros.length)
						deleteMacro(m.getName());
				}
			}
			createMacros(macros);
		}
	}
	
	public void createMacros(ICdtVariable macros[]){
		if(fIsReadOnly)
			throw ExceptionFactory.createIsReadOnlyException();
		for (ICdtVariable macro : macros) {
			createMacro(macro);
		}
	}
	
	public boolean isEmpty(){
		return fMacros == null || fMacros.isEmpty();
	}

	public ICdtVariable createMacro(ICdtVariable copy){
		if(fIsReadOnly)
			throw ExceptionFactory.createIsReadOnlyException();
		String name = copy.getName();
		if(name == null || "".equals(name = name.trim())) //$NON-NLS-1$
			return null;

		int type = copy.getValueType();
		
		ICdtVariable macro = null;
		try{
			if(CdtVariableResolver.isStringListVariable(type)){
				String value[] = copy.getStringListValue();
				macro = checkMacro(name, type, value);
				if(macro == null){
					macro = new StorableCdtVariable(name, type, value);
					addMacro(macro);
					fIsDirty = true;
					fIsChanged = true;
				}
			}
			else {
				String value = copy.getStringValue();
				macro = checkMacro(name, type, value);
				if(macro == null){
					macro = new StorableCdtVariable(name, type, value);
					addMacro(macro);
					fIsDirty = true;
					fIsChanged = true;
				}
			}

		}catch(CdtVariableException e){
		}
		return macro;
	}

	public ICdtVariable createMacro(String name, int type, String value[]){
		if(name == null || "".equals(name = name.trim()) || !CdtVariableResolver.isStringListVariable(type)) //$NON-NLS-1$
			return null;

		ICdtVariable macro = checkMacro(name, type, value);
		if(macro == null){
			macro = new StorableCdtVariable(name, type, value);
			addMacro(macro);
			fIsDirty = true;
			fIsChanged = true;
		}
		return macro;
	}

	/**
	 * Returns the "dirty" state for this set of macros.
	 * If the dirty state is <code>true</code>, that means that the macros 
	 * is out of synch with the repository and the macros need to be serialized.
	 * <br><br>
	 * The dirty state is automatically set to <code>false</code> when the macros are serialized
	 * by calling the serialize() method  
	 * @return boolean 
	 */
	public boolean isDirty(){
		return fIsDirty;
	}
	
	/**
	 * sets the "dirty" state for this set of macros.
	 * @see org.eclipse.cdt.internal.core.cdtvariables.StorableCdtVariables#isDirty()
	 * @param dirty represents the new state
	 */
	public void setDirty(boolean dirty){
		fIsDirty = dirty;
	}
	
	/**
	 * Returns the "change" state for this set of macros.
	 * The "change" state represents whether the macros were changed or not.
	 * This state is not reset when the serialize() method is called
	 * Users can use this state to monitor whether the macros were changed or not.
	 * The "change" state can be reset only by calling the setChanged(false) method 
	 * @return boolean
	 */
	public boolean isChanged(){
		return fIsChanged;
	}
	
/*	public boolean isExpanded(){
		return fExpandInMakefile;
	}
*/	
/*	public void setExpanded(boolean expand){
		if(fExpandInMakefile != expand){
			fExpandInMakefile = expand;
			fIsDirty = true;
			//should we set the change state here?
			fIsChanged = true;
		}
	}
*/	
	/**
	 * sets the "change" state for this set of macros.
	 * @see org.eclipse.cdt.internal.core.cdtvariables.StorableCdtVariables#isChanged()
	 * @param changed represents the new "change" state
	 */
	public void setChanged(boolean changed){
		fIsChanged = changed;
	}

	public ICdtVariable getMacro(String name){
		if(name == null || "".equals(name = name.trim())) //$NON-NLS-1$
			return null;
		
		ICdtVariable var = getMap().get(name);
		if(var == null){
			int indx = name.indexOf(':');
			if(indx != -1){
				String baseName = name.substring(0, indx);
				ICdtVariable tmp = getMap().get(baseName);
				if(tmp != null 
						&& CdtVariableManager.getDefault().toEclipseVariable(tmp, null) != null){
					var = EclipseVariablesVariableSupplier.getInstance().getVariable(name);
				}
			}
		}
		return var;
	}
	
	public ICdtVariable[] getMacros(){
		Collection<ICdtVariable> macros = getMap().values();		
		return macros.toArray(new ICdtVariable[macros.size()]);
	}
	
	public ICdtVariable deleteMacro(String name){
		if(fIsReadOnly)
			throw ExceptionFactory.createIsReadOnlyException();

		if(name == null || "".equals(name = name.trim())) //$NON-NLS-1$
			return null;

		ICdtVariable macro = getMap().remove(name);
		if(macro != null){
			fIsDirty = true;
			fIsChanged = true;
		}

		return macro;
	}
	
	public boolean deleteAll(){
		if(fIsReadOnly)
			throw ExceptionFactory.createIsReadOnlyException();
		Map<String, ICdtVariable> map = getMap();
		if(map.size() > 0){
			fIsDirty = true;
			fIsChanged = true;
			map.clear();
			return true;
		}
		return false;
	}
	
	public boolean contains(ICdtVariable var){
		ICdtVariable curVar = getMacro(var.getName());
		if(curVar == null)
			return false;
		
		if(new VarKey(curVar, false).equals(new VarKey(var, false)))
			return true;
		
		return false;

	}
}
