/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */
package net.sourceforge.pmd.lang.xml.ast;

import static net.sourceforge.pmd.lang.xml.ast.XmlNode.BEGIN_COLUMN;
import static net.sourceforge.pmd.lang.xml.ast.XmlNode.BEGIN_LINE;
import static net.sourceforge.pmd.lang.xml.ast.XmlNode.END_COLUMN;
import static net.sourceforge.pmd.lang.xml.ast.XmlNode.END_LINE;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import net.sourceforge.pmd.lang.ast.xpath.Attribute;
import net.sourceforge.pmd.util.CompoundIterator;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

public class XmlNodeInvocationHandler implements InvocationHandler {
    private final Node node;
    private Object userData;
    private XmlParser parser;

    public XmlNodeInvocationHandler(XmlParser parser, Node node) {
        this.parser = parser;
        this.node = node;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // XmlNode method?
        if (method.getDeclaringClass().isAssignableFrom(XmlNode.class)
                && !"java.lang.Object".equals(method.getDeclaringClass().getName())) {
            if ("jjtGetNumChildren".equals(method.getName())) {
                return node.hasChildNodes() ? node.getChildNodes().getLength() : 0;
            } else if ("jjtGetChild".equals(method.getName())) {
                return parser.createProxy(node.getChildNodes().item(((Integer) args[0]).intValue()));
            } else if ("jjtGetChildIndex".equals(method.getName())) {
                Node parent = node.getParentNode();
                NodeList childNodes = parent.getChildNodes();
                for (int i = 0; i < childNodes.getLength(); i++) {
                    if (node == childNodes.item(i)) {
                        return i;
                    }
                }
                throw new IllegalStateException("This node is not a child of its parent: " + node);
            } else if ("getImage".equals(method.getName())) {
                if (node instanceof Text) {
                    return ((Text) node).getData();
                } else {
                    return null;
                }
            } else if ("jjtGetParent".equals(method.getName())) {
                Node parent = node.getParentNode();
                if (parent != null && !(parent instanceof Document)) {
                    return parser.createProxy(parent);
                } else {
                    return null;
                }
            } else if ("getAttributeIterator".equals(method.getName())) {
                List<Iterator<Attribute>> iterators = new ArrayList<Iterator<Attribute>>();

                // Expose DOM Attributes
                final NamedNodeMap attributes = node.getAttributes();
                iterators.add(new Iterator<Attribute>() {
                    private int index;

                    public boolean hasNext() {
                        return attributes != null && index < attributes.getLength();
                    }

                    public Attribute next() {
                        Node attributeNode = attributes.item(index++);
                        return new Attribute(parser.createProxy(node), attributeNode.getNodeName(), attributeNode
                                .getNodeValue());
                    }

                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                });

                // Expose Text/CDATA nodes to have an 'Image' attribute like
                // AST Nodes
                if (proxy instanceof Text) {
                    iterators.add(Collections.singletonList(
                            new Attribute((net.sourceforge.pmd.lang.ast.Node) proxy, "Image", ((Text) proxy)
                                    .getData())).iterator());
                }

                // Expose Java Attributes
                // iterators.add(new
                // AttributeAxisIterator((net.sourceforge.pmd.lang.ast.Node)
                // p));

                return new CompoundIterator<Attribute>(iterators.toArray(new Iterator[iterators.size()]));
            } else if ("getBeginLine".equals(method.getName())) {
                return getUserData(BEGIN_LINE);
            } else if ("getBeginColumn".equals(method.getName())) {
                return getUserData(BEGIN_COLUMN);
            } else if ("getEndLine".equals(method.getName())) {
                return getUserData(END_LINE);
            } else if ("getEndColumn".equals(method.getName())) {
                return getUserData(END_COLUMN);
            } else if ("getNode".equals(method.getName())) {
                return node;
            } else if ("getUserData".equals(method.getName())) {
                return userData;
            } else if ("setUserData".equals(method.getName())) {
                userData = args[0];
                return null;
            } else if ("isFindBoundary".equals(method.getName())) {
                return false;
            }
            throw new UnsupportedOperationException("Method not supported for XmlNode: " + method);
        }
        // Delegate method
        else {
            if ("toString".equals(method.getName())) {
                String s = node.getNodeName();
                s = s.replace("#", "");
                return s;
            }
            Object result = method.invoke(node, args);
            return result;
        }
    }

    private Integer getUserData(String key) {
        if (node.getUserData(key) != null) {
            return (Integer) node.getUserData(key);
        }
        return Integer.valueOf(-1);
    }
}
