////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.expr.parser;

import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.LocalParamSetter;
import net.sf.saxon.expr.sort.DocumentSorter;
import net.sf.saxon.functions.Current;
import net.sf.saxon.functions.Reverse;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.SequenceType;

/**
* PromotionOffer is an object used transiently during compilation of an expression. It contains
* information passed by a containing expression to its subexpressions, when looking for subexpressions
* that can be promoted to a higher level because they are not dependent on the context established
* by the containing expression. The object is also used to pass back return information when the
* promotion actually takes place.
*/

public class PromotionOffer  {

    /**
    * FOCUS_INDEPENDENT requests promotion of all non-trivial subexpressions that don't depend on the
    * focus. This is typically used to extract subexpressions from a filter predicate. The offer is
    * optional - each subexpression can decide whether it's worth the trouble of promoting itself.
    * The offer is normally passed on to subexpressions, except subexpressions that are evaluated
    * with a different focus
    */

    public static final int FOCUS_INDEPENDENT = 10;

    /**
    * RANGE_INDEPENDENT requests promotion of all non-trivial subexpressions that don't depend on a
    * specified range variable. This is typically used to extract subexpressions from the action of
    * a for expression or the condition of a some/every quantified expression. The offer is
    * optional - each subexpression can decide whether it's worth the trouble of promoting itself.
    * The offer is normally passed on to subexpressions, except subexpressions that are evaluated
    * with a different focus or a different range variable, because these may have other dependencies
    * that prevent their promotion.
    */

    public static final int RANGE_INDEPENDENT = 11;

    /**
     * Inline variable references causes all references to a variable V to be replaced by the
     * expression E. The variable is supplied in the "binding" property; the replacement expression
     * in the containingExpression property. A special case is where the replacement expression is
     * a ContextItemExpression; in this case the offer is not passed on to subexpressions where
     * the context is different.
    */

    public static final int INLINE_VARIABLE_REFERENCES = 12;

    /**
     * UNORDERED indicates that the containing expression does not require the results
     * to be delivered in any particular order. The boolean mustEliminateDuplicates
     * is set if duplicate items in the result are not allowed.
     */

    public static final int UNORDERED = 13;

    /**
     * REPLACE_CURRENT causes calls to the XSLT current() function to be replaced by
     * reference to a variable. The variable binding is the single member of the array bindingList
     */

    public static final int REPLACE_CURRENT = 14;

    /**
     * EXTRACT_GLOBAL_VARIABLES identifies subexpressions that are not constant, but have no dependencies
     * other than on global variables or parameters (they must also be non-creative). Such expressions can
     * be extracted from a function or template and converted into global variables. This optimization is done
     * in Saxon-EE only.
     */

    public static final int EXTRACT_GLOBAL_VARIABLES = 15;

    /**
     * The optimizer in use
     */

    private Optimizer optimizer;

    /**
     * The expression visitor in use
     */

    public ExpressionVisitor visitor;

    /**
    * action is one of the possible promotion actions, FOCUS_INDEPENDENT, RANGE_INDEPENDENT,
    * INLINE_VARIABLE_REFERENCES, UNORDERED, EXTRACT_GLOBAL_VARIABLES
    */

    public int action;

    /**
    * In the case of FOCUS_INDEPENDENT, "promoteDocumentDependent" is a boolean that, when set to
    * true, indicates that it is safe to promote a subexpression that depends on the context document
    * but not on other aspects of the focus. This is the case, for example, in a filter expression when
    * it is known that all the nodes selected by the expression will be in the same document - as happens
    * when the filter is applied to a path expression. This allows subexpressions such as key() to be
    * promoted
    */

    public boolean promoteDocumentDependent = false;

    /**
     * In the case of FOCUS_INDEPENDENT, "promoteXSLTFunctions" is a boolean that, when set to true, indicates
     * that it is safe to promote XSLT functions such as current(). This flag is set when rewriting XPath expressions
     * and is unset when rewriting XSLT templates.
     */

    public boolean promoteXSLTFunctions = true;

    /**
     * In the case of UNORDERED, "retainAllNodes" is a boolean that is set to
     * true if the nodes can be delivered in any order so long as the right number of nodes
     * are delivered. If this boolean is false, the caller doesn't care whether duplicate nodes
     * are retained or whether they are eliminated.
     */

    public boolean retainAllNodes = true;

    /**
    * In the case of RANGE_INDEPENDENT, "binding" identifies the range variables whose dependencies
    * we are looking for. For INLINE_VARIABLE_REFERENCES it is a single Binding that we are aiming to inline
    */

    /*@Nullable*/ public Binding[] bindingList;

    /**
    * When a promotion offer is made, containingExpression identifies the level to which the promotion
    * should occur. When a subexpression is promoted, an expression of the form let $VAR := SUB return ORIG
    * is created, and this replaces the original containingExpression within the PromotionOffer.
    */

    public Expression containingExpression;

    /**
     * Flag that is set if the offer has been accepted, that is, if the expression has changed
     */

    public boolean accepted = false;

    /**
     * Create a PromotionOffer for use with a particular Optimizer
     * @param optimizer the optimizer
     */

    public PromotionOffer(Optimizer optimizer) {
        this.optimizer = optimizer;
    }

    /**
     * Get the optimizer in use
     * @return the optimizer
     */

    public Optimizer getOptimizer() {
        return optimizer;
    }

    /**
     * Method to test whether a subexpression qualifies for promotion, and if so, to
     * accept the promotion.
     * @param parent
     * @param child the subexpression in question
     * @return if promotion was done, returns the expression that should be used in place
     * of the child expression. If no promotion was done, returns null. If promotion is
     * determined not to be necessary for this subtree, returns the supplied child expression
     * unchanged
     */

    public Expression accept(Expression parent, Expression child) throws XPathException {
        switch (action) {
            case RANGE_INDEPENDENT: {
                int properties = child.getSpecialProperties();
                if (((properties & StaticProperty.NON_CREATIVE) != 0) &&
                        !ExpressionTool.dependsOnVariable(child, bindingList) &&
                        (child.getDependencies() &
                                (StaticProperty.HAS_SIDE_EFFECTS | StaticProperty.DEPENDS_ON_ASSIGNABLE_GLOBALS)) == 0) {
                    return promote(parent, child);
                }
                break;
            }

            case FOCUS_INDEPENDENT: {
                int dependencies = child.getDependencies();
                int properties = child.getSpecialProperties();
                if (!promoteXSLTFunctions && ((dependencies & StaticProperty.DEPENDS_ON_XSLT_CONTEXT) != 0)) {
                    break;
                }
                if (ExpressionTool.dependsOnVariable(child, bindingList)) {
                    break;
                }
                if ((dependencies &
                        (StaticProperty.HAS_SIDE_EFFECTS | StaticProperty.DEPENDS_ON_ASSIGNABLE_GLOBALS)) != 0) {
                    break;
                }
                if ((dependencies & StaticProperty.DEPENDS_ON_FOCUS) == 0 &&
                        (properties & StaticProperty.NON_CREATIVE) != 0) {
                    return promote(parent, child);
                } else if (promoteDocumentDependent &&
                        (dependencies & StaticProperty.DEPENDS_ON_NON_DOCUMENT_FOCUS) == 0 &&
                        (properties & StaticProperty.NON_CREATIVE) != 0) {
                    return promote(parent, child);
                }
                break;
            }

            case REPLACE_CURRENT: {
                if (child instanceof Current) {
                    LocalVariableReference var = new LocalVariableReference((Assignation)containingExpression);
                    ExpressionTool.copyLocationInfo(child, var);
                    return var;
                } else if (!ExpressionTool.callsFunction(child, Current.FN_CURRENT)) {
                    return child;
                }
                break;
            }

            case INLINE_VARIABLE_REFERENCES: {
                if (child instanceof VariableReference &&
                    ((VariableReference)child).getBinding() == bindingList[0]) {
                    try {
                        Expression copy = containingExpression.copy();
                        ExpressionTool.copyLocationInfo(child, copy);
                        return copy;
                    } catch (UnsupportedOperationException err) {
                        // If we can't make a copy, return the original. This is safer than it seems,
                        // because on the paths where this happens, we are merely moving the expression from
                        // one place to another, not replicating it
                        return containingExpression;
                    }
                }
                break;
            }
            case UNORDERED: {
                if (child instanceof Reverse) {
                    return ((Reverse)child).getArguments()[0];
                } else if (child instanceof DocumentSorter && !retainAllNodes) {
                    return ((DocumentSorter)child).getBaseExpression();
                }
                break;
            }
            case EXTRACT_GLOBAL_VARIABLES:
                if (!(child instanceof Literal || child instanceof LocalParamSetter ||
                        (child == containingExpression) ||
                        ExpressionTool.containsLocalParam(child)) &&
                        (child.getDependencies()&~StaticProperty.DEPENDS_ON_RUNTIME_ENVIRONMENT) == 0 &&
                        (child.getSpecialProperties() & StaticProperty.NON_CREATIVE) != 0) {
                    return optimizer.extractGlobalVariables(child, visitor, this);
                }
                break;

            default:
                throw new UnsupportedOperationException("Unknown promotion action " + action);
        }
        return null;
    }

    /**
     * Method to promote a subexpression. A LetExpression is created which binds the child expression
     * to a system-created variable, and then returns the original expression, with the child expression
     * replaced by a reference to the variable.
     * @param child the expression to be promoted
     * @return the expression that results from the promotion, if any took place
    */

    private Expression promote(Expression parent, Expression child) {
        final TypeHierarchy th = optimizer.getConfiguration().getTypeHierarchy();

        // if the expression being promoted is an operand of "=", make the variable an indexed variable
        boolean indexed = false;
        //Expression parent = containingExpression.findParentOf(child);
        if (parent instanceof GeneralComparison && ((GeneralComparison)parent).getOperator() == Token.EQUALS) {
            indexed = true;
        }

        LetExpression let = new LetExpression();
        let.setVariableQName(new StructuredQName("zz", NamespaceConstant.SAXON, "zz" + let.hashCode()));
        SequenceType type = SequenceType.makeSequenceType(child.getItemType(th), child.getCardinality());
        let.setRequiredType(type);
        ExpressionTool.copyLocationInfo(containingExpression, let);
        //let.setSequence(LazyExpression.makeLazyExpression(child));
        let.setSequence(child);
        let.setEvaluationMode(
                Cardinality.allowsMany(child.getCardinality()) ?
                        ExpressionTool.MAKE_MEMO_CLOSURE :
                        ExpressionTool.MAKE_SINGLETON_CLOSURE);
        let.setAction(containingExpression);
        let.adoptChildExpression(containingExpression);
        if (indexed) {
            let.setIndexedVariable();
        }
        containingExpression = let;
        accepted = true;
        LocalVariableReference var = new LocalVariableReference(let);
        int properties = child.getSpecialProperties()&StaticProperty.NOT_UNTYPED_ATOMIC;
        var.setStaticType(type, null, properties);
        ExpressionTool.copyLocationInfo(containingExpression, var);
        return var;
    }


}

