/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2002-2015 Pentaho
// All Rights Reserved.
*/
package mondrian.olap;

import mondrian.mdx.*;
import mondrian.olap.fun.Resolver;
import mondrian.olap.type.Type;
import mondrian.olap.type.TypeUtil;
import mondrian.resource.MondrianResource;
import mondrian.util.ArrayStack;

import java.util.*;

/**
 * Default implementation of {@link mondrian.olap.Validator}.
 *
 * <p>Uses a stack to help us guess the type of our parent expression
 * before we've completely resolved our children -- necessary,
 * unfortunately, when figuring out whether the "*" operator denotes
 * multiplication or crossjoin.
 *
 * <p>Keeps track of which nodes have already been resolved, so we don't
 * try to resolve nodes which have already been resolved. (That would not
 * be wrong, but can cause resolution to be an <code>O(2^N)</code>
 * operation.)
 *
 * <p>The concrete implementing class needs to implement
 * {@link #getQuery()} and {@link #defineParameter(Parameter)}.
 *
 * @author jhyde
 */
abstract class ValidatorImpl implements Validator {
    protected final ArrayStack<QueryPart> stack = new ArrayStack<QueryPart>();
    private final FunTable funTable;
    private final Map<QueryPart, QueryPart> resolvedNodes =
        new HashMap<QueryPart, QueryPart>();
    private final QueryPart placeHolder = Literal.zero;
    private final Map<FunCall, List<String>> scopeExprs =
        new HashMap<FunCall, List<String>>();

    /**
     * Creates a ValidatorImpl.
     *
     * @param funTable Function table
     *
     * @param resolvedIdentifiers map of already resolved Ids
     * @pre funTable != null
     */
    protected ValidatorImpl(
        FunTable funTable, Map<QueryPart, QueryPart> resolvedIdentifiers)
    {
        Util.assertPrecondition(funTable != null, "funTable != null");
        this.funTable = funTable;
        resolvedNodes.putAll(resolvedIdentifiers);
    }

    public Exp validate(Exp exp, boolean scalar) {
        Exp resolved;
        try {
            resolved = (Exp) resolvedNodes.get(exp);
        } catch (ClassCastException e) {
            // A classcast exception will occur if there is a String
            // placeholder in the map. This is an internal error -- should
            // not occur for any query, valid or invalid.
            throw Util.newInternal(
                e,
                "Infinite recursion encountered while validating '"
                + Util.unparse(exp) + "'");
        }
        if (resolved == null) {
            try {
                stack.push((QueryPart) exp);
                // To prevent recursion, put in a placeholder while we're
                // resolving.
                resolvedNodes.put((QueryPart) exp, placeHolder);
                resolved = exp.accept(this);
                Util.assertTrue(resolved != null);
                resolvedNodes.put((QueryPart) exp, (QueryPart) resolved);
            } finally {
                stack.pop();
            }
        }

        if (scalar) {
            final Type type = resolved.getType();
            if (!TypeUtil.canEvaluate(type)) {
                String exprString = Util.unparse(resolved);
                throw MondrianResource.instance().MdxMemberExpIsSet.ex(
                    exprString);
            }
        }

        return resolved;
    }

    public void validate(ParameterExpr parameterExpr) {
        ParameterExpr resolved =
            (ParameterExpr) resolvedNodes.get(parameterExpr);
        if (resolved != null) {
            return; // already resolved
        }
        try {
            stack.push(parameterExpr);
            resolvedNodes.put(parameterExpr, placeHolder);
            resolved = (ParameterExpr) parameterExpr.accept(this);
            assert resolved != null;
            resolvedNodes.put(parameterExpr, resolved);
        } finally {
            stack.pop();
        }
    }

    public void validate(MemberProperty memberProperty) {
        MemberProperty resolved =
            (MemberProperty) resolvedNodes.get(memberProperty);
        if (resolved != null) {
            return; // already resolved
        }
        try {
            stack.push(memberProperty);
            resolvedNodes.put(memberProperty, placeHolder);
            memberProperty.resolve(this);
            resolvedNodes.put(memberProperty, memberProperty);
        } finally {
            stack.pop();
        }
    }

    public void validate(QueryAxis axis) {
        final QueryAxis resolved = (QueryAxis) resolvedNodes.get(axis);
        if (resolved != null) {
            return; // already resolved
        }
        try {
            stack.push(axis);
            resolvedNodes.put(axis, placeHolder);
            axis.resolve(this);
            resolvedNodes.put(axis, axis);
        } finally {
            stack.pop();
        }
    }

    public void validate(Formula formula) {
        final Formula resolved = (Formula) resolvedNodes.get(formula);
        if (resolved != null) {
            return; // already resolved
        }
        try {
            stack.push(formula);
            resolvedNodes.put(formula, placeHolder);
            formula.accept(this);
            resolvedNodes.put(formula, formula);
        } finally {
            stack.pop();
        }
    }

    public FunDef getDef(
        Exp[] args,
        String funName,
        Syntax syntax)
    {
        // Compute signature first. It makes debugging easier.
        final String signature =
            syntax.getSignature(
                funName, Category.Unknown, ExpBase.getTypes(args));

        // Resolve function by its upper-case name first.  If there is only one
        // function with that name, stop immediately.  If there is more than
        // function, use some custom method, which generally involves looking
        // at the type of one of its arguments.
        List<Resolver> resolvers = funTable.getResolvers(funName, syntax);
        assert resolvers != null;

        final List<Resolver.Conversion> conversionList =
            new ArrayList<Resolver.Conversion>();
        int minConversionCost = Integer.MAX_VALUE;
        List<FunDef> matchDefs = new ArrayList<FunDef>();
        List<Resolver.Conversion> matchConversionList = null;
        for (Resolver resolver : resolvers) {
            conversionList.clear();
            FunDef def = resolver.resolve(args, this, conversionList);
            if (def != null) {
                int conversionCost = sumConversionCost(conversionList);
                if (conversionCost < minConversionCost) {
                    minConversionCost = conversionCost;
                    matchDefs.clear();
                    matchDefs.add(def);
                    matchConversionList =
                        new ArrayList<Resolver.Conversion>(conversionList);
                } else if (conversionCost == minConversionCost) {
                    matchDefs.add(def);
                } else {
                    // ignore this match -- it required more coercions than
                    // other overloadings we've seen
                }
            }
        }
        switch (matchDefs.size()) {
        case 0:
            throw MondrianResource.instance().NoFunctionMatchesSignature.ex(
                signature);
        case 1:
            break;
        default:
            final StringBuilder buf = new StringBuilder();
            for (FunDef matchDef : matchDefs) {
                if (buf.length() > 0) {
                    buf.append(", ");
                }
                buf.append(matchDef.getSignature());
            }
            throw MondrianResource.instance()
                .MoreThanOneFunctionMatchesSignature.ex(
                    signature,
                    buf.toString());
        }

        final FunDef matchDef = matchDefs.get(0);
        for (Resolver.Conversion conversion : matchConversionList) {
            conversion.checkValid();
            conversion.apply(this, Arrays.asList(args));
        }

        return matchDef;
    }

    public boolean alwaysResolveFunDef() {
        return false;
    }

    private int sumConversionCost(
        List<Resolver.Conversion> conversionList)
    {
        int cost = 0;
        for (Resolver.Conversion conversion : conversionList) {
            cost += conversion.getCost();
        }
        return cost;
    }

    public boolean canConvert(
        int ordinal, Exp fromExp, int to, List<Resolver.Conversion> conversions)
    {
        return TypeUtil.canConvert(
            ordinal,
            fromExp.getType(),
            to,
            conversions);
    }

    public boolean requiresExpression() {
        return requiresExpression(stack.size() - 1);
    }

    private boolean requiresExpression(int n) {
        if (n < 1) {
            return false;
        }
        final Object parent = stack.get(n - 1);
        if (parent instanceof Formula) {
            return ((Formula) parent).isMember();
        } else if (parent instanceof ResolvedFunCall) {
            final ResolvedFunCall funCall = (ResolvedFunCall) parent;
            if (funCall.getFunDef().getSyntax() == Syntax.Parentheses) {
                return requiresExpression(n - 1);
            } else {
                int k = whichArg(funCall, (Exp) stack.get(n));
                if (k < 0) {
                    // Arguments of call have mutated since call was placed
                    // on stack. Presumably the call has already been
                    // resolved correctly, so the answer we give here is
                    // irrelevant.
                    return false;
                }
                final FunDef funDef = funCall.getFunDef();
                final int[] parameterTypes = funDef.getParameterCategories();
                return parameterTypes[k] != Category.Set;
            }
        } else if (parent instanceof UnresolvedFunCall) {
            final UnresolvedFunCall funCall = (UnresolvedFunCall) parent;
            if (funCall.getSyntax() == Syntax.Parentheses
                || funCall.getFunName().equals("*"))
            {
                return requiresExpression(n - 1);
            } else {
                int k = whichArg(funCall, (Exp) stack.get(n));
                if (k < 0) {
                    // Arguments of call have mutated since call was placed
                    // on stack. Presumably the call has already been
                    // resolved correctly, so the answer we give here is
                    // irrelevant.
                    return false;
                }
                return requiresExpression(funCall, k);
            }
        } else {
            return false;
        }
    }

    /**
     * Returns whether the <code>k</code>th argument to a function call
     * has to be an expression.
     */
    boolean requiresExpression(
        UnresolvedFunCall funCall,
        int k)
    {
        // The function call has not been resolved yet. In fact, this method
        // may have been invoked while resolving the child. Consider this:
        //   CrossJoin([Measures].[Unit Sales] * [Measures].[Store Sales])
        //
        // In order to know whether to resolve '*' to the multiplication
        // operator (which returns a scalar) or the crossjoin operator
        // (which returns a set) we have to know what kind of expression is
        // expected.
        List<Resolver> resolvers =
            funTable.getResolvers(
                funCall.getFunName(),
                funCall.getSyntax());
        for (Resolver resolver2 : resolvers) {
            if (!resolver2.requiresExpression(k)) {
                // This resolver accepts a set in this argument position,
                // therefore we don't REQUIRE a scalar expression.
                return false;
            }
        }
        return true;
    }

    public FunTable getFunTable() {
        return funTable;
    }

    public Parameter createOrLookupParam(
        boolean definition,
        String name,
        Type type,
        Exp defaultExp,
        String description)
    {
        final SchemaReader schemaReader = getQuery().getSchemaReader(false);
        Parameter param = schemaReader.getParameter(name);

        if (definition) {
            if (param != null) {
                if (param.getScope() == Parameter.Scope.Statement) {
                    ParameterImpl paramImpl = (ParameterImpl) param;
                    paramImpl.setDescription(description);
                    paramImpl.setDefaultExp(defaultExp);
                    paramImpl.setType(type);
                }
                return param;
            }
            param = new ParameterImpl(
                name,
                defaultExp, description, type);

            // Append it to the list of known parameters.
            defineParameter(param);
            return param;
        } else {
            if (param != null) {
                return param;
            }
            throw MondrianResource.instance().UnknownParameter.ex(name);
        }
    }

    private int whichArg(final FunCall node, final Exp arg) {
        final Exp[] children = node.getArgs();
        for (int i = 0; i < children.length; i++) {
            if (children[i] == arg) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Defines a parameter.
     *
     * @param param Parameter
     */
    protected abstract void defineParameter(Parameter param);
}

// End ValidatorImpl.java

