/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.ode.bpel.elang.xpath20.runtime;

import java.util.Iterator;

import javax.xml.namespace.QName;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathExpression;

import net.sf.saxon.expr.AxisExpression;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.ItemChecker;
import net.sf.saxon.expr.PathExpression;
import net.sf.saxon.expr.VariableReference;
import net.sf.saxon.om.Axis;
import net.sf.saxon.om.NamePool;
import net.sf.saxon.pattern.NameTest;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.xpath.XPathExpressionImpl;

import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.NSContext;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * A helper utility that modifies XPath Expression in-place.  This is meant
 * to be reusable across the XPath and XQuery runtimes.
 */
public class XPath20ExpressionModifier {
    private NSContext contextUris;
    private NamePool namePool;

    /**
     * Creates a new XPath20ExpressionModifier object.
     *
     * @param contextUris 
     * @param namePool 
     */
    public XPath20ExpressionModifier(NSContext contextUris, NamePool namePool) {
        this.contextUris = contextUris;
        this.namePool = namePool;
    }

    /**
     * Insert nodes into the specified XPath expression wherever
     * required To be precise, an node is added to its parent if: 
     * a) the node is an element... 
     * b) that corresponds to an step... 
     * c) that has a child axis... 
     * d) whose parent had no children with its name... 
     * e) and all preceding steps are element name tests.
     *
     * @param xpathExpr
     * @param namePool
     *
     * @throws DOMException
     * @throws TransformerException
     */
    @SuppressWarnings("unchecked")
    public void insertMissingData(XPathExpression xpathExpr, Node contextNode)
        throws DOMException, TransformerException {
        if ((contextNode == null) || !(contextNode instanceof Element) ||
                !(xpathExpr instanceof XPathExpressionImpl)) {
            return;
        }

        Expression expression = ((XPathExpressionImpl) xpathExpr).getInternalExpression();
        Iterator<Expression> subExpressions = (Iterator<Expression>) expression.iterateSubExpressions();

        if (!subExpressions.hasNext()) {
            return;
        }

        Expression subExpr = (Expression) subExpressions.next();

        if (!(subExpr instanceof PathExpression)) {
            return;
        }

        Document document = contextNode.getOwnerDocument();
        PathExpression pathExpr = (PathExpression) subExpr;
        Expression step = pathExpr.getFirstStep();

        while (step != null) {
            if (step instanceof AxisExpression) {
                AxisExpression axisExpr = (AxisExpression) step;

                NodeTest nodeTest = axisExpr.getNodeTest();

                if (!(nodeTest instanceof NameTest)) {
                    break;
                }

                NameTest nameTest = (NameTest) nodeTest;

                QName childName = getQualifiedName(nameTest.getFingerprint(),
                        namePool, contextUris);
                
                if (Axis.CHILD == axisExpr.getAxis()) {
                    if (NodeKindTest.ELEMENT.getNodeKindMask() != nameTest.getNodeKindMask()) {
                        break;
                    }
                	
                    NodeList children = ((Element) contextNode).getElementsByTagNameNS(childName.getNamespaceURI(),
                            childName.getLocalPart());
                    if ((children == null) || (children.getLength() == 0)) {
                        Node child = document.createElementNS(childName.getNamespaceURI(),
                                DOMUtils.getQualifiedName(childName));
                        contextNode.appendChild(child);
                        contextNode = child;
                    } else if (children.getLength() == 1) {
                        contextNode = children.item(0);
                    } else {
                        break;
                    }
                } else if (Axis.ATTRIBUTE == axisExpr.getAxis()) {
                    if (NodeKindTest.ATTRIBUTE.getNodeKindMask() != nameTest.getNodeKindMask()) {
                        break;
                    }
                    
                    Attr attribute = ((Element) contextNode).getAttributeNodeNS(childName.getNamespaceURI(), childName.getLocalPart());
                    if (attribute == null) {
                    	attribute = document.createAttributeNS(childName.getNamespaceURI(), childName.getLocalPart());
                    	((Element) contextNode).setAttributeNode(attribute);
                    	contextNode = attribute;
                    } else {
                    	break;
                    }
                	
                } else {
                	break;
                }


            } else if (step instanceof ItemChecker) {
                ItemChecker itemChecker = (ItemChecker) step;
                Expression baseExpr = itemChecker.getBaseExpression();

                if (!(baseExpr instanceof VariableReference)) {
                    break;
                }
            } else {
                break;
            }

            if (pathExpr != null) {
                Expression remainingSteps = pathExpr.getRemainingSteps();

                if (remainingSteps instanceof PathExpression) {
                    pathExpr = (PathExpression) remainingSteps;
                    step = pathExpr.getFirstStep();
                } else if (remainingSteps instanceof AxisExpression) {
                    pathExpr = null;
                    step = (AxisExpression) remainingSteps;
                }
            } else {
                break;
            }
        }
    }

    /**
     * Create the QName by running the given finger print against the
     * given context
     *
     * @param fingerprint
     * @param namePool
     * @param nsContext
     *
     * @return The QName corresponding to the finger print
     */
    private QName getQualifiedName(int fingerprint, NamePool namePool,
        NSContext nsContext) {
        String localName = namePool.getLocalName(fingerprint);
        String prefix = namePool.getPrefix(fingerprint);
        String uri = namePool.getURI(fingerprint);

        // Unfortunately, NSContext.getPrefix(String URI) doesn't always work
        // So, we need to find the prefix for the URI the hard way
        if ((prefix == null) || "".equals(prefix)) {
            for (String nsPrefix : nsContext.getPrefixes()) {
                String nsUri = nsContext.getNamespaceURI(nsPrefix);

                if (nsUri.equals(uri)) {
                    prefix = nsPrefix;
                }
            }
        }

        return new QName(uri, localName, prefix);
    }
}