/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 * 
 * Nuts Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.core.oxm.adapter;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;

/**
 * AbstractAdapterNode is the base for childAdapters that expose a read-only
 * view of a Java object as a DOM Node. This class implements the core
 * parent-child and sibling node traversal functionality shared by all adapter
 * type nodes and used in proxy node support.
 * 
 * @see AbstractElementAdapter
 */
public abstract class AbstractNodeAdapter implements NodeAdapter {

	protected static final NamedNodeMap EMPTY_NAMEDNODEMAP = new NamedNodeMap() {
		public int getLength() {
			return 0;
		}

		public Node item(int index) {
			return null;
		}

		public Node getNamedItem(String name) {
			return null;
		}

		public Node removeNamedItem(String name) throws DOMException {
			return null;
		}

		public Node setNamedItem(Node arg) throws DOMException {
			return null;
		}

		public Node setNamedItemNS(Node arg) throws DOMException {
			return null;
		}

		public Node getNamedItemNS(String namespaceURI, String localName) {
			return null;
		}

		public Node removeNamedItemNS(String namespaceURI, String localName)
				throws DOMException {
			return null;
		}
	};

	private List<Node> childAdapters;

	// The domain object that we are adapting
	private Object propertyValue;
	private String propertyName;
	private NodeAdapter parent;
	private AdapterFactory adapterFactory;

	public AbstractNodeAdapter() {
	}

	/**
	 * 
	 * @param adapterFactory
	 * @param parent
	 * @param propertyName
	 * @param value
	 */
	protected void setContext(AdapterFactory adapterFactory,
			NodeAdapter parent, String propertyName, Object value) {
		setAdapterFactory(adapterFactory);
		setParent(parent);
		setPropertyName(propertyName);
		setPropertyValue(value);
	}


	public NamedNodeMap getAttributes() {
		return EMPTY_NAMEDNODEMAP;
	}

	/**
	 * subclasses override to produce their children
	 * 
	 * @return List of child adapters.
	 */
	protected List<Node> buildChildAdapters() {
		return new ArrayList<Node>();
	}

	/**
	 * reset child adapters
	 */
	protected void resetChildAdapters() {
		childAdapters = null;
	}

	/**
	 * Lazily initialize child childAdapters
	 */
	protected List<Node> getChildAdapters() {
		if (childAdapters == null) {
			childAdapters = buildChildAdapters();
		}
		return childAdapters;
	}

	public Node getChildBeforeOrAfter(Node child, boolean before) {
		List adapters = getChildAdapters();
		int index = adapters.indexOf(child);
		if (index < 0) {
			throw new RuntimeException(child + " is no child of " + this);
		}
		int siblingIndex = before ? index - 1 : index + 1;
		return ((0 < siblingIndex) && (siblingIndex < adapters.size())) ? ((Node)adapters.get(siblingIndex))
				: null;
	}

	public Node getChildAfter(Node child) {
		return getChildBeforeOrAfter(child, false/* after */);
	}

	public Node getChildBefore(Node child) {
		return getChildBeforeOrAfter(child, true/* after */);
	}

	public NodeList getElementsByTagName(String tagName) {
		if (tagName.equals("*")) {
			return getChildNodes();
		}
		else {
			LinkedList<Node> filteredChildren = new LinkedList<Node>();

			for (Node adapterNode : getChildAdapters()) {
				if (adapterNode.getNodeName().equals(tagName)) {
					filteredChildren.add(adapterNode);
				}
			}

			return new SimpleNodeList(filteredChildren);
		}
	}

	// Begin Node methods
	public NodeList getChildNodes() {
		NodeList nl = new SimpleNodeList(getChildAdapters());
		return nl;
	}

	public Node getFirstChild() {
		return (getChildNodes().getLength() > 0) ? getChildNodes().item(0)
				: null;
	}

	public Node getLastChild() {
		return (getChildNodes().getLength() > 0) ? getChildNodes().item(
				getChildNodes().getLength() - 1) : null;
	}

	public String getLocalName() {
		return null;
	}

	public String getNamespaceURI() {
		return null;
	}

	public void setNodeValue(String string) throws DOMException {
		throw new UnsupportedOperationException();
	}

	public String getNodeValue() throws DOMException {
		throw new UnsupportedOperationException();
	}

	public Document getOwnerDocument() {
		for (Node p = getParentNode(); p != null; p = p.getParentNode()) {
			if (p instanceof Document) {
				return (Document)p;
			}
		}
		
		return null;
	}

	public Node getParentNode() {
		return getParent();
	}

	public NodeAdapter getParent() {
		return parent;
	}

	public void setParent(NodeAdapter parent) {
		this.parent = parent;
	}

	public Object getPropertyValue() {
		return propertyValue;
	}

	public void setPropertyValue(Object prop) {
		this.propertyValue = prop;
	}

	public void setPrefix(String string) throws DOMException {
		throw new UnsupportedOperationException();
	}

	public String getPrefix() {
		return null;
	}

	public Node getNextSibling() {
		Node next = getParent().getChildAfter(this);
		return next;
	}

	public Node getPreviousSibling() {
		return getParent().getChildBefore(this);
	}

	public String getPropertyName() {
		return propertyName;
	}

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

	public AdapterFactory getAdapterFactory() {
		return adapterFactory;
	}

	public void setAdapterFactory(AdapterFactory adapterFactory) {
		this.adapterFactory = adapterFactory;
	}

	public boolean isSupported(String string, String string1) {
		throw new UnsupportedOperationException("isSupported");
	}

	public Node appendChild(Node node) throws DOMException {
		throw new UnsupportedOperationException("appendChild");
	}

	public Node cloneNode(boolean b) {
		throw new UnsupportedOperationException("cloneNode");
	}

	public boolean hasAttributes() {
		return false;
	}

	public boolean hasChildNodes() {
		return CollectionUtils.isNotEmpty(getChildAdapters());
	}

	public Node insertBefore(Node node, Node node1) throws DOMException {
		throw new UnsupportedOperationException("insertBefore");
	}

	public void normalize() {
		throw new UnsupportedOperationException("normalize");
	}

	public Node removeChild(Node node) throws DOMException {
		throw new UnsupportedOperationException();
	}

	public Node replaceChild(Node node, Node node1) throws DOMException {
		throw new UnsupportedOperationException();
	}

	// Begin DOM 3 methods

	public boolean isDefaultNamespace(String string) {
		throw new UnsupportedOperationException("isDefaultNamespace");
	}

	public String lookupNamespaceURI(String string) {
		throw new UnsupportedOperationException("lookupNamespaceURI");
	}

	public String getNodeName() {
		return propertyName;
	}

	public String getBaseURI() {
		throw new UnsupportedOperationException("getBaseURI");
	}

	public short compareDocumentPosition(Node node) throws DOMException {
		throw new UnsupportedOperationException("compareDocumentPosition");
	}

	public String getTextContent() throws DOMException {
		throw new UnsupportedOperationException("getTextContent");
	}

	public void setTextContent(String string) throws DOMException {
		throw new UnsupportedOperationException("setTextContent");

	}

	public boolean isSameNode(Node node) {
		throw new UnsupportedOperationException("isSameNode");
	}

	public String lookupPrefix(String string) {
		throw new UnsupportedOperationException("lookupPrefix");
	}

	public boolean isEqualNode(Node node) {
		throw new UnsupportedOperationException("isEqualNode");
	}

	public Object getFeature(String string, String string1) {
		throw new UnsupportedOperationException("getFeature");
	}

	public Object setUserData(String string, Object object,
			UserDataHandler userDataHandler) {
		throw new UnsupportedOperationException("setUserData");
	}

	public Object getUserData(String string) {
		throw new UnsupportedOperationException("getUserData");
	}

	// End node methods

	public String toString() {
		return getClass() + ": " + getNodeName() + " parent=" + getParentNode();
	}
}
