/*
 * Created on Apr 10, 2005
 *
 */
package edu.uga.cs.lsdis.meteors.wadls.util;

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

import javax.wadls.Grammars;
import javax.wadls.Params;
import javax.wadls.WADLSException;
import javax.wadls.extensions.schema.Schema;
import javax.xml.namespace.QName;

import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import edu.uga.cs.lsdis.meteors.wadls.Constants;
import edu.uga.cs.lsdis.meteors.wadls.extensions.schema.SchemaConstants;
import edu.uga.cs.lsdis.meteors.wadls.util.xml.QNameUtils;
import edu.uga.cs.lsdis.meteors.wadls.util.xml.XPathUtils;

/**
 * This file is a collection of utilities which find the location of a XML/XSD Element by using the given path.
 * @author Zixin Wu
 *
 */

public class SchemaUtils {
	
	/**
	 * Search an XML element in the schemas contained in the given Types.
	 * @param types	Search in the schemas contained in this WSDLS Types.
	 * @param tagName XML Element name of the desired element.
	 * @param elementName The value of the attribute "name" of the desired element. 
	 * @return An XML element whose XML Element name is "tagName" and whose attribute "name" is "elementName".
	 * @throws WADLSException 
	 */
	public static Element findXMLEleInSchemas(Grammars grammars, String tagName, QName elementName) throws WADLSException{
		Map schemas = grammars.getSchemas();
		Iterator it = schemas.values().iterator();
		while(it.hasNext()){
			Schema schema = (Schema)(it.next());
			Element schemaEle = schema.getElement();
			if (schemaEle.getAttribute(Constants.ATTR_TARGET_NAMESPACE).equals(elementName.getNamespaceURI())){
				Element foundEle = findXMLEleByName(schemaEle, tagName, elementName);
				if (foundEle != null)
					return foundEle;
			}
		}
		return null;
	}
	
	/**
	 * Search the element whose XML Element name is "tagName" and whose attribute "name" is "elementName",
	 *  in the startElement, search in only one level depth.
	 * @param startElement Start searching from this XML Element.
	 * @param tagName XML Element name of the desired element.
	 * @param elementName The value of the attribute "name" of the desired element. 
	 * @return An XML element whose XML Element name is "tagName" and whose attribute "name" is "elementName".
	 * @throws WADLSException
	 */
	public static Element findXMLEleByName(Element startElement, String tagName, QName elementName) throws WADLSException{
		NodeList nodeList = startElement.getChildNodes();
		//find the element with the required name as an attribute
		int listLength = nodeList.getLength();
		for(int i=0;i<listLength;i++){
			Node currentNode = nodeList.item(i);
			if (currentNode.getNodeType() != Node.ELEMENT_NODE) continue;
			Element currentElement = (Element)nodeList.item(i);
			//get the "name" attribute
			String nameAttr = currentElement.getAttribute(SchemaConstants.ATTR_NAME);
			QName qNameAttr = QNameUtils.getQName(nameAttr, currentElement);
			//compare both namespaceURI and localname of the "name" attribute
			if (elementName == null || qNameAttr.equals(elementName)){
				//compare localname of the tag, and check namespace of the tag is XSD
				QName currentTagQName = QNameUtils.getQName(currentElement.getTagName(), currentElement);
				if (SchemaConstants.XSD_STR_LIST.contains(currentTagQName.getNamespaceURI())
						&& tagName.equals(currentTagQName.getLocalPart())){
					//this is the element we are looking for.
					return currentElement;
				}
			}
		}
		return null;
	}
	
	
	/**
	 * Return a list of XSD elements contained in the startElement (XSD Element), search in only 1 level depth.
	 * @param startElement Start search from this XSD element.
	 * @param types WSDLS Types
	 * @return A list of XSD elements contained in the startElement. If no XSD element is contained, the list has 0 element.
	 * @throws WADLSException
	 */
	public static List listXSDElesInEle(Element startElement, Grammars grammars) throws WADLSException{
		String refAttr = startElement.getAttribute(SchemaConstants.ATTR_REF);
		if (refAttr != ""){
			//look for the referenced element
			QName elementQName = QNameUtils.getQName(refAttr, startElement);
			Element refenecedElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_ELEMENT, elementQName);
			if (refenecedElement != null)
				return listXSDElesInEle(refenecedElement, grammars);
			else{
				throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find referenced element");
			}
		}
		else{
			Element complexElement = null;
			String typeAttr = startElement.getAttribute(SchemaConstants.ATTR_TYPE);
			if (typeAttr != ""){
				//get the QName of typeAttr
				QName typeQName = QNameUtils.getQName(typeAttr, startElement);
				if (SchemaConstants.XSD_STR_LIST.contains(typeQName.getNamespaceURI()))
					//primitive data type
					return new ArrayList();
				else{
					//ComplexType or SimpleType
					complexElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_COMPLEXTYPE, typeQName);
					if (complexElement == null)
						//simpleType
						return new ArrayList();;
				}
			}
			else{
				//No type, look for complexType in the subNodes
				complexElement = findXMLEleByName(startElement, SchemaConstants.ELEM_COMPLEXTYPE, null);
				if (complexElement == null){
					WADLSException wsdlsExc = new WADLSException(WADLSException.NOT_FOUND_ELE_BY_PATH, 
							"cannot find complexType matching the path");
					wsdlsExc.setLocation(XPathUtils.getXPathExprFromNode(startElement));
					throw wsdlsExc;
				}
			}
			
			//we got the complexType element so far.
			//we need to collect all the elements
			return listXSDElesInComplexType(complexElement);
		}
	}
	
	public static List listXSDElesInEle(Element startElement, Params params) throws WADLSException{
		/*String refAttr = startElement.getAttribute(SchemaConstants.ATTR_REF);
		if (refAttr != ""){
			//look for the referenced element
			QName elementQName = QNameUtils.getQName(refAttr, startElement);
			Element refenecedElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_ELEMENT, elementQName);
			if (refenecedElement != null)
				return listXSDElesInEle(refenecedElement, grammars);
			else{
				throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find referenced element");
			}
		}
		else{
			Element complexElement = null;
			String typeAttr = startElement.getAttribute(SchemaConstants.ATTR_TYPE);
			if (typeAttr != ""){
				//get the QName of typeAttr
				QName typeQName = QNameUtils.getQName(typeAttr, startElement);
				if (SchemaConstants.XSD_STR_LIST.contains(typeQName.getNamespaceURI()))
					//primitive data type
					return new ArrayList();
				else{
					//ComplexType or SimpleType
					complexElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_COMPLEXTYPE, typeQName);
					if (complexElement == null)
						//simpleType
						return new ArrayList();;
				}
			}
			else{
				//No type, look for complexType in the subNodes
				complexElement = findXMLEleByName(startElement, SchemaConstants.ELEM_COMPLEXTYPE, null);
				if (complexElement == null){
					WADLSException wsdlsExc = new WADLSException(WADLSException.NOT_FOUND_ELE_BY_PATH, 
							"cannot find complexType matching the path");
					wsdlsExc.setLocation(XPathUtils.getXPathExprFromNode(startElement));
					throw wsdlsExc;
				}
			}
			
			//we got the complexType element so far.
			//we need to collect all the elements
			return listXSDElesInComplexType(complexElement);
		}*/
		
		List temp = null;
		temp.set(1, startElement);
		return temp;
		
	}
	
	/**
	 * Return a list of XSD elements contained in the complexElement (XSD complexType), search in only 1 level depth.
	 * @param complexElement Start search from this XSD complexType.
	 * @return A list of XSD elements contained in the startElement. If no XSD element is contained, the list has 0 element.
	 * @throws WADLSException
	 */
	public static List listXSDElesInComplexType(Element complexElement) throws WADLSException{
		List subElements = new ArrayList();
		
		NodeList subNodes = complexElement.getChildNodes();
		int length = subNodes.getLength();
		for(int i=0;i<length;i++){
			Node currentNode = subNodes.item(i);
			if (currentNode.getNodeType() != Node.ELEMENT_NODE) continue;
			Element currentElement = (Element)currentNode;
			String tagName = currentElement.getTagName();
			QName tagQName = QNameUtils.getQName(tagName, currentElement);
			String localPart = tagQName.getLocalPart();
			if (!SchemaConstants.XSD_STR_LIST.contains(tagQName.getNamespaceURI()))
				continue;
			if (localPart.equals(SchemaConstants.ELEM_ELEMENT))
				subElements.add(currentElement);
			else if
				(localPart.equals(SchemaConstants.ELEM_ALL)
				|| localPart.equals(SchemaConstants.ELEM_SEQUENCE)
				|| localPart.equals(SchemaConstants.ELEM_CHOICE)
				|| localPart.equals(SchemaConstants.ELEM_COMPLEXCONTENT)
				|| localPart.equals(SchemaConstants.ELEM_RESTRICTION)
				)
				//Just skip it, call this method recursively.
				subElements.addAll(listXSDElesInComplexType(currentElement));
		}
		return subElements;
	}
		
	/**
	 * Search an XSD element located by the startElement and path.
	 * @param startElement Start search from this XSD element.
	 * @param path
	 * @param types WSDLS Types
	 * @return An XSD element
	 * @throws WADLSException
	 */
	public static Element findXSDEleOnEle(Element startElement, String path, Grammars grammars) throws WADLSException{
		String refAttr = startElement.getAttribute(SchemaConstants.ATTR_REF);
		if (refAttr != ""){
			//look for the referenced element
			QName elementQName = QNameUtils.getQName(refAttr, startElement);
			Element refenecedElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_ELEMENT, elementQName);
			if (refenecedElement != null)
				return findXSDEleOnEle(refenecedElement, path, grammars);
			else{
				throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find referenced element");
			}
		}
		else{
			if (path == "")
				return startElement;
			
			String typeAttr = startElement.getAttribute(SchemaConstants.ATTR_TYPE);
			if (typeAttr != ""){			//<... type="...">
				//get the QName of typeAttr
				QName typeQName = QNameUtils.getQName(typeAttr, startElement);
				if (SchemaConstants.XSD_STR_LIST.contains(typeQName.getNamespaceURI())){
					//primitive data type
					WADLSException wsdlsExc = new WADLSException(WADLSException.PATH_ERROR, 
							"Primitive type cannot has path");
					wsdlsExc.setLocation(XPathUtils.getXPathExprFromNode(startElement));
					throw wsdlsExc;
				}
				else{
					//find the complexType with the given type QName
					Element complexTypeElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_COMPLEXTYPE, typeQName);
					if (complexTypeElement != null)
						return findXSDEleOnComplexType(complexTypeElement, path, grammars);
					else
					//cannot find in complexType
						throw new DOMException(DOMException.NOT_FOUND_ERR, "cannot find complexType by the given path");
				}
			}
			else{
				//No type, look for complexType in the subNodes
				Element complexElement = findXMLEleByName(startElement, SchemaConstants.ELEM_COMPLEXTYPE, null);
				if (complexElement != null)
					return findXSDEleOnComplexType(complexElement, path, grammars);
				else{
					WADLSException wsdlsExc = new WADLSException(WADLSException.NOT_FOUND_ELE_BY_PATH, 
							"cannot find complexType matching the path");
					wsdlsExc.setLocation(XPathUtils.getXPathExprFromNode(startElement));
					throw wsdlsExc;
				}
			}
		}
	}
	
	public static Element findXSDEleOnEle(Element startElement, String path, Params params) throws WADLSException{
		/*String refAttr = startElement.getAttribute(SchemaConstants.ATTR_REF);
		if (refAttr != ""){
			//look for the referenced element
			QName elementQName = QNameUtils.getQName(refAttr, startElement);
			Element refenecedElement = findXMLEleInSchemas(params, SchemaConstants.ELEM_ELEMENT, elementQName);
			if (refenecedElement != null)
				return findXSDEleOnEle(refenecedElement, path, grammars);
			else{
				throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find referenced element");
			}
		}
		else{
			if (path == "")
				return startElement;
			
			String typeAttr = startElement.getAttribute(SchemaConstants.ATTR_TYPE);
			if (typeAttr != ""){			//<... type="...">
				//get the QName of typeAttr
				QName typeQName = QNameUtils.getQName(typeAttr, startElement);
				if (SchemaConstants.XSD_STR_LIST.contains(typeQName.getNamespaceURI())){
					//primitive data type
					WADLSException wsdlsExc = new WADLSException(WADLSException.PATH_ERROR, 
							"Primitive type cannot has path");
					wsdlsExc.setLocation(XPathUtils.getXPathExprFromNode(startElement));
					throw wsdlsExc;
				}
				else{
					//find the complexType with the given type QName
					Element complexTypeElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_COMPLEXTYPE, typeQName);
					if (complexTypeElement != null)
						return findXSDEleOnComplexType(complexTypeElement, path, grammars);
					else
					//cannot find in complexType
						throw new DOMException(DOMException.NOT_FOUND_ERR, "cannot find complexType by the given path");
				}
			}
			else{
				//No type, look for complexType in the subNodes
				Element complexElement = findXMLEleByName(startElement, SchemaConstants.ELEM_COMPLEXTYPE, null);
				if (complexElement != null)
					return findXSDEleOnComplexType(complexElement, path, grammars);
				else{
					WADLSException wsdlsExc = new WADLSException(WADLSException.NOT_FOUND_ELE_BY_PATH, 
							"cannot find complexType matching the path");
					wsdlsExc.setLocation(XPathUtils.getXPathExprFromNode(startElement));
					throw wsdlsExc;
				}
			}
		}*/
		return startElement;
	}

	/**
	 * Search an XSD Element from the XSD complexType.
	 * @param complexTypeElement Start searching from this XSD ComplexType
	 * @param path
	 * @param types	WSDLS Types
	 * @return An XSD Element
	 * @throws WADLSException
	 */
	public static Element findXSDEleOnComplexType(Element complexTypeElement, String path, Grammars grammars) throws WADLSException{
		if (path == null || path == "")
			return null;
		//look for the matching element.
		String splitedPath[] = splitPath(path);
		String elementName = splitedPath[0];
		path = splitedPath[1];
		Element targetElement = findXSDEleInComplexType(complexTypeElement, elementName, grammars);
		if (targetElement != null)
			return findXSDEleOnEle(targetElement, path, grammars);
		else{
			WADLSException wsdlsExc = new WADLSException(WADLSException.NOT_FOUND_ELE_BY_PATH, 
			"Cannot find the element matching the path");
			wsdlsExc.setLocation(XPathUtils.getXPathExprFromNode(complexTypeElement));
			throw wsdlsExc;
		}
	}
	
	public static Element findXSDEleOnComplexType(Element complexTypeElement, String path, Params params) throws WADLSException{
		/*if (path == null || path == "")
			return null;
		//look for the matching element.
		String splitedPath[] = splitPath(path);
		String elementName = splitedPath[0];
		path = splitedPath[1];
		Element targetElement = findXSDEleInComplexType(complexTypeElement, elementName, grammars);
		if (targetElement != null)
			return findXSDEleOnEle(targetElement, path, grammars);
		else{
			WADLSException wsdlsExc = new WADLSException(WADLSException.NOT_FOUND_ELE_BY_PATH, 
			"Cannot find the element matching the path");
			wsdlsExc.setLocation(XPathUtils.getXPathExprFromNode(complexTypeElement));
			throw wsdlsExc;
		}*/
		return complexTypeElement;
	}

	/**
	 * Search an XSD Element recursively inside of the complexType.
	 * @param startElement
	 * @param elementName The desired attribute "name"
	 * @param types WSDL Types
	 * @return A XSD Element
	 * @throws WADLSException
	 */
	public static Element findXSDEleInComplexType(Element startElement, String elementName, Grammars grammars) throws WADLSException{
		NodeList subNodes = startElement.getChildNodes();
		int length = subNodes.getLength();
		for(int i=0;i<length;i++){
			Node currentNode = subNodes.item(i);
			if (currentNode.getNodeType() != Node.ELEMENT_NODE) continue;
			Element currentElement = (Element)currentNode;
			String tagName = currentElement.getTagName();
			QName tagQName = QNameUtils.getQName(tagName, currentElement);
			String localPart = tagQName.getLocalPart();
			if (!SchemaConstants.XSD_STR_LIST.contains(tagQName.getNamespaceURI()))
				continue;
			if (localPart.equals(SchemaConstants.ELEM_ELEMENT)){
				String refAttr = currentElement.getAttribute(SchemaConstants.ATTR_REF);
				if (refAttr != ""){
					if (refAttr.equals(elementName)){	//TODO need to compare the namespaces
						//look for the referenced element
						QName elementQName = QNameUtils.getQName(refAttr, currentElement);
						Element refenecedElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_ELEMENT, elementQName);
						return refenecedElement;
					}
				}
				else{
					String nameAttr = currentElement.getAttribute(SchemaConstants.ATTR_NAME);
//					if (nameAttr == "") continue;
					if (nameAttr.equals(elementName))	//TODO need to compare the namespaces
						//this is the element we are looking for.
						return currentElement;
				}
			}
			else if
				(localPart.equals(SchemaConstants.ELEM_ALL)
				|| localPart.equals(SchemaConstants.ELEM_SEQUENCE)
				|| localPart.equals(SchemaConstants.ELEM_CHOICE)
				|| localPart.equals(SchemaConstants.ELEM_COMPLEXCONTENT)
				|| localPart.equals(SchemaConstants.ELEM_RESTRICTION)
				){
				//Just skip it, call this method recursively.
				return findXSDEleInComplexType(currentElement, elementName, grammars);
			}
			else if (localPart.equals(SchemaConstants.ELEM_EXTENSION)){
				//try to find it inside the <xsd:extension>
				Element returnElement = findXSDEleInComplexType(currentElement, elementName, grammars);
				if (returnElement != null)
					return returnElement;
				else{
					String baseAttr = currentElement.getAttribute(SchemaConstants.ATTR_BASE);
					QName baseQName = QNameUtils.getQName(baseAttr, currentElement);
					Element baseElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_COMPLEXTYPE, baseQName);
					return findXSDEleInComplexType(baseElement, elementName, grammars);
				}
			}
			else if (localPart.equals(SchemaConstants.ELEM_GROUP)){
				//find <group>.
				String refAttr = currentElement.getAttribute(SchemaConstants.ATTR_REF);
				if (refAttr != ""){
					//look for the referenced <group>
					QName groupQName = QNameUtils.getQName(refAttr, currentElement);
					Element groupElement = findXMLEleInSchemas(grammars, SchemaConstants.ELEM_GROUP, groupQName);
					if (groupElement != null){
						//try to find it in the referenced <group>
						Element returnElement = findXSDEleInComplexType(groupElement, elementName, grammars);
						if (returnElement != null)
							return returnElement;
						else
							//Not in the referenced <group>
							continue;
					}
					else
						throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find the referenced group");
				}
				else
					//Just skip it, call this method recursively.
					return findXSDEleInComplexType(currentElement, elementName, grammars);
			}
		}
		return null;
	}

	/**
	 * return the first schema in the given extensible elements.
	 * @param extElements Search the first schema in this extensible elements.
	 * @return The first schema in the given extensible elements.
	 * @throws WADLSException
	 */
	public static Schema getFirstSchema(List extElements){
		if (extElements == null)
			return null;
		Object schemaObj = extElements.iterator().next();
		if (schemaObj == null)
			return null;
	/*	ExtensibilityElement schemaEE = (ExtensibilityElement) schemaObj;
		if (SchemaConstants.XSD_QNAME_LIST.contains(schemaEE.getElementType()))
			return (Schema)schemaObj;
		else*/
			return null;
	}
	
	/**
	 * return all schemas in the given extensible elements.
	 * @param extElements Search the first schema in this extensible elements.
	 * @return All schemas in the given extensible elements.
	 * @throws WADLSException
	 */
	public static List getSchemas(List extElements){
		List schemas = new ArrayList();
		if (extElements != null){
			Iterator it = extElements.iterator();
			while(it.hasNext()){
				Object schemaObj = it.next();
				if (schemaObj == null)
					continue;
				/*ExtensibilityElement schemaEE = (ExtensibilityElement) schemaObj;
				if (SchemaConstants.XSD_QNAME_LIST.contains(schemaEE.getElementType()))
					schemas.add((Schema)schemaObj);*/
			}
		}
		return schemas;
	}

	/**
	 * This method split the path string, return a string array, 
	 * whose first dim is the string before the first '/' ,
	 * and the second dim is the remaining string.
	 * @param path The path to be splitted.
	 * @return The splitted string.
	 */
	public static String[] splitPath(String path){
		String returnStr[] = new String[2];	//the string before '/'
		int slashPosition = path.indexOf('/');
		if (slashPosition == -1){
			//no deeper path
			returnStr[0] = path;
			returnStr[1] = "";
		}
		else{
			returnStr[0] = path.substring(0, slashPosition);
			returnStr[1] = path.substring(slashPosition+1);
			//if the remaining path is "/", empty it.
			if (returnStr[1] == "/") returnStr[1] = "";
		}
		return returnStr;
	}	
	
}
