// Copyright (c) 2006, Oracle. All rights reserved.  
package oracle.toplink.essentials.extension.spatial;

import java.util.Vector;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.expressions.FunctionExpression;
import oracle.toplink.essentials.internal.helper.ClassConstants;
import oracle.toplink.essentials.internal.helper.NonSynchronizedVector;


/**
 * PUBLIC:
 * <b>Purpose</b>: Provide a definition for spatial expression operators and a
 * factory for constructing expressions for usage in queries.<p>
 *
 * Oracle DB 10.2 Spatial Documentation {link}http://download-west.oracle.com/docs/cd/B19306_01/appdev.102/b14255/toc.htm{link}
 *
 * @author Doug Clarke
 * @since TopLink Essentials v2b41
 */
public enum SpatialOperator {

    /**
     * SDO_WITHIN_DISTANCE(geometry1, geometry2, params)
     * Determines if two geometries are within a specified distance from one another.
     */
    WITHIN_DISTANCE("MDSYS.SDO_WITHIN_DISTANCE", 3, "TRUE"),

    /**
     * SDO_RELATE(geometry1, geometry2, params)
     * Determines whether or not two geometries interact in a specified way.
     */
    RELATE("MDSYS.SDO_RELATE", 3, "TRUE"),

    /**
     * SDO_FILTER(geometry1, geometry2, params)
     * Specifies which geometries may interact with a given geometry.
     */
    FILTER("MDSYS.SDO_FILTER", 3, "TRUE"),

    /**
     * SDO_NN(geometry1, geometry2, params)
     * Determines the nearest neighbor geometries to a geometry
     */
    NN("MDSYS.SDO_NN", 3, "TRUE"),

    /**
     * SDO_NN_DISTANCE(nnOpIndex)
     * Returns the distance of an object returned by the NN operator.
     */
    NN_DISTANCE("MDSYS.SDO_NN_DISTANCE", 1),

    // Convenience Operators for SDO_RELATE Operations
    ANYINTERACT("MDSYS.SDO_ANYINTERACT", 2),
    CONTAINS("MDSYS.SDO_CONTAINS", 2),
    COVEREDBY("MDSYS.SDO_COVEREDBY", 2),
    COVERS("MDSYS.SDO_COVERS", 2),
    EQUAL("MDSYS.SDO_EQUAL", 2),
    INSIDE("MDSYS.SDO_INSIDE", 2),
    ON("MDSYS.SDO_ON", 2),
    OVERLAPBDYDISJOINT("MDSYS.SDO_OVERLAPBDYDISJOINT", 2),
    OVERLAPBDYINTERSECT("MDSYS.SDO_OVERLAPBDYINTERSECT", 2),
    OVERLAPS("MDSYS.SDO_OVERLAPS", 2),
    TOUCH("MDSYS.SDO_TOUCH", 2),

    /**
     * SDO_GEOM.RELATE(geomtry1, mask, geometry2, tolerance)
     * Determines how two objects interact.
     */
    GEOM_RELATE("SDO_GEOM.RELATE", 4),
    ;

    private final String operatorName;
    private final ExpressionOperator operator;
    private final int numberofArguments;

    /**
     * Value used for comparison of the VARCHAR returned from the operator/function.
     * In many cases 'TRUE' is returned so the expression must additionally do
     * a comparison value for this to get a boolean value for use in the overall
     * selection criteria.
     */
    private final String defaultResultCompareValue;

    SpatialOperator(String name, int numberofArgs,
                    String defaultResultCompareValue) {
        this.operatorName = name;
        this.numberofArguments = numberofArgs;
        this.defaultResultCompareValue = defaultResultCompareValue;
        this.operator = createOperator(name, numberofArgs);
    }

    SpatialOperator(String name, int numberofArgs) {
        this(name, numberofArgs, null);
    }

    /**
     * Construct the singleton expression operator required for building of
     * function expresssions which are used in the selection criteria of queries
     */
    public static ExpressionOperator createOperator(String operatorName,
                                                    int numArgs) {
        ExpressionOperator op = new ExpressionOperator();
        op.setType(ExpressionOperator.FunctionOperator);
        Vector v = NonSynchronizedVector.newInstance(numArgs + 1);
        v.addElement(operatorName + "(");
        for (int argCount = 1; argCount < numArgs; argCount++) {
            v.addElement(", ");
        }
        v.addElement(")");
        op.printsAs(v);
        op.bePrefix();
        op.setNodeClass(ClassConstants.FunctionExpression_Class);

        return op;
    }

    public String getOperatorName() {
        return this.operatorName;
    }

    public int getNumberOfArguments() {
        return this.numberofArguments;
    }

    public String getDefaultResultCompareValue() {
        return this.defaultResultCompareValue;
    }

    public ExpressionOperator getOperator() {
        return this.operator;
    }

    /**
     * Build an expression for the most common spatial operator format.
     *
     * @param geom1 expression referenceing a geometry attribute
     * @param geom2 an expression for a second geometry or a JGeometry instance
     * @param params a populated SpatialParameters or NULL
     *
     * @return an Expression representing the spatial operator for use in the
     *         selection criteria of a named or dynamic TopLink query
     */
    public Expression buildExpression(Expression geom1, Object geom2,
                                      SpatialParameters params) {
        return buildExpression(geom1.getBuilder(),
                               new Object[] { geom1, geom2, params == null ?
                                                            null :
                                                            params.toString() });
    }

    /**
     * Helper method to simplify building an expression where no result comaprison
     * to a boolean is required or the default comparison to TRUE is wanted.
     */
    public Expression buildExpression(ExpressionBuilder builder,
                                      Object[] arguments) {
        return buildExpression(builder, arguments,
                               getDefaultResultCompareValue());
    }

    /**
     * Construct an expression using the operator and adding all of the provided
     * arguments.
     *
     * @param builder base ExpressionBuilder for the query
     * @param arguments an array of values matching the defined operator
     * @param resultCompareValue specifies if the operator requires comparison to a
     *        boolean value and what that value is. If null the operator's
     *        requiredBooleanComparison value is used.
     *        
     * @return an Expression for use in a named or dynamic query
     */
    public Expression buildExpression(ExpressionBuilder builder,
                                      Object[] arguments,
                                      String resultCompareValue) {
        if (arguments == null || arguments.length != getNumberOfArguments()) {
            throw new IllegalArgumentException("");
        }

        return buildExpression(builder, getOperator(), arguments,
                               resultCompareValue);
    }

    public static Expression buildExpression(ExpressionBuilder builder,
                                             ExpressionOperator operator,
                                             Object[] arguments,
                                             String resultCompareValue) {
        FunctionExpression expression = new FunctionExpression();
        expression.setBaseExpression(builder);

        for (int index = 0; index < arguments.length; index++) {
            expression.addChild(Expression.from(arguments[index], builder));
        }

        expression.setOperator(operator);

        Expression resultExp = expression;

        if (resultCompareValue != null) {
            resultExp = resultExp.equal(resultCompareValue);
        }

        return resultExp;
    }

    /**
     * Create an expression using adynamically created operator. This method is
     * intended for use when the set of operators supported does not include one
     * that is required or a custom operator is in use.
     */
    public static Expression buildExpression(ExpressionBuilder builder,
                                             String operatorName,
                                             Object[] arguments,
                                             String resultCompareValue) {
        ExpressionOperator operator =
            createOperator(operatorName, arguments.length);
        return buildExpression(builder, operator, arguments,
                               resultCompareValue);
    }
}