/*
* 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-2013 Pentaho Corporation..  All rights reserved.
*/

package mondrian.calc;

import mondrian.calc.impl.BetterExpCompiler;
import mondrian.olap.*;
import mondrian.olap.type.Type;
import mondrian.util.CreationException;
import mondrian.util.ObjectFactory;

import org.eigenbase.util.property.StringProperty;

import java.util.List;

/**
 * Mediates the compilation of an expression ({@link mondrian.olap.Exp})
 * into a compiled expression ({@link Calc}).
 *
 * @author jhyde
 * @since Sep 28, 2005
 */
public interface ExpCompiler {

    /**
     * Returns the evaluator to be used for evaluating expressions during the
     * compilation process.
     */
    Evaluator getEvaluator();

    /**
     * Returns the validator which was used to validate this expression.
     *
     * @return validator
     */
    Validator getValidator();

    /**
     * Compiles an expression.
     *
     * @param exp Expression
     * @return Compiled expression
     */
    Calc compile(Exp exp);

    /**
     * Compiles an expression to a given result type.
     *
     * <p>If <code>resultType</code> is not null, casts the expression to that
     * type. Throws an exception if that conversion is not allowed by the
     * type system.
     *
     * <p>The <code>preferredResultStyles</code> parameter specifies a list
     * of desired result styles. It must not be null, but may be empty.
     *
     * @param exp Expression
     *
     * @param resultType Desired result type, or null to use expression's
     *                   current type
     *
     * @param preferredResultStyles List of result types, in descending order
     *                   of preference. Never null.
     *
     * @return Compiled expression, or null if none can satisfy
     */
    Calc compileAs(
        Exp exp,
        Type resultType,
        List<ResultStyle> preferredResultStyles);

    /**
     * Compiles an expression which yields a {@link Member} result.
     */
    MemberCalc compileMember(Exp exp);

    /**
     * Compiles an expression which yields a {@link Level} result.
     */
    LevelCalc compileLevel(Exp exp);

    /**
     * Compiles an expression which yields a {@link Dimension} result.
     */
    DimensionCalc compileDimension(Exp exp);

    /**
     * Compiles an expression which yields a {@link Hierarchy} result.
     */
    HierarchyCalc compileHierarchy(Exp exp);

    /**
     * Compiles an expression which yields an <code>int</code> result.
     * The expression is implicitly converted into a scalar.
     */
    IntegerCalc compileInteger(Exp exp);

    /**
     * Compiles an expression which yields a {@link String} result.
     * The expression is implicitly converted into a scalar.
     */
    StringCalc compileString(Exp exp);

    /**
     * Compiles an expression which yields a {@link java.util.Date} result.
     * The expression is implicitly converted into a scalar.
     */
    DateTimeCalc compileDateTime(Exp exp);

    /**
     * Compiles an expression which yields an immutable {@link TupleList}
     * result.
     *
     * <p>Always equivalent to <code>{@link #compileList}(exp, false)</code>.
     */
    ListCalc compileList(Exp exp);

    /**
     * Compiles an expression which yields {@link TupleList} result.
     *
     * <p>Such an expression is generally a list of {@link Member} objects or a
     * list of tuples (each represented by a {@link Member} array).
     *
     * <p>See {@link #compileList(mondrian.olap.Exp)}.
     *
     * @param exp Expression
     * @param mutable Whether resulting list is mutable
     */
    ListCalc compileList(Exp exp, boolean mutable);

    /**
     * Compiles an expression which yields an immutable {@link Iterable} result.
     *
     * @param exp Expression
     * @return Calculator which yields an Iterable
     */
    IterCalc compileIter(Exp exp);

    /**
     * Compiles an expression which yields a <code>boolean</code> result.
     *
     * @param exp Expression
     * @return Calculator which yields a boolean
     */
    BooleanCalc compileBoolean(Exp exp);

    /**
     * Compiles an expression which yields a <code>double</code> result.
     *
     * @param exp Expression
     * @return Calculator which yields a double
     */
    DoubleCalc compileDouble(Exp exp);

    /**
     * Compiles an expression which yields a tuple result.
     *
     * @param exp Expression
     * @return Calculator which yields a tuple
     */
    TupleCalc compileTuple(Exp exp);

    /**
     * Compiles an expression to yield a scalar result.
     *
     * <p>If the expression yields a member or tuple, the calculator will
     * automatically apply that member or tuple to the current dimensional
     * context and return the value of the current measure.
     *
     * @param exp Expression
     * @param specific Whether to try to use the specific compile method for
     *   scalar types. For example, if <code>specific</code> is true and
     *   <code>exp</code> is a string expression, calls
     *   {@link #compileString(mondrian.olap.Exp)}
     * @return Calculation which returns the scalar value of the expression
     */
    Calc compileScalar(Exp exp, boolean specific);

    /**
     * Implements a parameter, returning a unique slot which will hold the
     * parameter's value.
     *
     * @param parameter Parameter
     * @return Slot
     */
    ParameterSlot registerParameter(Parameter parameter);

    /**
     * Returns a list of the {@link ResultStyle}s
     * acceptable to the caller.
     */
    List<ResultStyle> getAcceptableResultStyles();

    /**
     * The <code>ExpCompiler.Factory</code> is used to access
     * <code>ExpCompiler</code> implementations. Each call returns
     * a new instance. This factory supports overriding the default
     * instance by use of a <code>ThreadLocal</code> and by defining a
     * <code>System</code> property with the <code>ExpCompiler</code>
     * class name.
     */
    public static final class Factory extends ObjectFactory<ExpCompiler> {
        private static final Factory factory;
        private static final Class[] CLASS_ARRAY;
        static {
            factory = new Factory();
            CLASS_ARRAY = new Class[] {
                Evaluator.class,
                Validator.class,
                ResultStyle[].class,
            };
        }

        /**
         * Create a <code>ExpCompiler</code> instance, each call returns a
         * new compiler.
         *
         * @param evaluator the <code>Evaluator</code> to use with the compiler
         * @param validator the <code>Validator</code> to use with the compiler
         * @return the new <code>ExpCompiler</code> compiler
         * @throws CreationException if the compiler can not be created
         */
        public static ExpCompiler getExpCompiler(
            final Evaluator evaluator,
            final Validator validator)
            throws CreationException
        {
            return getExpCompiler(evaluator, validator, ResultStyle.ANY_LIST);
        }

        /**
         *
         *
         * @param evaluator the <code>Evaluator</code> to use with the compiler
         * @param validator the <code>Validator</code> to use with the compiler
         * @param resultStyles the initial <code>ResultStyle</code> array
         * for the compiler
         * @return the new <code>ExpCompiler</code> compiler
         * @throws CreationException if the compiler can not be created
         */
        public static ExpCompiler getExpCompiler(
            final Evaluator evaluator,
            final Validator validator,
            final List<ResultStyle> resultStyles)
                throws CreationException
        {
            return factory.getObject(
                CLASS_ARRAY,
                new Object[] {
                    evaluator,
                    validator,
                    resultStyles
                });
        }

        /**
         * <code>ThreadLocal</code> used to hold the class name of an
         * <code>ExpCompiler</code> implementation.
         * Generally, this should only be used for testing.
         */
        private static final ThreadLocal<String> ClassName =
            new ThreadLocal<String>();

        /**
         * Get the class name of a <code>ExpCompiler</code> implementation
         * or null.
         *
         * @return the class name or null.
        */
        public static String getThreadLocalClassName() {
            return ClassName.get();
        }
        /**
         * Sets the class name of a  <code>ExpCompiler</code> implementation.
         * This should be called (obviously) before calling the
         * <code>ExpCompiler.Factory</code> <code>getExpCompiler</code>
         * method to get the <code>ExpCompiler</code> implementation.
         * Generally, this is only used for testing.
         *
         * @param className Class name
         */
        public static void setThreadLocalClassName(String className) {
            ClassName.set(className);
        }
        /**
         * Clears the class name (regardless of whether a class name was set).
         * When a class name is set using <code>setThreadLocalClassName</code>,
         * the setting whould be done in a try-block and a call to this
         * clear method should be in the finally-clause of that try-block.
         */
        public static void clearThreadLocalClassName() {
            ClassName.set(null);
        }

        /**
         * The constructor for the <code>ExpCompiler.Factory</code>.
         * This passes the <code>ExpCompiler</code> class to the
         * <code>ObjectFactory</code> base class.
        */
        private Factory() {
            super(ExpCompiler.class);
        }
        /**
         * Get the class name set in the <code>ThreadLocal</code> or null.
         *
         * @return class name or null.
         */
        protected String getClassName() {
            return getThreadLocalClassName();
        }

        /**
         * Return the <code>ExpCompiler.Factory</code property name.
         *
         * @return <code>ExpCompiler.Factory</code> property name
         */
        protected StringProperty getStringProperty() {
            return MondrianProperties.instance().ExpCompilerClass;
        }
        /**
         * The <code>ExpCompiler.Factory</code>'s implementation of the
         * <code>ObjectFactory</code>'s abstract method which returns
         * the default <code>ExpCompiler</code> instance.
         *
         * @param parameterTypes array of classes: Evaluator, Validator and
         *  ResultStyle
         * @param parameterValues  the Evaluator, Validator and ResultStyle
         * values
         * @return <code>ExpCompiler</code> instance
         * @throws CreationException if the <code>ExpCompiler</code> can not be
         * created.
         */
        protected ExpCompiler getDefault(
            final Class[] parameterTypes,
            final Object[] parameterValues)
            throws CreationException
        {
            // Strong typed above so don't need to check here
            Evaluator evaluator = (Evaluator) parameterValues[0];
            Validator validator = (Validator) parameterValues[1];
            List<ResultStyle> resultStyles =
                (List<ResultStyle>) parameterValues[2];

            // Here there is bleed-through from the "calc.impl" implementation
            // directory into the "calc" interface definition directory.
            // This can be avoided if we were to use reflection to
            // create this the default ExpCompiler implementation.
            return new BetterExpCompiler(
                evaluator, validator, resultStyles);
        }

        /**
         * Get the underlying Factory object.
         * <p>
         * This is for testing only.
         *
         * @return the <code>ExpCompiler.Factory</code> object
         */
        public static Factory getFactory() {
            return factory;
        }

        /**
         * Get the current override contect.
         * <p>
         * This is for testing only.
         *
         * @return the override context object.
         */
        public Object removeContext() {
            return new Context();
        }

        /**
         * Restore the current overrides.
         * <p>
         * This is for testing only.
         *
         * @param context the current override object.
         */
        public void restoreContext(final Object context) {
            if (context instanceof Context) {
                ((Context) context).restore();
            }
        }

        /**
         * The <code>ExpCompiler</code> only has two override mechanisms: the
         * <code>ThreadLocal</code> and <code>System</code>
         * <code>Properties</code>. This class captures and clears the current
         * values for both in the constructor and then replaces them
         * in the <code>restore</code> method.
         * <p>
         * This is for testing only.
         */
        public static class Context implements ObjectFactory.Context {
            private final String threadLocalClassName;
            private final String systemPropertyClassName;

            /**
             * Creates a Context.
             */
            Context() {
                this.threadLocalClassName =
                        ExpCompiler.Factory.getThreadLocalClassName();
                if (this.threadLocalClassName != null) {
                    ExpCompiler.Factory.clearThreadLocalClassName();
                }

                this.systemPropertyClassName =
                        System.getProperty(ExpCompiler.class.getName());
                if (this.systemPropertyClassName != null) {
                    System.getProperties().remove(ExpCompiler.class.getName());
                }
            }

            /**
             * Restores the previous context.
             */
            private void restore() {
                if (this.threadLocalClassName != null) {
                    ExpCompiler.Factory.setThreadLocalClassName(
                        this.threadLocalClassName);
                }
                if (this.systemPropertyClassName != null) {
                    System.setProperty(
                        ExpCompiler.class.getName(),
                        this.systemPropertyClassName);
                }
            }
        }
    }
}

// End ExpCompiler.java
