/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
package fr.gouv.culture.sdx.pipeline;

import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.xml.SimpleXPathString;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;

//TODO used interned string comparisons for performance in this class and sub clasess?

public abstract class AbstractNodeBasedTransformation extends AbstractTransformation {
	protected HashMap _attributesToTrack = new HashMap();
	protected HashMap _wildcardNodesToTrack = new HashMap();
	//the below instance variables need to be reset at each pass
	protected String c_uri = "";
	protected String c_loc = "";
	protected String c_raw = "";
	protected Stack c_attributes = new Stack();
	protected SimpleXPathString _xpathString = new SimpleXPathString();

	protected void resetFields() {
		c_uri = "";
		c_loc = "";
		c_raw = "";
		c_attributes = new Stack();
		_xpathString = new SimpleXPathString();
	}

	protected Attributes peekCurrentAttributes() {
		Attributes l_atts = null;
		if (this.c_attributes.size() > 0)
			l_atts = (Attributes) this.c_attributes.peek();
		return l_atts;

	}


	public void configure(Configuration configuration) throws ConfigurationException {
		super.configure(configuration);
		setInternalParameters(super.transParameters);
	}

	protected void setCurrentElementProperties(String uri, String loc, String raw, Attributes atts) {
		this.c_uri = uri;
		this.c_loc = loc;
		this.c_raw = raw;
		if (atts == null) atts = new AttributesImpl();
		this.c_attributes.push(atts);
		if (Utilities.checkString(this.c_uri))
			_xpathString.setUri(this.c_uri);
		if (Utilities.checkString(this.c_loc))
			_xpathString.concatLocalXPath(this.c_loc);
		if (Utilities.checkString(this.c_raw))
			_xpathString.concatQualifiedXPath(this.c_raw);
	}

	protected void resetCurrentElementProperties(String uri, String loc, String raw) {
		_xpathString.removeUri(uri);
		_xpathString.trimLocalXPath(loc);
		_xpathString.trimQualifiedXPath(raw);
		this.c_uri = "";
		this.c_loc = "";
		this.c_raw = "";
		if (this.c_attributes.size() > 0)
			this.c_attributes.pop();
	}


	protected void setInternalParameters(Parameters params) {
		super.transParameters = params;
		setNodesToTrack(params);
	}

	protected void setNodesToTrack(Parameters params) {
		String[] paramNames = params.getNames();
		for (int i = 0; i < paramNames.length; i++) {
			String paramName = paramNames[i];
			if (paramName.indexOf("/@") > -1 || paramName.indexOf("@") == 0 && paramName.indexOf("/*/") == -1 && !paramName.startsWith("//"))
				_attributesToTrack.put(paramName, params.getParameter(paramName, ""));
			else if (paramName.startsWith("//") || paramName.indexOf("/*/") > 0)
				_wildcardNodesToTrack.put(paramName, params.getParameter(paramName, ""));
		}
	}

	protected boolean nodeIsParameter(String uri, String loc, String raw, Attributes atts) {
		boolean l_nodeIsParam = false;
		if (Utilities.checkString(elementIsParameter(uri, loc, raw, atts)))
			l_nodeIsParam = true;
		if (!l_nodeIsParam && Utilities.checkString(attributeIsParameter(uri, loc, raw, atts)))
			l_nodeIsParam = true;//we have an attribute name
		return l_nodeIsParam;
	}

	protected String elementIsParameter(String uri, String loc, String raw, Attributes atts) {
		String l_elementName = null;
		if (super.transParameters != null && (loc != null || uri != null)) {
			String l_mappedUri = "";
			String l_prefix = "";
			int l_colonIdx = raw.indexOf(":");
			if (l_colonIdx < 0)
				l_colonIdx = raw.length();
			l_prefix = raw.substring(0, l_colonIdx);
			String l_elemNameFromWildcard = matchesWildcardElement(loc, raw);
			//checking if their is a wildcard pattern for this node that is not an attribute
			if (Utilities.checkString(l_elemNameFromWildcard) && l_elemNameFromWildcard.indexOf("@") == -1) {
				l_elementName = l_elemNameFromWildcard;
				l_mappedUri = super.transParameters.getParameter(l_elemNameFromWildcard, l_mappedUri);
				if (Utilities.checkString(l_mappedUri) && !l_mappedUri.equals(uri))
					l_elementName = null;
			}
			//checking if the local name is specified as a parameter
			else if (super.transParameters.isParameter(loc)) {
				l_elementName = loc;
				l_mappedUri = super.transParameters.getParameter(loc, l_mappedUri);
				if (Utilities.checkString(l_mappedUri) && !l_mappedUri.equals(uri))
					l_elementName = null;
			}
			//checking if the qualified name is specified as a parameter
			else if (super.transParameters.isParameter(raw)) {
				l_elementName = raw;
				l_mappedUri = super.transParameters.getParameter(raw, l_mappedUri);
				if (Utilities.checkString(l_mappedUri) && !l_mappedUri.equals(uri))
					l_elementName = null;
			}
			//checking if the simple xpath local name is specified as a parameter
			else if (super.transParameters.isParameter(_xpathString.getLocalXPath())) {
				l_elementName = _xpathString.getLocalXPath();
				l_mappedUri = super.transParameters.getParameter(_xpathString.getLocalXPath(), l_mappedUri);
				if (Utilities.checkString(l_mappedUri) && !l_mappedUri.equals(uri))
					l_elementName = null;
			}
			//checking if the simple xpath qualified name is specified as a parameter
			else if (super.transParameters.isParameter(_xpathString.getQualifiedXPath())) {
				l_elementName = _xpathString.getQualifiedXPath();
				l_mappedUri = super.transParameters.getParameter(_xpathString.getQualifiedXPath(), l_mappedUri);
				if (Utilities.checkString(l_mappedUri) && !l_mappedUri.equals(uri))
					l_elementName = null;
			}
			//checking if the prefix is specified as a parameter
			else if (super.transParameters.isParameter(l_prefix)) {
				l_elementName = "ns-prefix : " + l_prefix;
				l_mappedUri = super.transParameters.getParameter(l_prefix, "");
				if (Utilities.checkString(l_mappedUri) && !l_mappedUri.equals(uri))
					l_elementName = null;
			}
			//checking if the uri is specified as a parameter
			else if (super.transParameters.isParameter(uri)) {
				l_elementName = "ns-uri : " + uri;
				String l_mappedPrefix = super.transParameters.getParameter(uri, "");
				if (Utilities.checkString(l_mappedPrefix) && !l_mappedPrefix.equals(l_prefix))
					l_elementName = null;
			}
		}
		return l_elementName;
	}

	protected String attributeIsParameter(String uri, String loc, String raw, Attributes atts) {
		if (_attributesToTrack == null && _wildcardNodesToTrack == null) return null;
		if (_attributesToTrack.size() == 0 && _wildcardNodesToTrack.size() == 0) return null;
		String l_attributName = null;

		String l_attNameFromWildcard = matchesWildcardAttribute(loc, raw);
		if (Utilities.checkString(l_attNameFromWildcard)) {
			String l_mappedAttValue = (String) _wildcardNodesToTrack.get(l_attNameFromWildcard);
			String l_attName = l_attNameFromWildcard.substring(l_attNameFromWildcard.indexOf("@") + 1, l_attNameFromWildcard.length());
			String l_attValue = atts.getValue(l_attName);
			if (l_attValue != null) {
				l_attributName = l_attName;
				if (Utilities.checkString(l_mappedAttValue) && !l_mappedAttValue.equals(l_attValue))
					l_attributName = null;
			}
		}

		if (l_attributName != null) return l_attributName;

		//evaulating attributes for the following xpath cases:
		Iterator l_attributesToEvaluate = _attributesToTrack.keySet().iterator();
		while (l_attributesToEvaluate.hasNext()) {
			//the key is the representation of the attribute node
			String l_attributeKey = (String) l_attributesToEvaluate.next();
			//getting any specifically mapped value
			String l_mappedAttValue = (String) _attributesToTrack.get(l_attributeKey);
			//getting the actual attribute name from the key
			String l_attName = l_attributeKey.substring(l_attributeKey.indexOf("@") + 1, l_attributeKey.length());
			//looking up the attribute value from the provided attributes from the SAX stream
			String l_attValue = atts.getValue(l_attName);
			// Case : /@name
			if (_xpathString.getLocalXPath().equals("/") && l_attributeKey.startsWith("/@")) {
				//attribute of root element
				if (l_attValue != null) {
					l_attributName = l_attName;
					if (Utilities.checkString(l_mappedAttValue) && !l_mappedAttValue.equals(l_attValue))
						l_attributName = null;
				}
			}
			/*Cases:
			* //@name OR @name
			* /root/prefix:elementName/@name
			* /root/elementName/@name
			*/
			else if (l_attributeKey.startsWith("@")
					|| l_attributeKey.startsWith("//@")
					|| l_attributeKey.startsWith(_xpathString.getLocalXPath())
					|| l_attributeKey.startsWith(_xpathString.getQualifiedXPath())) {
				//attribute anywhere in tree
				if (l_attValue != null) {
					l_attributName = l_attName;
					if (Utilities.checkString(l_mappedAttValue) && !l_mappedAttValue.equals(l_attValue))
						l_attributName = null;
				}
			}

			if (l_attributName != null) return l_attributName;

		}
		return l_attributName;

	}

	protected String matchesWildcardElement(String loc, String raw) {
		String l_elementName = null;
		Iterator l_wildcardNamesToEvaluate = _wildcardNodesToTrack.keySet().iterator();
		while (l_wildcardNamesToEvaluate.hasNext()) {
			//take the name
			String l_wildcardNodeExpr = (String) l_wildcardNamesToEvaluate.next();
			if (l_wildcardNodeExpr.indexOf("@") == -1)//take all patterns WITHOUT attribute nodes
				l_elementName = matchesWildcardName(loc, raw, l_wildcardNodeExpr);
			if (l_elementName != null) return l_elementName;
		}
		return l_elementName;

	}

	protected String matchesWildcardAttribute(String loc, String raw) {
		String l_attributeName = null;
		Iterator l_wildcardNamesToEvaluate = _wildcardNodesToTrack.keySet().iterator();
		while (l_wildcardNamesToEvaluate.hasNext()) {
			//take the name
			String l_wildcardNodeExpr = (String) l_wildcardNamesToEvaluate.next();
			if (l_wildcardNodeExpr.indexOf("@") > -1)//take only patterns WITH attribute nodes
				l_attributeName = matchesWildcardName(loc, raw, l_wildcardNodeExpr);
			if (l_attributeName != null) return l_attributeName;
		}
		return l_attributeName;
	}

	protected String matchesWildcardName(String loc, String raw, String wildcardExpr) {
		String l_nodeName = null;
		//take the name
		String l_wildcardNodeExpr = wildcardExpr;
		/*we should handle the following cases
		//@name
		//elementLocalName or //elementQualName
		*/
		String l_wildcardAttrStr = "//@";
		String l_wildcardLocalStr = "//" + loc;
		String l_wildcardQualStr = "//" + raw;
		String l_localXpathStr = _xpathString.getLocalXPath();
		String l_QualXpathStr = _xpathString.getQualifiedXPath();

		if (l_wildcardNodeExpr.startsWith(l_wildcardAttrStr))
			return l_wildcardNodeExpr;
		else if (l_wildcardNodeExpr.equals(l_wildcardLocalStr))
			return l_wildcardNodeExpr;
		else if (l_wildcardNodeExpr.equals(l_wildcardQualStr))
			return l_wildcardNodeExpr;
		else if (l_wildcardNodeExpr.indexOf("/*/") > 0) {
			if (wildCardExprMatchesElementXpathString(l_wildcardNodeExpr, l_localXpathStr))
				return l_wildcardNodeExpr;
			else if (wildCardExprMatchesElementXpathString(l_wildcardNodeExpr, l_QualXpathStr))
				return l_wildcardNodeExpr;
		}

		return l_nodeName;
	}

	protected boolean wildCardExprMatchesElementXpathString(String wildcardExpr, String xpathStr) {
		//elementLocalName/"*"/@name //elementQualName/"*"/@name
		//elementLocalXPath/"*"/@name //elementQualXPath/"*"/@name
		String l_wildcardExpr = wildcardExpr;
		if (l_wildcardExpr.startsWith("//"))
			l_wildcardExpr = l_wildcardExpr.substring(1);//removing first forward slash for analysis

		String l_wildcardExprNoAttr = null;
		int attIdx = l_wildcardExpr.indexOf("@");
		if (attIdx > -1)
			l_wildcardExprNoAttr = l_wildcardExpr.substring(0, attIdx);

		if (wildcardExpr.startsWith("//") && xpathStr.indexOf(l_wildcardExprNoAttr) > -1)
			return true;

		String l_exprBeforeAsterix = l_wildcardExprNoAttr.substring(0, l_wildcardExprNoAttr.indexOf("/*/"));
		String l_exprAfterAsterix = l_wildcardExprNoAttr.substring(l_wildcardExprNoAttr.indexOf("/*/") + 3, l_wildcardExpr.length());
		if (xpathStr.indexOf(l_exprBeforeAsterix) == 0 &&
				xpathStr.indexOf(l_exprAfterAsterix) == (l_exprBeforeAsterix + "/*/").length())
			return true;

		return false;
	}

	public void recycle() {
		super.recycle();
		this.resetFields();
	}


}
