/*******************************************************************************
 * Copyright (c) 2007, 2008 Symbian Software Limited 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:
 * Bala Torati (Symbian) - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.templateengine.process;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.templateengine.TemplateCore;
import org.eclipse.cdt.core.templateengine.TemplateEngine;
import org.w3c.dom.Element;

/**
 * ProcessArgument class responsible for constructing process Arguments  by taking info from Template.
 */
public class ProcessArgument {
	
	static final String ELEM_NAME = "name"; //$NON-NLS-1$
	private static final String ELEM_VALUE = "value"; //$NON-NLS-1$
	private static final String ELEM_ELEMENT = "element"; //$NON-NLS-1$
	private static final String ELEM_SIMPLE = "simple"; //$NON-NLS-1$
	private static final String ELEM_SIMPLE_ARRAY = "simple-array"; //$NON-NLS-1$
	private static final String ELEM_COMPLEX = "complex"; //$NON-NLS-1$
	private static final String ELEM_COMPLEX_ARRAY = "complex-array"; //$NON-NLS-1$
	
	private String name;
	private byte type;
	
	private String simpleValue;
	private String[] simpleValueArray;
	private ProcessArgument[] complexValue;
	private ProcessArgument[][] complexValueArray;
	
	private String resolvedSimpleValue;
	private String[] resolvedSimpleValueArray;
	
	private TemplateCore template;
	
	private Set<String> macros;
	private boolean resolved;
	private ProcessParameter externalParam;
	
	/**
	 * constructor
	 * @param template
	 * @param elem
	 */
	public ProcessArgument(TemplateCore template, Element elem) {
		this.template = template;
		this.name = elem.getAttribute(ELEM_NAME);
		String elemName = elem.getNodeName();
		if (elemName.equals(ELEM_SIMPLE)) {
			type = ProcessParameter.SIMPLE;
			simpleValue = elem.getAttribute(ELEM_VALUE);
			collectMacros(simpleValue);
		} else if (elemName.equals(ELEM_SIMPLE_ARRAY)) {
			type = ProcessParameter.SIMPLE_ARRAY;
			List<Element> valueElements = TemplateEngine.getChildrenOfElementByTag(elem, ELEM_ELEMENT);
			simpleValueArray = new String[valueElements.size()];
			for (int i = 0, l = valueElements.size(); i < l; i++) {
				simpleValueArray[i] = (valueElements.get(i)).getAttribute(ELEM_VALUE);
				collectMacros(simpleValueArray[i]);
			}
		} else if (elemName.equals(ELEM_COMPLEX)) {
			type = ProcessParameter.COMPLEX;
			List<Element> children = TemplateEngine.getChildrenOfElement(elem);
			complexValue = new ProcessArgument[children.size()];
			for (int i = 0, l = children.size(); i < l; i++) {
				complexValue[i] = new ProcessArgument(template, children.get(i));
				Set<String> subMacros = complexValue[i].getMacros();
				if (macros == null) {
					macros = new HashSet<String>();
				}
				if (subMacros != null) {
					macros.addAll(subMacros);
				}
			}
		} else if (elemName.equals(ELEM_COMPLEX_ARRAY)) {
			type = ProcessParameter.COMPLEX_ARRAY;
			List<Element> valueElements = TemplateEngine.getChildrenOfElementByTag(elem, ELEM_ELEMENT);
			complexValueArray = new ProcessArgument[valueElements.size()][];
			
			for (int i = 0, l = valueElements.size(); i < l; i++) {
				List<Element> children = TemplateEngine.getChildrenOfElement(valueElements.get(i));
				complexValueArray[i] = new ProcessArgument[children.size()];
				for (int j = 0, l2 = children.size(); j < l2; j++) {
					complexValueArray[i][j] = new ProcessArgument(template, children.get(j));
					Set<String> subMacros = complexValueArray[i][j].getMacros();
					if (subMacros != null) {
						if (macros == null) {
							macros = new HashSet<String>();
						}
						macros.addAll(subMacros);
					}
				}
			}
		}
	}
	
	/**
	 * Creates an <i>external</i> argument. This is not read from the template descriptor. 
	 * @param param The ProcessParameter whose replacement this argument is in the Process call 
	 */
	public ProcessArgument(TemplateCore template, ProcessParameter param) {
		this.template = template;
		name = param.getName();
		type = param.getType();
		macros = new HashSet<String>();
		macros.add(name);
		simpleValue = ProcessHelper.getReplaceMarker(name);
		this.externalParam = param;
	}

	/**
	 * Adds the macros based on the value.
	 * @param value
	 */
	private void collectMacros(String value) {
		if (value == null) {
			return;
		}
		if (macros == null) {
			macros = new HashSet<String>();
		}
		macros.addAll(ProcessHelper.getReplaceKeys(value));
	}

	/**
	 * Returns Parameter name.
	 * @return   parameter name as String
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Returns the Parameter Type
	 * @return the Parmeter Type as String
	 */
	public byte getParameterType() {
		return type;
	}
	
	/**
	 * Returns the Simple Value.
	 * @return   String,
	 */
	public String getSimpleValue() {
		return resolved ? resolvedSimpleValue : simpleValue;
	}
	
	/**
	 * Returns the Simple Array Values.
	 * @return String Array.
	 */
	public String[] getSimpleArrayValue() {
		return resolved ? resolvedSimpleValueArray : simpleValueArray;
	}
	
	/**
	 * Returns Process Arguments
	 */
	public ProcessArgument[] getComplexValue() {
		return complexValue;
	}
	
	/**
	 * Returns Process Arguments
	 */
	public ProcessArgument[][] getComplexArrayValue() {
		return complexValueArray;
	}
	
	/**
	 * Check for parameter type.
	 * @param param
	 * @return boolean
	 */
	public boolean isOfParameterType(ProcessParameter param) {
		if (param.getType() != type || !param.getName().equals(name)) {
			return false;
		}
		switch (type) {
			case ProcessParameter.SIMPLE:
				return simpleValue != null || param.isNullable(); 
			case ProcessParameter.SIMPLE_ARRAY:
				return true;
			case ProcessParameter.COMPLEX:
				ProcessParameter[] params = param.getComplexChildren();
				if (params.length != complexValue.length) {
					return false;
				}
				for (int i = 0; i < complexValue.length; i++) {
					if (!complexValue[i].isOfParameterType(params[i])) {
						return false;
					}
				}
				return true;
			case ProcessParameter.COMPLEX_ARRAY:
				params = param.getComplexChildren();
				for(int i=0; i<complexValueArray.length; i++) {
					ProcessArgument[] cValue = complexValueArray[i];
					if (params.length != cValue.length) {
						return false;
					}
					for (int j = 0; j < cValue.length; j++) {
						if (!cValue[j].isOfParameterType(params[j])) {
							return false;
						}
					}
				}
				return true;
		}
		return false;
	}

	/**
	 * @return true if All macros are Expandable.
	 */
	public boolean areAllMacrosExpandable() {
		switch (type) {
			case ProcessParameter.SIMPLE:
			case ProcessParameter.SIMPLE_ARRAY:
				// the code for these cases is in this form as
				// fall-through generates warning, with no way to
		        // suppress pre-java 1.5
				if (type==ProcessParameter.SIMPLE && externalParam != null) {
					return externalParam.isNullable() || template.getValueStore().get(name) != null;
				}
				if (macros == null || macros.size() == 0) {
					return true;
				}
				Map<String, String> valueStore = template.getValueStore();
				for(String macro : macros) {
					if (valueStore.get(macro) == null) {
						return false;
					}
				}
				return true;
			case ProcessParameter.COMPLEX:
				for(int i=0; i<complexValue.length; i++) {
					ProcessArgument arg = complexValue[i];
					if (!arg.areAllMacrosExpandable()) {
						return false;
					}
				}
				return true;
			case ProcessParameter.COMPLEX_ARRAY:
				for(int i=0; i<complexValueArray.length; i++) {
					ProcessArgument[] cValue =complexValueArray[i];
					for(int j=0; j<cValue.length; j++) {
						ProcessArgument arg = cValue[j];
						if (!arg.areAllMacrosExpandable()) {
							return false;
						}
					}
				}
				return true;
		}
		return true;
	}

	/**
	 * Returns the First Non-expandable Macro.
	 */
	public String getFirstNonExpandableMacro() {
		switch (type) {
			case ProcessParameter.SIMPLE:
			case ProcessParameter.SIMPLE_ARRAY:
				if (macros == null || macros.size() == 0) {
					return null;
				}
				Map<String, String> valueStore = template.getValueStore();
				for(String macro : macros) {
					if (valueStore.get(macro) == null) {
						return macro;
					}
				}
				return null;
			case ProcessParameter.COMPLEX:
				String macro;
				for(int i=0; i<complexValue.length; i++) {
					ProcessArgument arg = complexValue[i];
					if ((macro = arg.getFirstNonExpandableMacro()) != null) {
						return macro;
					}
				}
				return null;
			case ProcessParameter.COMPLEX_ARRAY:
				for(int i=0; i<complexValueArray.length; i++) {
					ProcessArgument[] cValue =complexValueArray[i];
					for(int j=0; j<cValue.length; j++) {
						ProcessArgument arg = cValue[j];
						if ((macro = arg.getFirstNonExpandableMacro()) != null) {
							return macro;
						}
					}
				}
				return null;
		}
		return null;
	}

	/**
	 * @return the macros defined in the context of this argument
	 */
	public Set<String> getMacros() {
		return macros;
	}
	
	/**
	 * resolve
	 *
	 */
	public void resolve() {
		Map<String, String> valueStore = template.getValueStore();
		switch (type) {
			case ProcessParameter.SIMPLE:
				if (externalParam != null) {
					resolvedSimpleValue = template.getValueStore().get(name);
				} else {
					resolvedSimpleValue = simpleValue;
					if (macros != null && !macros.isEmpty()) {
						resolvedSimpleValue = ProcessHelper.getValueAfterExpandingMacros(resolvedSimpleValue, macros, valueStore);
					}
				}
				break;
			case ProcessParameter.SIMPLE_ARRAY:
				resolvedSimpleValueArray = simpleValueArray;
				if (macros != null && !macros.isEmpty()) {
					for (int i = 0; i < resolvedSimpleValueArray.length; i++) {
						resolvedSimpleValueArray[i] = ProcessHelper.getValueAfterExpandingMacros(resolvedSimpleValueArray[i], macros, valueStore);
					}
				}
				break;
			case ProcessParameter.COMPLEX:
				for(int i=0; i<complexValue.length; i++) {
					ProcessArgument arg = complexValue[i];
					arg.resolve();
				}
				break;
			case ProcessParameter.COMPLEX_ARRAY:
				for(int i=0; i<complexValueArray.length; i++) {
					ProcessArgument[] cValue =complexValueArray[i];
					for(int j=0; j<cValue.length; j++) {
						ProcessArgument arg = cValue[j];
						arg.resolve();
					}
				}
				break;
		}
		resolved = true;
	}
	
	/**
	 * Checks whether the process argument has resolved.
	 * @return  boolean, true if resolved.
	 */
	public boolean isResolved() {
		return resolved;
	}
	
	/*
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuffer b = new StringBuffer(name);
		b.append(":"); //$NON-NLS-1$
		switch (type) {
			case ProcessParameter.SIMPLE:
				return b.append(getSimpleValue()).toString();
			case ProcessParameter.SIMPLE_ARRAY:
				b.append("{"); //$NON-NLS-1$
				String[] strings = getSimpleArrayValue();
				for(int i=0; i<strings.length; i++) {
					b.append(strings[i]).append(", "); //$NON-NLS-1$
				}
				if (b.charAt(b.length() - 1) == ' ') { 
					b.replace(b.length() - 2, b.length(), "}"); //$NON-NLS-1$
				} else {
					b.append("}"); //$NON-NLS-1$
				}
				return b.toString();
			case ProcessParameter.COMPLEX:
				b.append("{"); //$NON-NLS-1$
				ProcessArgument[] args = getComplexValue();
				for(int i=0; i<args.length; i++) {
					ProcessArgument arg = args[i];
					b.append(arg).append(", "); //$NON-NLS-1$
				}
				if (b.charAt(b.length() - 1) == ' ') { 
					b.replace(b.length() - 2, b.length(), "}"); //$NON-NLS-1$
				} else {
					b.append("}"); //$NON-NLS-1$
				}
				return b.toString();
			case ProcessParameter.COMPLEX_ARRAY:
				b.append("{"); //$NON-NLS-1$
				ProcessArgument[][] argssCA = getComplexArrayValue();
				for(int i=0; i<argssCA.length; i++) {
					ProcessArgument[] argsCA = argssCA[i];
					b.append("{"); //$NON-NLS-1$
					for(int j=0; j<argsCA.length; j++) {
						ProcessArgument arg = argsCA[j];
						b.append(arg).append(", "); //$NON-NLS-1$
					}
					if (b.charAt(b.length() - 1) == ' ') { 
						b.replace(b.length() - 2, b.length(), "}, "); //$NON-NLS-1$
					} else {
						b.append("}, "); //$NON-NLS-1$
					}
				}
				if (b.charAt(b.length() - 1) == ' ') { 
					b.replace(b.length() - 2, b.length(), "}"); //$NON-NLS-1$
				} else {
					b.append("}"); //$NON-NLS-1$
				}
				return b.toString();
		}
		return ""; //$NON-NLS-1$
	}
}
