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

package net.sourceforge.pmd.lang.ast.xpath;

import java.util.ArrayList;
import java.util.Iterator;

import org.jaxen.DefaultNavigator;
import org.jaxen.XPath;
import org.jaxen.util.SingleObjectIterator;

import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.RootNode;

/**
 * @author daniels
 */
public class DocumentNavigator extends DefaultNavigator {

    private static final Iterator<Node> EMPTY_ITERATOR = new ArrayList<Node>().iterator();

    @Override
    public String getAttributeName(Object arg0) {
        return ((Attribute) arg0).getName();
    }

    @Override
    public String getAttributeNamespaceUri(Object arg0) {
        return "";
    }

    @Override
    public String getAttributeQName(Object arg0) {
        return ((Attribute) arg0).getName();
    }

    @Override
    public String getAttributeStringValue(Object arg0) {
        return ((Attribute) arg0).getStringValue();
    }

    @Override
    public String getCommentStringValue(Object arg0) {
        return "";
    }

    @Override
    public String getElementName(Object node) {
        return ((Node) node).getXPathNodeName();
    }

    @Override
    public String getElementNamespaceUri(Object arg0) {
        return "";
    }

    @Override
    public String getElementQName(Object arg0) {
        return getElementName(arg0);
    }

    @Override
    public String getElementStringValue(Object arg0) {
        return "";
    }

    @Override
    public String getNamespacePrefix(Object arg0) {
        return "";
    }

    @Override
    public String getNamespaceStringValue(Object arg0) {
        return "";
    }

    @Override
    public String getTextStringValue(Object arg0) {
        return "";
    }

    @Override
    public boolean isAttribute(Object arg0) {
        return arg0 instanceof Attribute;
    }

    @Override
    public boolean isComment(Object arg0) {
        return false;
    }

    @Override
    public boolean isDocument(Object arg0) {
        return arg0 instanceof RootNode;
    }

    @Override
    public boolean isElement(Object arg0) {
        return arg0 instanceof Node;
    }

    @Override
    public boolean isNamespace(Object arg0) {
        return false;
    }

    @Override
    public boolean isProcessingInstruction(Object arg0) {
        return false;
    }

    @Override
    public boolean isText(Object arg0) {
        return false;
    }

    @Override
    public XPath parseXPath(String arg0) {
        return null;
    }

    @Override
    public Object getParentNode(Object arg0) {
        if (arg0 instanceof Node) {
            return ((Node) arg0).jjtGetParent();
        }
        if (arg0 instanceof Attribute) {
            return ((Attribute) arg0).getParent();
        }
        // can't navigate to parent node...
        return null;
    }

    @Override
    public Iterator<Attribute> getAttributeAxisIterator(Object arg0) {
        if (arg0 instanceof AttributeNode) {
            return ((AttributeNode) arg0).getAttributeIterator();
        } else {
            return new AttributeAxisIterator((Node) arg0);
        }
    }

    /**
     * Get an iterator over all of this node's children.
     *
     * @param contextNode
     *            The context node for the child axis.
     * @return A possibly-empty iterator (not null).
     */
    @Override
    public Iterator<Node> getChildAxisIterator(Object contextNode) {
        return new NodeIterator((Node) contextNode) {
            @Override
            protected Node getFirstNode(Node node) {
                return getFirstChild(node);
            }

            @Override
            protected Node getNextNode(Node node) {
                return getNextSibling(node);
            }
        };
    }

    /**
     * Get a (single-member) iterator over this node's parent.
     *
     * @param contextNode
     *            the context node for the parent axis.
     * @return A possibly-empty iterator (not null).
     */
    @Override
    public Iterator<Node> getParentAxisIterator(Object contextNode) {
        if (isAttribute(contextNode)) {
            return new SingleObjectIterator(((Attribute) contextNode).getParent());
        }
        Node parent = ((Node) contextNode).jjtGetParent();
        if (parent != null) {
            return new SingleObjectIterator(parent);
        } else {
            return EMPTY_ITERATOR;
        }
    }

    /**
     * Get an iterator over all following siblings.
     *
     * @param contextNode
     *            the context node for the sibling iterator.
     * @return A possibly-empty iterator (not null).
     */
    @Override
    public Iterator<Node> getFollowingSiblingAxisIterator(Object contextNode) {
        return new NodeIterator((Node) contextNode) {
            @Override
            protected Node getFirstNode(Node node) {
                return getNextNode(node);
            }

            @Override
            protected Node getNextNode(Node node) {
                return getNextSibling(node);
            }
        };
    }

    /**
     * Get an iterator over all preceding siblings.
     *
     * @param contextNode
     *            The context node for the preceding sibling axis.
     * @return A possibly-empty iterator (not null).
     */
    @Override
    public Iterator<Node> getPrecedingSiblingAxisIterator(Object contextNode) {
        return new NodeIterator((Node) contextNode) {
            @Override
            protected Node getFirstNode(Node node) {
                return getNextNode(node);
            }

            @Override
            protected Node getNextNode(Node node) {
                return getPreviousSibling(node);
            }
        };
    }

    /**
     * Get an iterator over all following nodes, depth-first.
     *
     * @param contextNode
     *            The context node for the following axis.
     * @return A possibly-empty iterator (not null).
     */
    @Override
    public Iterator<Node> getFollowingAxisIterator(Object contextNode) {
        return new NodeIterator((Node) contextNode) {
            @Override
            protected Node getFirstNode(Node node) {
                if (node == null) {
                    return null;
                } else {
                    Node sibling = getNextSibling(node);
                    if (sibling == null) {
                        return getFirstNode(node.jjtGetParent());
                    } else {
                        return sibling;
                    }
                }
            }

            @Override
            protected Node getNextNode(Node node) {
                if (node == null) {
                    return null;
                } else {
                    Node n = getFirstChild(node);
                    if (n == null) {
                        n = getNextSibling(node);
                    }
                    if (n == null) {
                        return getFirstNode(node.jjtGetParent());
                    } else {
                        return n;
                    }
                }
            }
        };
    }

    /**
     * Get an iterator over all preceding nodes, depth-first.
     *
     * @param contextNode
     *            The context node for the preceding axis.
     * @return A possibly-empty iterator (not null).
     */
    @Override
    public Iterator<Node> getPrecedingAxisIterator(Object contextNode) {
        return new NodeIterator((Node) contextNode) {
            @Override
            protected Node getFirstNode(Node node) {
                if (node == null) {
                    return null;
                } else {
                    Node sibling = getPreviousSibling(node);
                    if (sibling == null) {
                        return getFirstNode(node.jjtGetParent());
                    } else {
                        return sibling;
                    }
                }
            }

            @Override
            protected Node getNextNode(Node node) {
                if (node == null) {
                    return null;
                } else {
                    Node n = getLastChild(node);
                    if (n == null) {
                        n = getPreviousSibling(node);
                    }
                    if (n == null) {
                        return getFirstNode(node.jjtGetParent());
                    } else {
                        return n;
                    }
                }
            }
        };
    }

    @Override
    public Object getDocumentNode(Object contextNode) {
        if (isDocument(contextNode)) {
            return contextNode;
        }
        if (null == contextNode) {
            throw new RuntimeException("contextNode may not be null");
        }
        return getDocumentNode(getParentNode(contextNode));
    }
}
