/*
 * Copyright (c) 2013 Red Hat, Inc. and/or its affiliates.
 *
 *  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:
 *      Brad Davis - bradsdavis@gmail.com - Initial API and implementation
*/
package org.jboss.windup.util.xml;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.DefaultHandler2;


/**
 * A SAX ContentHandler which stores the location of each node into that node's <code>setUserData()</code>.
 */
public class LocationAwareContentHandler extends DefaultHandler2
{
	final public static String LINE_NUMBER_KEY_NAME = "ln";
	final public static String COLUMN_NUMBER_KEY_NAME = "cn";
	final public static String DOCTYPE_KEY_NAME = "dt";
	final public static String NAMESPACE_KEY_NAME = "nsuri";


	private final Set<String> namespaceURIs = new HashSet<>();
	private final Document doc;
	private Locator locator;
	private Element current;
	private Doctype doctype;

	@Override
	public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws SAXException, IOException {
		doctype = new Doctype(name, publicId, systemId, baseURI);
		doc.setUserData(DOCTYPE_KEY_NAME, doctype, null);
		return new InputSource(new ByteArrayInputStream(new byte[0]));
	}

	public LocationAwareContentHandler(Document d) {
		this.doc = d;
	}

	@Override
	public void setDocumentLocator(final Locator locator) {
		this.locator = locator;
	}

	@Override
	public void startElement(final String uri, final String localName, final String qName, final Attributes attrs) throws SAXException {
		Element e = null;
		if (localName != null && !"".equals(localName)) {
			e = doc.createElementNS(uri, localName);
		}
		else {
			e = doc.createElement(qName);
		}

		storeLineInformation(e);
		if(StringUtils.isNotBlank(uri)) {
			namespaceURIs.add(uri);
		}

		if(doc.getUserData(NAMESPACE_KEY_NAME) == null) {
			doc.setUserData(NAMESPACE_KEY_NAME, namespaceURIs, null);
		}

		if (current == null) {
			doc.appendChild(e);
		}
		else {
			current.appendChild(e);
		}
		current = e;

		if (attrs != null) {
			for (int i = 0; i < attrs.getLength(); i++) {
				Attr attr = null;
				if (attrs.getLocalName(i) != null && !"".equals(attrs.getLocalName(i))) {
					attr = doc.createAttributeNS(attrs.getURI(i), attrs.getLocalName(i));
					attr.setValue(attrs.getValue(i));
					storeLineInformation(attr);
					current.setAttributeNodeNS(attr);
				}
				else {
					attr = doc.createAttribute(attrs.getQName(i));
					attr.setValue(attrs.getValue(i));
					storeLineInformation(attr);
					current.setAttributeNode(attr);
				}
			}
		}
	}

	@Override
	public void endElement(final String uri, final String localName, final String qName) {
		if (current == null) {
			return;
		}

		Node parent = current.getParentNode();
		// If the parent is the document itself, then we're done.
		if (parent.getParentNode() == null) {
			current.normalize();
			current = null;
		}
		else {
			current = (Element) current.getParentNode();
		}
	}

	private void storeLineInformation(Node e) {
	    e.setUserData(LINE_NUMBER_KEY_NAME, this.locator.getLineNumber(), null);
        e.setUserData(COLUMN_NUMBER_KEY_NAME, this.locator.getColumnNumber(), null);
	}

	@Override
	public void characters(char buf[], int offset, int length) {
		if (current != null) {
			Node n = doc.createTextNode(new String(buf, offset, length));
			storeLineInformation(n);
			current.appendChild(n);
		}
	}

	public class Doctype {
		public String name;
		public String publicId;
		public String systemId;
		public String baseURI;

		public Doctype(String name, String publicId, String systemId, String baseURI) {
			this.name = name;
			this.publicId = publicId;
			this.systemId = systemId;
			this.baseURI = baseURI;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getPublicId() {
			return publicId;
		}

		public void setPublicId(String publicId) {
			this.publicId = publicId;
		}

		public String getSystemId() {
			return systemId;
		}

		public void setSystemId(String systemId) {
			this.systemId = systemId;
		}

		public String getBaseURI() {
			return baseURI;
		}

		public void setBaseURI(String baseURI) {
			this.baseURI = baseURI;
		}

		@Override
		public String toString() {
			return "Doctype [name=" + name + ", publicId=" + publicId
					+ ", systemId=" + systemId + ", baseURI=" + baseURI + "]";
		}
	}
}
