/*
 * reserved comment block
 * DO NOT REMOVE OR ALTER!
 */
/*
 * 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 com.sun.org.apache.xalan.internal.xsltc.compiler;

import java.util.Vector;

import com.sun.org.apache.bcel.internal.generic.ALOAD;
import com.sun.org.apache.bcel.internal.generic.ASTORE;
import com.sun.org.apache.bcel.internal.generic.CHECKCAST;
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.ICONST;
import com.sun.org.apache.bcel.internal.generic.ILOAD;
import com.sun.org.apache.bcel.internal.generic.ISTORE;
import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.LocalVariableGen;
import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.org.apache.bcel.internal.generic.PUSH;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
import com.sun.org.apache.xml.internal.dtm.Axis;
import com.sun.org.apache.xml.internal.dtm.DTM;

/**
 * @author Jacek Ambroziak
 * @author Santiago Pericas-Geertsen
 * @author Morten Jorgensen
 */
final class Step extends RelativeLocationPath {

    /**
     * This step's axis as defined in class Axis.
     */
    private int _axis;

    /**
     * A vector of predicates (filters) defined on this step - may be null
     */
    private Vector _predicates;

    /**
     * Some simple predicates can be handled by this class (and not by the
     * Predicate class) and will be removed from the above vector as they are
     * handled. We use this boolean to remember if we did have any predicates.
     */
    private boolean _hadPredicates = false;

    /**
     * Type of the node test.
     */
    private int _nodeType;

    public Step(int axis, int nodeType, Vector predicates) {
        _axis = axis;
        _nodeType = nodeType;
        _predicates = predicates;
    }

    /**
     * Set the parser for this element and all child predicates
     */
    public void setParser(Parser parser) {
        super.setParser(parser);
        if (_predicates != null) {
            final int n = _predicates.size();
            for (int i = 0; i < n; i++) {
                final Predicate exp = (Predicate)_predicates.elementAt(i);
                exp.setParser(parser);
                exp.setParent(this);
            }
        }
    }

    /**
     * Define the axis (defined in Axis class) for this step
     */
    public int getAxis() {
        return _axis;
    }

    /**
     * Get the axis (defined in Axis class) for this step
     */
    public void setAxis(int axis) {
        _axis = axis;
    }

    /**
     * Returns the node-type for this step
     */
    public int getNodeType() {
        return _nodeType;
    }

    /**
     * Returns the vector containing all predicates for this step.
     */
    public Vector getPredicates() {
        return _predicates;
    }

    /**
     * Returns the vector containing all predicates for this step.
     */
    public void addPredicates(Vector predicates) {
        if (_predicates == null) {
            _predicates = predicates;
        }
        else {
            _predicates.addAll(predicates);
        }
    }

    /**
     * Returns 'true' if this step has a parent pattern.
     * This method will return 'false' if this step occurs on its own under
     * an element like <xsl:for-each> or <xsl:apply-templates>.
     */
    private boolean hasParentPattern() {
        final SyntaxTreeNode parent = getParent();
        return (parent instanceof ParentPattern ||
                parent instanceof ParentLocationPath ||
                parent instanceof UnionPathExpr ||
                parent instanceof FilterParentPath);
    }

    /**
     * Returns 'true' if this step has a parent location path.
     */
    private boolean hasParentLocationPath() {
        return getParent() instanceof ParentLocationPath;
    }

    /**
     * Returns 'true' if this step has any predicates
     */
    private boolean hasPredicates() {
        return _predicates != null && _predicates.size() > 0;
    }

    /**
     * Returns 'true' if this step is used within a predicate
     */
    private boolean isPredicate() {
        SyntaxTreeNode parent = this;
        while (parent != null) {
            parent = parent.getParent();
            if (parent instanceof Predicate) return true;
        }
        return false;
    }

    /**
     * True if this step is the abbreviated step '.'
     */
    public boolean isAbbreviatedDot() {
        return _nodeType == NodeTest.ANODE && _axis == Axis.SELF;
    }


    /**
     * True if this step is the abbreviated step '..'
     */
    public boolean isAbbreviatedDDot() {
        return _nodeType == NodeTest.ANODE && _axis == Axis.PARENT;
    }

    /**
     * Type check this step. The abbreviated steps '.' and '@attr' are
     * assigned type node if they have no predicates. All other steps
     * have type node-set.
     */
    public Type typeCheck(SymbolTable stable) throws TypeCheckError {

        // Save this value for later - important for testing for special
        // combinations of steps and patterns than can be optimised
        _hadPredicates = hasPredicates();

        // Special case for '.'
        //   in the case where '.' has a context such as book/.
        //   or .[false()] we can not optimize the nodeset to a single node.
        if (isAbbreviatedDot()) {
            _type = (hasParentPattern() || hasPredicates() || hasParentLocationPath()) ?
                Type.NodeSet : Type.Node;
        }
        else {
            _type = Type.NodeSet;
        }

        // Type check all predicates (expressions applied to the step)
        if (_predicates != null) {
            final int n = _predicates.size();
            for (int i = 0; i < n; i++) {
                final Expression pred = (Expression)_predicates.elementAt(i);
                pred.typeCheck(stable);
            }
        }

        // Return either Type.Node or Type.NodeSet
        return _type;
    }

    /**
     * Translate a step by pushing the appropriate iterator onto the stack.
     * The abbreviated steps '.' and '@attr' do not create new iterators
     * if they are not part of a LocationPath and have no filters.
     * In these cases a node index instead of an iterator is pushed
     * onto the stack.
     */
    public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
        translateStep(classGen, methodGen, hasPredicates() ? _predicates.size() - 1 : -1);
    }

    private void translateStep(ClassGenerator classGen,
                               MethodGenerator methodGen,
                               int predicateIndex) {
        final ConstantPoolGen cpg = classGen.getConstantPool();
        final InstructionList il = methodGen.getInstructionList();

        if (predicateIndex >= 0) {
            translatePredicates(classGen, methodGen, predicateIndex);
        } else {
            int star = 0;
            String name = null;
            final XSLTC xsltc = getParser().getXSLTC();

            if (_nodeType >= DTM.NTYPES) {
                final Vector ni = xsltc.getNamesIndex();

                name = (String)ni.elementAt(_nodeType-DTM.NTYPES);
                star = name.lastIndexOf('*');
            }

            // If it is an attribute, but not '@*', '@pre:*' or '@node()',
            // and has no parent
            if (_axis == Axis.ATTRIBUTE && _nodeType != NodeTest.ATTRIBUTE
                && _nodeType != NodeTest.ANODE && !hasParentPattern()
                && star == 0)
            {
                int iter = cpg.addInterfaceMethodref(DOM_INTF,
                                                     "getTypedAxisIterator",
                                                     "(II)"+NODE_ITERATOR_SIG);
                il.append(methodGen.loadDOM());
                il.append(new PUSH(cpg, Axis.ATTRIBUTE));
                il.append(new PUSH(cpg, _nodeType));
                il.append(new INVOKEINTERFACE(iter, 3));
                return;
            }

            SyntaxTreeNode parent = getParent();
            // Special case for '.'
            if (isAbbreviatedDot()) {
                if (_type == Type.Node) {
                    // Put context node on stack if using Type.Node
                    il.append(methodGen.loadContextNode());
                }
                else {
                    if (parent instanceof ParentLocationPath){
                        // Wrap the context node in a singleton iterator if not.
                        int init = cpg.addMethodref(SINGLETON_ITERATOR,
                                                    "<init>",
                                                    "("+NODE_SIG+")V");
                        il.append(new NEW(cpg.addClass(SINGLETON_ITERATOR)));
                        il.append(DUP);
                        il.append(methodGen.loadContextNode());
                        il.append(new INVOKESPECIAL(init));
                    } else {
                        // DOM.getAxisIterator(int axis);
                        int git = cpg.addInterfaceMethodref(DOM_INTF,
                                                "getAxisIterator",
                                                "(I)"+NODE_ITERATOR_SIG);
                        il.append(methodGen.loadDOM());
                        il.append(new PUSH(cpg, _axis));
                        il.append(new INVOKEINTERFACE(git, 2));
                    }
                }
                return;
            }

            // Special case for /foo/*/bar
            if ((parent instanceof ParentLocationPath) &&
                (parent.getParent() instanceof ParentLocationPath)) {
                if ((_nodeType == NodeTest.ELEMENT) && (!_hadPredicates)) {
                    _nodeType = NodeTest.ANODE;
                }
            }

            // "ELEMENT" or "*" or "@*" or ".." or "@attr" with a parent.
            switch (_nodeType) {
            case NodeTest.ATTRIBUTE:
                _axis = Axis.ATTRIBUTE;
            case NodeTest.ANODE:
                // DOM.getAxisIterator(int axis);
                int git = cpg.addInterfaceMethodref(DOM_INTF,
                                                    "getAxisIterator",
                                                    "(I)"+NODE_ITERATOR_SIG);
                il.append(methodGen.loadDOM());
                il.append(new PUSH(cpg, _axis));
                il.append(new INVOKEINTERFACE(git, 2));
                break;
            default:
                if (star > 1) {
                    final String namespace;
                    if (_axis == Axis.ATTRIBUTE)
                        namespace = name.substring(0,star-2);
                    else
                        namespace = name.substring(0,star-1);

                    final int nsType = xsltc.registerNamespace(namespace);
                    final int ns = cpg.addInterfaceMethodref(DOM_INTF,
                                                    "getNamespaceAxisIterator",
                                                    "(II)"+NODE_ITERATOR_SIG);
                    il.append(methodGen.loadDOM());
                    il.append(new PUSH(cpg, _axis));
                    il.append(new PUSH(cpg, nsType));
                    il.append(new INVOKEINTERFACE(ns, 3));
                    break;
                }
            case NodeTest.ELEMENT:
                // DOM.getTypedAxisIterator(int axis, int type);
                final int ty = cpg.addInterfaceMethodref(DOM_INTF,
                                                "getTypedAxisIterator",
                                                "(II)"+NODE_ITERATOR_SIG);
                // Get the typed iterator we're after
                il.append(methodGen.loadDOM());
                il.append(new PUSH(cpg, _axis));
                il.append(new PUSH(cpg, _nodeType));
                il.append(new INVOKEINTERFACE(ty, 3));

                break;
            }
        }
    }


    /**
     * Translate a sequence of predicates. Each predicate is translated
     * by constructing an instance of <code>CurrentNodeListIterator</code>
     * which is initialized from another iterator (recursive call),
     * a filter and a closure (call to translate on the predicate) and "this".
     */
    public void translatePredicates(ClassGenerator classGen,
                                    MethodGenerator methodGen,
                                    int predicateIndex) {
        final ConstantPoolGen cpg = classGen.getConstantPool();
        final InstructionList il = methodGen.getInstructionList();

        int idx = 0;

        if (predicateIndex < 0) {
            translateStep(classGen, methodGen, predicateIndex);
        }
        else {
            final Predicate predicate = (Predicate) _predicates.get(predicateIndex--);

            // Special case for predicates that can use the NodeValueIterator
            // instead of an auxiliary class. Certain path/predicates pairs
            // are translated into a base path, on top of which we place a
            // node value iterator that tests for the desired value:
            //   foo[@attr = 'str']  ->  foo/@attr + test(value='str')
            //   foo[bar = 'str']    ->  foo/bar + test(value='str')
            //   foo/bar[. = 'str']  ->  foo/bar + test(value='str')
            if (predicate.isNodeValueTest()) {
                Step step = predicate.getStep();

                il.append(methodGen.loadDOM());
                // If the predicate's Step is simply '.' we translate this Step
                // and place the node test on top of the resulting iterator
                if (step.isAbbreviatedDot()) {
                    translateStep(classGen, methodGen, predicateIndex);
                    il.append(new ICONST(DOM.RETURN_CURRENT));
                }
                // Otherwise we create a parent location path with this Step and
                // the predicates Step, and place the node test on top of that
                else {
                    ParentLocationPath path = new ParentLocationPath(this, step);
                    _parent = step._parent = path;      // Force re-parenting

                    try {
                        path.typeCheck(getParser().getSymbolTable());
                    }
                    catch (TypeCheckError e) { }
                    translateStep(classGen, methodGen, predicateIndex);
                    path.translateStep(classGen, methodGen);
                    il.append(new ICONST(DOM.RETURN_PARENT));
                }
                predicate.translate(classGen, methodGen);
                idx = cpg.addInterfaceMethodref(DOM_INTF,
                                                GET_NODE_VALUE_ITERATOR,
                                                GET_NODE_VALUE_ITERATOR_SIG);
                il.append(new INVOKEINTERFACE(idx, 5));
            }
            // Handle '//*[n]' expression
            else if (predicate.isNthDescendant()) {
                il.append(methodGen.loadDOM());
                // il.append(new ICONST(NodeTest.ELEMENT));
                il.append(new PUSH(cpg, predicate.getPosType()));
                predicate.translate(classGen, methodGen);
                il.append(new ICONST(0));
                idx = cpg.addInterfaceMethodref(DOM_INTF,
                                                "getNthDescendant",
                                                "(IIZ)"+NODE_ITERATOR_SIG);
                il.append(new INVOKEINTERFACE(idx, 4));
            }
            // Handle 'elem[n]' expression
            else if (predicate.isNthPositionFilter()) {
                idx = cpg.addMethodref(NTH_ITERATOR_CLASS,
                                       "<init>",
                                       "("+NODE_ITERATOR_SIG+"I)V");

                // Backwards branches are prohibited if an uninitialized object
                // is on the stack by section 4.9.4 of the JVM Specification,
                // 2nd Ed.  We don't know whether this code might contain
                // backwards branches, so we mustn't create the new object until
                // after we've created the suspect arguments to its constructor.
                // Instead we calculate the values of the arguments to the
                // constructor first, store them in temporary variables, create
                // the object and reload the arguments from the temporaries to
                // avoid the problem.
                translatePredicates(classGen, methodGen, predicateIndex); // recursive call
                LocalVariableGen iteratorTemp
                        = methodGen.addLocalVariable("step_tmp1",
                                         Util.getJCRefType(NODE_ITERATOR_SIG),
                                         null, null);
                iteratorTemp.setStart(
                        il.append(new ASTORE(iteratorTemp.getIndex())));

                predicate.translate(classGen, methodGen);
                LocalVariableGen predicateValueTemp
                        = methodGen.addLocalVariable("step_tmp2",
                                         Util.getJCRefType("I"),
                                         null, null);
                predicateValueTemp.setStart(
                        il.append(new ISTORE(predicateValueTemp.getIndex())));

                il.append(new NEW(cpg.addClass(NTH_ITERATOR_CLASS)));
                il.append(DUP);
                iteratorTemp.setEnd(
                        il.append(new ALOAD(iteratorTemp.getIndex())));
                predicateValueTemp.setEnd(
                        il.append(new ILOAD(predicateValueTemp.getIndex())));
                il.append(new INVOKESPECIAL(idx));
            }
            else {
                idx = cpg.addMethodref(CURRENT_NODE_LIST_ITERATOR,
                                       "<init>",
                                       "("
                                       + NODE_ITERATOR_SIG
                                       + CURRENT_NODE_LIST_FILTER_SIG
                                       + NODE_SIG
                                       + TRANSLET_SIG
                                       + ")V");

                // Backwards branches are prohibited if an uninitialized object
                // is on the stack by section 4.9.4 of the JVM Specification,
                // 2nd Ed.  We don't know whether this code might contain
                // backwards branches, so we mustn't create the new object until
                // after we've created the suspect arguments to its constructor.
                // Instead we calculate the values of the arguments to the
                // constructor first, store them in temporary variables, create
                // the object and reload the arguments from the temporaries to
                // avoid the problem.
                translatePredicates(classGen, methodGen, predicateIndex); // recursive call
                LocalVariableGen iteratorTemp
                        = methodGen.addLocalVariable("step_tmp1",
                                         Util.getJCRefType(NODE_ITERATOR_SIG),
                                         null, null);
                iteratorTemp.setStart(
                        il.append(new ASTORE(iteratorTemp.getIndex())));

                predicate.translateFilter(classGen, methodGen);
                LocalVariableGen filterTemp
                        = methodGen.addLocalVariable("step_tmp2",
                              Util.getJCRefType(CURRENT_NODE_LIST_FILTER_SIG),
                              null, null);
                filterTemp.setStart(
                        il.append(new ASTORE(filterTemp.getIndex())));
                // create new CurrentNodeListIterator
                il.append(new NEW(cpg.addClass(CURRENT_NODE_LIST_ITERATOR)));
                il.append(DUP);

                iteratorTemp.setEnd(
                        il.append(new ALOAD(iteratorTemp.getIndex())));
                filterTemp.setEnd(il.append(new ALOAD(filterTemp.getIndex())));

                il.append(methodGen.loadCurrentNode());
                il.append(classGen.loadTranslet());
                if (classGen.isExternal()) {
                    final String className = classGen.getClassName();
                    il.append(new CHECKCAST(cpg.addClass(className)));
                }
                il.append(new INVOKESPECIAL(idx));
            }
        }
    }

    /**
     * Returns a string representation of this step.
     */
    public String toString() {
        final StringBuffer buffer = new StringBuffer("step(\"");
        buffer.append(Axis.getNames(_axis)).append("\", ").append(_nodeType);
        if (_predicates != null) {
            final int n = _predicates.size();
            for (int i = 0; i < n; i++) {
                final Predicate pred = (Predicate)_predicates.elementAt(i);
                buffer.append(", ").append(pred.toString());
            }
        }
        return buffer.append(')').toString();
    }
}
