/*******************************************************************************
* Copyright (c) 2006 IONA Technologies PLC
* 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:
*     IONA Technologies PLC - initial API and implementation
*******************************************************************************/

package org.eclipse.stp.sc.xmlvalidator.utils;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
//import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;

public class XMLUtils {

    public static Document loadXmlDocument(InputStream inputStream) throws Exception {
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document doc = builder.parse(inputStream);
        doc.normalize();
        return doc;
    }
    
	public static Document loadXmlDocument(String filePath) throws Exception {
		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
	    Document doc = builder.parse(filePath);
	    doc.normalize();
	    return doc;
	}
	public static List<Element> getElementList(Document doc) {
		assert(doc != null);
		ArrayList<Element> elementList = new ArrayList<Element>();
		NodeList nodeList = doc.getChildNodes();
		if (nodeList != null) {
			for (int i = 0; i < nodeList.getLength(); i ++) {
				getElementList(nodeList.item(i), elementList);
			}
		}
		return elementList;
	}
	public static boolean compareTrees(Document dom1, Document dom2) {
		try {
			compareDocuments(dom1, dom2);
			NodeList nodeList1 = dom1.getChildNodes();
			NodeList nodeList2 = dom2.getChildNodes();
			assertTrue(nodeList1.getLength() == nodeList2.getLength());
			compareNodeLists(nodeList1, nodeList2);
		} catch (AssertionError e) {
			return false;
		}
		return true;
	}
	private static void compareNodes(Node node1, Node node2) {
		if (node1 == null || node2 == null) {
			assertTrue(node1 == null && node2 == null);
		} else {
			compareStrings(node1.getBaseURI(), node2.getBaseURI());
			// in case of namespace and qualified name, no way to set localName
			//! compareStrings(node1.getLocalName(), node2.getLocalName());
			// for the first element, the namespace URI are different:
			// DomLocationParser: http://schemas.xmlsoap.org/wsdl/;
			// DocumentBuilder (standard parser) from JDK: null.
			//! compareStrings(node1.getNamespaceURI(), node2.getNamespaceURI());
			compareStrings(node1.getNodeName(), node2.getNodeName());    
			assertTrue(node1.getNodeType() == node2.getNodeType());    
			compareStrings(node1.getNodeValue(), node2.getNodeValue());
			// for the first element, the node prefix are different:
			// DomLocationParser: wsdl; DocumentBuilder: null.
			//! compareStrings(node1.getPrefix(), node2.getPrefix());
			compareStrings(node1.getTextContent(), node2.getTextContent());
		}
	}
	private static void compareElements(Element element1, Element element2) {
		if (element1 == null || element2 == null) {
			assertTrue(element1 == null && element2 == null);
		} else {
			// no enough data to get the schema type information 
			//! compareTypeInfos(element1.getSchemaTypeInfo(), element2.getSchemaTypeInfo());
			compareStrings(element1.getTagName(), element2.getTagName());
			compareNodes(element1, element2);
		}
	}

	/*
	private static void compareTypeInfos(TypeInfo typeInfo1, TypeInfo typeInfo2) {
		if (typeInfo1 == null || typeInfo2 == null) {
			assertTrue(typeInfo1 == null && typeInfo2 == null);
		} else {
			compareStrings(typeInfo1.getTypeName(), typeInfo2.getTypeName());
			compareStrings(typeInfo1.getTypeNamespace(), typeInfo2.getTypeNamespace());
		}
	}
	*/
	
	private static void compareDocuments(Document document1, Document document2) {
		// no enough data to get the document type information
		//! compareDocumentTypes(document1.getDoctype(), document2.getDoctype());
		compareStrings(document1.getDocumentURI(), document2.getDocumentURI());
		// no enough data to get the input encoding
		//! compareStrings(document1.getInputEncoding(), document2.getInputEncoding());
		assertTrue(document1.getStrictErrorChecking() == document2.getStrictErrorChecking());
		// no enough data to get the XML encoding
		//! compareStrings(document1.getXmlEncoding(), document2.getXmlEncoding());
		assertTrue(document1.getXmlStandalone() == document2.getXmlStandalone());
		compareStrings(document1.getXmlVersion(), document2.getXmlVersion());
		compareNodes(document1, document2);
	}
	
	/*
	private static void compareDocumentTypes(DocumentType docType1, DocumentType docType2) {
		if(docType1 == null || docType2 == null) {
			assert(docType1 == null && docType2 == null);
		} else {
			// do something here
		}
	}
	*/
	
	private static void compareStrings(String string1, String string2) {
		if (string1 == null || string2 == null) {
			assertTrue(string1 == string2);
		} else {
			assertTrue(string1.equals(string2));
		}
	}
	private static void compareNodeLists(NodeList nodeList1, NodeList nodeList2) {
		int i1 = 0, length1 = nodeList1 == null ? 0 : nodeList1.getLength();
		int i2 = 0, length2 = nodeList2 == null ? 0 : nodeList2.getLength();

		while ((i1 < length1) || (i2 < length2)) {
			Node node1 = null;
			Node node2 = null;

			while (i1 < length1) {
				Node node = nodeList1.item(i1++);
				if (node instanceof Text) {
					node1 = node;
					break; 
				}
				if (node instanceof Element) {
					node1 = node;
					break; 
				}
				if (node instanceof ProcessingInstruction) {
					node1 = node;
					break; 
				}
			}
			while (i2 < length2) {
				Node node = nodeList2.item(i2++);
				if (node instanceof Text) {
					node2 = node;
					break; 
				}
				if (node instanceof Element) {
					node2 = node;
					break; 
				}
				if (node instanceof ProcessingInstruction) {
					node2 = node;
					break; 
				}
			}
			if ((node1 == null) || (node2 == null)) {
				assertTrue((node1 == null) && (node2 == null));
			} else {
				if (node1 instanceof Text || node2 instanceof Text) {
					assertTrue(node1 instanceof Text && node2 instanceof Text);
					compareTexts((Text)node1, (Text)node2);
				}
				if (node1 instanceof Element || node2 instanceof Element) {
					assertTrue(node1 instanceof Element && node2 instanceof Element);
					compareElements((Element)node1, (Element)node2);
					compareAttrLists(node1.getAttributes(), node2.getAttributes());
					NodeList subList1 = node1.getChildNodes();
					NodeList subList2 = node2.getChildNodes();
					compareNodeLists(subList1, subList2);
				}
				if (node1 instanceof ProcessingInstruction || node2 instanceof ProcessingInstruction) {
					assertTrue(node1 instanceof ProcessingInstruction && node2 instanceof ProcessingInstruction);
					compareProcessingInstructions((ProcessingInstruction)node1, (ProcessingInstruction)node2);
				}
			}
		}
	}
	private static void compareTexts(Text text1, Text text2) {
		compareStrings(text1.getWholeText(), text2.getWholeText());
		compareNodes(text1, text2);
	}
	private static void compareProcessingInstructions(ProcessingInstruction pi1, ProcessingInstruction pi2) {
		if (pi1 == null || pi2 == null) {
			assertTrue(pi1 == null || pi2 == null);
		} else {
			compareStrings(pi1.getTarget(), pi2.getTarget());
			compareStrings(pi1.getData(), pi2.getData());
			compareNodes(pi1, pi2);
		}
	}
	private static void compareAttrs(Attr attr1, Attr attr2) {
		if (attr1 == null || attr2 == null) {
			assert(attr1 == null && attr2 == null);
		} else {
			compareStrings(attr1.getName(), attr2.getName());
			// no enough data to get the type information
			//! compareTypeInfos(attr1.getSchemaTypeInfo(), attr2.getSchemaTypeInfo());
			assertTrue(attr1.getSpecified() == attr2.getSpecified());
			compareStrings(attr1.getValue(), attr2.getValue());
			assertTrue(attr1.isId() == attr2.isId());
			compareNodes(attr1, attr2);
		}
	}
	private static void compareAttrLists(NamedNodeMap attrList1, NamedNodeMap attrList2) {
		int i1 = 0, length1 = attrList1 == null ? 0 : attrList1.getLength();
		int i2 = 0, length2 = attrList2 == null ? 0 : attrList2.getLength();
		assertTrue(length1 == length2);
		while (i1 < length1 && i2 < length2) {
			Node node1 = attrList1.item(i1++);
			Node node2 = attrList2.item(i2++);
			assertTrue(node1 instanceof Attr);
			assertTrue(node2 instanceof Attr);
			compareAttrs((Attr)node1, (Attr)node2);
		}
	}
	private static void getElementList(Node node, List<Element> elementList) {
		assert(node != null);
		assert(elementList != null);
		if(node instanceof Element) {
			elementList.add((Element)node);
			NodeList nodeList = node.getChildNodes();
			if (nodeList == null) return;
			for (int i = 0; i < nodeList.getLength(); i++) {
				getElementList(nodeList.item(i), elementList);
			}
		}
	}
	private static void assertTrue(boolean condition) {
		if(!condition) {
			throw new AssertionError();
		}
	}
}
