/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the "License").  You may not use this file except
 * in compliance with the License.
 *
 * You can obtain a copy of the license at
 * glassfish/bootstrap/legal/CDDLv1.0.txt or
 * https://glassfish.dev.java.net/public/CDDLv1.0.html.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * HEADER in each file and include the License file at
 * glassfish/bootstrap/legal/CDDLv1.0.txt.  If applicable,
 * add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your
 * own identifying information: Portions Copyright [yyyy]
 * [name of copyright owner]
 */

// Copyright (c) 1998, 2006, Oracle. All rights reserved.  
package oracle.toplink.essentials.internal.expressions;

import java.io.*;
import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.databaseaccess.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.AbstractSession;

/**
 * Used for expressions that have 0 to n children.
 * These include not, between and all functions.
 */
public class FunctionExpression extends Expression {
    protected Expression baseExpression;
    protected Vector children;
    protected ExpressionOperator operator;
    protected transient ExpressionOperator platformOperator;
    protected Class resultType;

    public FunctionExpression() {
        this.children = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2);
        this.resultType = null;
    }

    public void addChild(Expression child) {
        getChildren().addElement(child);
    }

    /**
     * INTERNAL:
     * Find the alias for a given table
     */
    public DatabaseTable aliasForTable(DatabaseTable table) {
        return getBaseExpression().aliasForTable(table);
    }

    /**
     * INTERNAL:
     */
    public Expression create(Expression base, Object singleArgument, ExpressionOperator operator) {
        baseExpression = base;
        addChild(base);
        Expression arg = Expression.from(singleArgument, base);
        addChild(arg);
        setOperator(operator);
        return this;
    }
    
    /**
     * INTERNAL: 
     * added for Trim support.  TRIM([trim_character FROM] string_primary)
     */
    public Expression createWithBaseLast(Expression base, Object singleArgument, ExpressionOperator anOperator) {
        baseExpression = base;
        Expression arg = Expression.from(singleArgument, base);
        addChild(arg);
        addChild(base);
        setOperator(anOperator);
        return this;
    }

    /**
     * INTERNAL:
     */
    public Expression create(Expression base, Vector arguments, ExpressionOperator operator) {
        baseExpression = base;
        addChild(base);
        for (Enumeration e = arguments.elements(); e.hasMoreElements();) {
            Expression arg = Expression.from(e.nextElement(), base);
            addChild(arg);
        }
        setOperator(operator);
        return this;
    }

    /**
     * INTERNAL:
     * Used for debug printing.
     */
    public String descriptionOfNodeType() {
        return "Function";
    }

    /**
     * INTERNAL:
     * Check if the object conforms to the expression in memory.
     * This is used for in-memory querying.
     * If the expression in not able to determine if the object conform throw a not supported exception.
     */
    public boolean doesConform(Object object, AbstractSession session, AbstractRecord translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy, boolean isObjectUnregistered) {
        // Must check for NOT and negate entire base expression.
        if (getOperator().getSelector() == ExpressionOperator.Not) {
            return !getBaseExpression().doesConform(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);
        }

        // Conform between or in function.
        if ((getOperator().getSelector() == ExpressionOperator.Between) || (getOperator().getSelector() == ExpressionOperator.NotBetween)
                ||(getOperator().getSelector() == ExpressionOperator.In) || (getOperator().getSelector() == ExpressionOperator.NotIn)) {
            // Extract the value from the left side.
            Object leftValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);

            // Extract the value from the arguments, skip the first child which is the base.
            Vector rightValue = new Vector(getChildren().size());
            for (int index = 1; index < getChildren().size(); index++) {
                Object valueFromRight;
                if (getChildren().elementAt(index) instanceof Expression) {
                    valueFromRight = ((Expression)getChildren().elementAt(index)).valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);
                } else {      
                    valueFromRight = getChildren().elementAt(index);
                }
                //If valueFromRight is a Vector, then there is only one child other than the base, e.g. valueFromRight is a collection of constants.  
                //Then it should be the vector to be compared with.  Don't add it to another collection.
                if (valueFromRight instanceof Vector) {
                    rightValue = (Vector)valueFromRight;
                //Single values should be added to the rightValue, which will be compared with leftValue.
                } else {
                    rightValue.addElement(valueFromRight);
                }

            }

            // If left is anyof collection of values, check each one.
            // If the right had an anyof not supported will be thrown from the operator.
            if (leftValue instanceof Vector) {
                for (Enumeration leftEnum = (Enumeration)((Vector)leftValue).elements();
                         leftEnum.hasMoreElements();) {
                    Object tempLeft = leftEnum.nextElement();
                    if (getOperator().doesRelationConform(tempLeft, rightValue)) {
                        return true;
                    }
                }

                // Only return false if none of the values match.
                return false;
            } else {
                return getOperator().doesRelationConform(leftValue, rightValue);
            }
        } else if ((getOperator().getSelector() == ExpressionOperator.IsNull) || (getOperator().getSelector() == ExpressionOperator.NotNull)) {
            // Extract the value from the left side.
            Object leftValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);

            // If left is anyof collection of values, check each one.
            if (leftValue instanceof Vector) {
                for (Enumeration leftEnum = (Enumeration)((Vector)leftValue).elements();
                         leftEnum.hasMoreElements();) {
                    Object tempLeft = leftEnum.nextElement();
                    if (getOperator().doesRelationConform(tempLeft, null)) {
                        return true;
                    }
                }

                // Only return false if none of the values match.
                return false;
            } else {
                return getOperator().doesRelationConform(leftValue, null);
            }
        }

        // No other relation functions are supported.
        // Non-relation functions are supported through valueFromObject().
        throw QueryException.cannotConformExpression();
    }

    public Expression getBaseExpression() {
        return baseExpression;
    }

    /**
     * Return the expression builder which is the ultimate base of this expression, or
     * null if there isn't one (shouldn't happen if we start from a root)
     */
    public ExpressionBuilder getBuilder() {
        if (getBaseExpression() == null) {
            return null;
        }
        return getBaseExpression().getBuilder();
    }

    public Vector getChildren() {
        return children;
    }

    /**
     * INTERNAL: Not to be confused with the public getField(String)
     * This returns a collection of all fields associated with this object. Really
     * only applies to query keys representing an object or to expression builders.
     *
     */
    public Vector getFields() {
        return getBaseExpression().getFields();
    }

    public ExpressionOperator getOperator() {
        return operator;
    }

    public ExpressionOperator getPlatformOperator(DatabasePlatform platform) {
        if (platformOperator == null) {
            initializePlatformOperator(platform);
        }
        return platformOperator;
    }
    
    public Class getResultType() {
        return resultType;
    }

    public boolean hasResultType() {
        return resultType != null;
    }
    
    /**
     * INTERNAL:
     */
    public void initializePlatformOperator(DatabasePlatform platform) {
        if (getOperator().isComplete()) {
            platformOperator = getOperator();
            return;
        }
        platformOperator = platform.getOperator(getOperator().getSelector());
        if (platformOperator == null) {
            throw QueryException.invalidOperator(getOperator().toString());
        }
    }

    public boolean isFunctionExpression() {
        return true;
    }

    /**
     * INTERNAL:
     * Return if the represents an object comparison.
     */
    protected boolean isObjectComparison() {
        if (getChildren().size() != 1) {
            return false;
        }

        int selector = getOperator().getSelector();
        if ((selector != ExpressionOperator.IsNull) && (selector != ExpressionOperator.NotNull)) {
            return false;
        }

        Expression base = getBaseExpression();
        return (base.isObjectExpression() && (!((ObjectExpression)base).isAttribute()));
    }

    /**
     * INTERNAL:
     * For iterating using an inner class
     */
    public void iterateOn(ExpressionIterator iterator) {
        super.iterateOn(iterator);
        for (Enumeration childrenEnum = getChildren().elements(); childrenEnum.hasMoreElements();) {
            Expression child = (Expression)childrenEnum.nextElement();
            child.iterateOn(iterator);
        }
    }

    /**
     * INTERNAL:
     * Normalize into a structure that is printable.
     * Also compute printing information such as outer joins.
     * This checks for object isNull and notNull comparisons.
     */
    public Expression normalize(ExpressionNormalizer normalizer) {
        //This method has no validation but we should still make the method call for consistency
        //bug # 2956674
        //validation is moved into normalize to ensure that expressions are valid before we attempt to work with them
        validateNode();
        if (getChildren().isEmpty()) {
            return this;
        }

        if (!isObjectComparison()) {
            for (int index = 0; index < getChildren().size(); index++) {
                getChildren().setElementAt(((Expression)getChildren().elementAt(index)).normalize(normalizer), index);
            }
            return this;
        } else {
            //if not normalising we must still validate the corresponding node to make sure that they are valid
            //bug # 2956674
            for (int index = 0; index < getChildren().size(); index++) {
                ((Expression)getChildren().elementAt(index)).validateNode();
            }
        }

        // This code is executed only in the case of an is[not]Null on an
        // object attribute.
        ObjectExpression base = (ObjectExpression)getBaseExpression();

        // For cr2334, fix code so that normalize is first called on base expressions.
        // I.e. if base itself had a base expression this expression would not be normalized.
        base.getBaseExpression().normalize(normalizer);

        // Switch to null foreign key comparison (i.e. get('c').isNull() to getField('C_ID').isNull()).
        // For bug 3105559 also must handle aggregates: get("period").isNull();
        Expression foreignKeyJoin = base.getMapping().buildObjectJoinExpression(base, (Object)null, getSession());

        if (getOperator().getSelector() == ExpressionOperator.NotNull) {
            foreignKeyJoin = foreignKeyJoin.not();
        }
        return foreignKeyJoin;
    }

    /**
     * INTERNAL:
     * Used for cloning.
     */
    protected void postCopyIn(Dictionary alreadyDone) {
        super.postCopyIn(alreadyDone);

        baseExpression = baseExpression.copiedVersionFrom(alreadyDone);
        Vector oldChildren = children;
        children = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        for (int i = 0; i < oldChildren.size(); i++) {
            addChild((Expression)(((Expression)oldChildren.elementAt(i)).copiedVersionFrom(alreadyDone)));
        }
    }

    /**
     * INTERNAL:
     * Print SQL
     */
    public void printSQL(ExpressionSQLPrinter printer) {
        ExpressionOperator realOperator;
        realOperator = getPlatformOperator(printer.getPlatform());
        //In or NotIn operator only comes with " IN (" and ")" since the number of elements are unknown.  Need to go through 
        //the collection to print out each element.
        if (realOperator.getSelector() == ExpressionOperator.In || realOperator.getSelector() == ExpressionOperator.NotIn) {
            printInCollection(getChildren(), printer, realOperator);
        } else {
            realOperator.printCollection(getChildren(), printer);
        }
    }

    /**
     * INTERNAL: Print the collection in "IN" onto the SQL stream.  Go through the collection to print out each element.
     */
    public void printInCollection(Vector items, ExpressionSQLPrinter printer, ExpressionOperator operator) {        
        ((Expression)items.elementAt(0)).printSQL(printer);
        printer.printString(operator.getDatabaseStrings()[0]);
        
        for (int i = 1; i < items.size() - 1; i++) {
            Expression item = (Expression)items.elementAt(i);
            item.printSQL(printer);
            printer.printString(", ");
        }
        
        ((Expression)items.elementAt(items.size() - 1)).printSQL(printer);
        printer.printString(operator.getDatabaseStrings()[1]);
    }

    /**
     * INTERNAL:
     * Print java for project class generation
     */
    public void printJava(ExpressionJavaPrinter printer) {
        ExpressionOperator realOperator = getPlatformOperator(printer.getPlatform());
        realOperator.printJavaCollection(getChildren(), printer);
    }

    /**
     * INTERNAL:
     * This expression is built on a different base than the one we want. Rebuild it and
     * return the root of the new tree
     */
    public Expression rebuildOn(Expression newBase) {
        Expression newLocalBase = getBaseExpression().rebuildOn(newBase);
        Vector newChildren = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(getChildren().size());
        for (int i = 1; i < getChildren().size(); i++) {// Skip the first one, since it's also the base
            newChildren.addElement(((Expression)children.elementAt(i)).rebuildOn(newBase));
        }
        newLocalBase.setSelectIfOrderedBy(getBaseExpression().selectIfOrderedBy());
        FunctionExpression rebuilt = (FunctionExpression) newLocalBase.performOperator(getOperator(), newChildren);
        rebuilt.setResultType(this.getResultType()); //copy over result type.
        return rebuilt;
    }

    /**
     * Sets the base of the expression
     * Added to allow creation of functions which take the column name
     * as something other than the first option.
     * Creation date: (9/11/00 4:04:03 PM)
     * @param expression oracle.toplink.essentials.expressions.Expression
     */
    public void setBaseExpression(Expression expression) {
        baseExpression = expression;
    }

    // Set the local base expression, ie the one on the other side of the operator
    // Most types will ignore this, since they don't need it.
    public void setLocalBase(Expression exp) {
        getBaseExpression().setLocalBase(exp);
    }

    public void setOperator(ExpressionOperator theOperator) {
        operator = theOperator;
    }
    
    public void setResultType(Class resultType) {
        this.resultType = resultType;
    }

    /**
     * INTERNAL:
     * Rebuild myself against the base, with the values of parameters supplied by the context
     * expression. This is used for transforming a standalone expression (e.g. the join criteria of a mapping)
     * into part of some larger expression. You normally would not call this directly, instead calling twist
     * See the comment there for more details"
     */
    public Expression twistedForBaseAndContext(Expression newBase, Expression context) {
        if (getChildren().isEmpty()) {
            return (Expression)clone();
        }
        Vector newChildren = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(getChildren().size());

        // For functions the base is the first child, we only want the arguments so start at the second.
        for (int index = 1; index < getChildren().size(); index++) {
            newChildren.addElement(((Expression)children.elementAt(index)).twistedForBaseAndContext(newBase, context));
        }

        // Aply the function to the twisted old base.
        Expression oldBase = (Expression)getChildren().elementAt(0);
        return oldBase.twistedForBaseAndContext(newBase, context).performOperator(getOperator(), newChildren);
    }

    /**
     * INTERNAL:
     * Return the value for in memory comparison.
     * This is only valid for valueable expressions.
     */
    public Object valueFromObject(Object object, AbstractSession session, AbstractRecord translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy, boolean isObjectUnregistered) {
        Object baseValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);
        Vector arguments = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(getChildren().size());
        for (int index = 1; index < getChildren().size(); index++) {
            if (getChildren().elementAt(index) instanceof Expression) {
                arguments.addElement(((Expression)getChildren().elementAt(index)).valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered));
            } else {
                arguments.addElement(getChildren().elementAt(index));
            }
        }
        if (baseValue instanceof Vector) {// baseValue might be a vector, so the individual values must be extracted before applying the function call to them
            Vector baseVector = new Vector();
            for (Enumeration valuesToCompare = (Enumeration)((Vector)baseValue).elements();
                     valuesToCompare.hasMoreElements();) {
                Object baseObject = (Object)valuesToCompare.nextElement();
                if (baseObject == null) {
                    baseVector.addElement(baseObject);
                } else {
                    baseVector.addElement(getOperator().applyFunction(baseObject, arguments));
                }
            }
            return baseVector;
        } else {
            // Do not apply functions to null, just leave as null.
            if (baseValue == null) {
                return null;
            } else {
                return getOperator().applyFunction(baseValue, arguments);
            }
        }
    }

    /**
     * INTERNAL:
     * Used to print a debug form of the expression tree.
     */
    public void writeDescriptionOn(BufferedWriter writer) throws IOException {
        writer.write(operator.toString());
    }

    /**
     * INTERNAL: called from SQLSelectStatement.writeFieldsFromExpression(...)
     */
    public void writeFields(ExpressionSQLPrinter printer, Vector newFields, SQLSelectStatement statement) {
        //print ", " before each selected field except the first one
        if (printer.isFirstElementPrinted()) {
            printer.printString(", ");
        } else {
            printer.setIsFirstElementPrinted(true);
        }

        if (getBaseExpression().isDataExpression()) {
            DatabaseField field = ((DataExpression)getBaseExpression()).getField();
            if (field == null) {
                // This means the select wants a *.
                field = new DatabaseField("*");
                field.setType(getResultType());
            } else {
                // Set the result type to the type specified for this function
                // expression, if one has been set. Otherwise, use the mappings
                // database field type.
                if (hasResultType()) {
                    field = (DatabaseField) field.clone();
                    field.setType(getResultType());
                }
            }
            
            newFields.addElement(field);
        } else {
            // This field is a complex function value so any name can be used.
            DatabaseField field = new DatabaseField("*");
            field.setType(getResultType());
            newFields.addElement(field);
        }

        printSQL(printer);
    }

    /**
     * INTERNAL:
     * Used in SQL printing.
     */
    public void writeSubexpressionsTo(BufferedWriter writer, int indent) throws IOException {
        if (baseExpression != null) {
            baseExpression.toString(writer, indent);
        }
    }
}