/*
 * Copyright (C) 2004 TiongHiang Lee
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not,  write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Email: thlee@onemindsoft.org
 */

package org.onemind.jxp;

import org.onemind.commons.java.lang.Null;
/**
 * The evaluator implements several arithmetic operations on objects
 * @author TiongHiang Lee (thlee@onemindsoft.org)
 * 
 */
public final class Evaluator
{

    /** the precision of parameters * */
    private static final short INT_PRECISION = 0, FLOAT_PRECISION = 1,
            LONG_PRECISION = 2, DOUBLE_PRECISION = 3;

    /**
     * Constructor
     */
    private Evaluator()
    {
    }

    /**
     * Plus operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object plus(Object a1, Object a2)
    {        
        if (a1 instanceof String || a1 instanceof Character){
            return a1.toString() + a2;
        } else if (a2 instanceof String || a2 instanceof Character){
            return a1 + a2.toString();
        }
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() + n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() + n2.longValue());
            case FLOAT_PRECISION :
                return new Float(n1.floatValue() + n2.floatValue());
            case DOUBLE_PRECISION :
                return new Double(n1.doubleValue() + n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Get the higest precision among the two number
     * @param n1 the first number
     * @param n2 the second numbe r
     * @return the highest precision
     */
    private static short getPrecision(Number n1, Number n2)
    {
        if (n1 instanceof Double || n2 instanceof Double)
        {
            return DOUBLE_PRECISION;
        } else if (n1 instanceof Float || n2 instanceof Float)
        {
            return FLOAT_PRECISION;
        } else if (n1 instanceof Long || n2 instanceof Long)
        {
            return LONG_PRECISION;
        } else
        { //if (n1 instanceof Integer || n2 instanceof Integer) {
            return INT_PRECISION;
        }
    }

    /**
     * Cast the object to a number
     * @param o the object
     * @return a number object
     */
    private static Number toNumber(Object o)
    {
        if (o instanceof Number)
        {
            return (Number) o;
        } else
        {
            throw new IllegalArgumentException(o + " is not a number");
        }
    }

    /**
     * Minus operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object minus(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() - n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() - n2.longValue());
            case FLOAT_PRECISION :
                return new Float(n1.floatValue() - n2.floatValue());
            case DOUBLE_PRECISION :
                return new Double(n1.doubleValue() - n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Multiply operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object multiply(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() * n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() * n2.longValue());
            case FLOAT_PRECISION :
                return new Float(n1.floatValue() * n2.floatValue());
            case DOUBLE_PRECISION :
                return new Double(n1.doubleValue() * n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Divide operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object divide(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() / n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() / n2.longValue());
            case FLOAT_PRECISION :
                return new Float(n1.floatValue() / n2.floatValue());
            case DOUBLE_PRECISION :
                return new Double(n1.doubleValue() / n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Remainder operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object remainder(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() % n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() % n2.longValue());
            case FLOAT_PRECISION :
                return new Float(n1.floatValue() % n2.floatValue());
            case DOUBLE_PRECISION :
                return new Double(n1.doubleValue() % n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Negation operation
     * @param a1 the first arg
     * @return the result
     */
    public static Object negate(Object a1)
    {
        Number n1 = toNumber(a1);
        switch (getPrecision(n1, n1))
        {
            case INT_PRECISION :
                return new Integer(-n1.intValue());
            case LONG_PRECISION :
                return new Long(-n1.longValue());
            case FLOAT_PRECISION :
                return new Float(-n1.floatValue());
            case DOUBLE_PRECISION :
                return new Double(-n1.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Equality operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Boolean eq(Object a1, Object a2)
    {
        if (a1 instanceof Number && a2 instanceof Number)
        {
            return Boolean.valueOf(a1.equals(a2));
        } else if (a1 instanceof Boolean && a2 instanceof Boolean)
        {
            return Boolean.valueOf(a1.equals(a2));
        } else if ((a1 == null || a1 == Null.instance)
                && (a2 == null || a2 == Null.instance))
        {
            return Boolean.TRUE;
        } else if (a1 instanceof Character && a2 instanceof Character){
            return Boolean.valueOf(a1.equals(a2));
        } else
        {
            return Boolean.valueOf(a1 == a2);
        }
    }

    /**
     * Inequality operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Boolean ne(Object a1, Object a2)
    {
        if (a1 instanceof Number && a2 instanceof Number)
        {
            return Boolean.valueOf(!a1.equals(a2));
        } else if (a1 instanceof Boolean && a2 instanceof Boolean)
        {
            return Boolean.valueOf(!a1.equals(a2));
        } else if ((a1 == null || a1 == Null.instance)
                && (a2 == null || a2 == Null.instance))
        {
            return Boolean.FALSE;
        } else
        {
            return Boolean.valueOf(a1 != a2);
        }
    }

    /**
     * Less than operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Boolean lt(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return Boolean.valueOf(n1.intValue() < n2.intValue());
            case LONG_PRECISION :
                return Boolean.valueOf(n1.longValue() < n2.longValue());
            case FLOAT_PRECISION :
                return Boolean.valueOf(n1.floatValue() < n2.floatValue());
            case DOUBLE_PRECISION :
                return Boolean.valueOf(n1.doubleValue() < n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Less than or equal operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Boolean le(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return Boolean.valueOf(n1.intValue() <= n2.intValue());
            case LONG_PRECISION :
                return Boolean.valueOf(n1.longValue() <= n2.longValue());
            case FLOAT_PRECISION :
                return Boolean.valueOf(n1.floatValue() <= n2.floatValue());
            case DOUBLE_PRECISION :
                return Boolean.valueOf(n1.doubleValue() <= n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Greater than operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Boolean gt(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return Boolean.valueOf(n1.intValue() > n2.intValue());
            case LONG_PRECISION :
                return Boolean.valueOf(n1.longValue() > n2.longValue());
            case FLOAT_PRECISION :
                return Boolean.valueOf(n1.floatValue() > n2.floatValue());
            case DOUBLE_PRECISION :
                return Boolean.valueOf(n1.doubleValue() > n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Greater than or equal operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Boolean ge(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n2))
        {
            case INT_PRECISION :
                return Boolean.valueOf(n1.intValue() >= n2.intValue());
            case LONG_PRECISION :
                return Boolean.valueOf(n1.longValue() >= n2.longValue());
            case FLOAT_PRECISION :
                return Boolean.valueOf(n1.floatValue() >= n2.floatValue());
            case DOUBLE_PRECISION :
                return Boolean.valueOf(n1.doubleValue() >= n2.doubleValue());
            default :
                throw new InternalError("Internal error");
        }
    }

    /**
     * Cast the given object to boolean type
     * @param o the object
     * @return boolean type
     */
    public static Boolean toBoolean(Object o)
    {
        if (o instanceof Boolean)
        {
            return (Boolean) o;
        } else
        {
            throw new IllegalArgumentException(o + " is not boolean");
        }
    }

    /**
     * Bitwise complement operation
     * @param o the object
     * @return the result
     */
    public static Object bitwiseComplement(Object o)
    {
        Number n1 = toNumber(o);
        switch (getPrecision(n1, n1))
        {
            case INT_PRECISION :
                return new Integer(~n1.intValue());
            case LONG_PRECISION :
                return new Long(~n1.longValue());
            default :
                throw new IllegalArgumentException(
                        "Cannot apply bitwise complement operation on float/double value");
        }
    }

    /**
     * Left-shift operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object leftShift(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n1))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() << n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() << n2.intValue());
            default :
                throw new IllegalArgumentException(
                        "Cannot apply << operator on float/double value");
        }
    }

    /**
     * right-signed-shift operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object rightSignedShift(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n1))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() >> n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() >> n2.intValue());
            default :
                throw new IllegalArgumentException(
                        "Cannot apply >> operator on float/double value");
        }
    }

    /**
     * Right-unsigned-shift operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object rightUnsignedShift(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n1))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() >>> n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() >>> n2.intValue());
            default :
                throw new IllegalArgumentException(
                        "Cannot apply >>> operator on float/double value");
        }
    }

    /**
     * Bitwise-and operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object bitwiseAnd(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n1))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() & n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() & n2.intValue());
            default :
                throw new IllegalArgumentException(
                        "Cannot apply & operator on float/double value");
        }
    }

    /**
     * Bitwise-or operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object bitwiseOr(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n1))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() | n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() | n2.intValue());
            default :
                throw new IllegalArgumentException(
                        "Cannot apply | operator on float/double value");
        }
    }

    /**
     * Bitwise-XOR operation
     * @param a1 the first arg
     * @param a2 the second arg
     * @return the result
     */
    public static Object bitwiseXOr(Object a1, Object a2)
    {
        Number n1 = toNumber(a1), n2 = toNumber(a2);
        switch (getPrecision(n1, n1))
        {
            case INT_PRECISION :
                return new Integer(n1.intValue() ^ n2.intValue());
            case LONG_PRECISION :
                return new Long(n1.longValue() ^ n2.intValue());
            default :
                throw new IllegalArgumentException(
                        "Cannot apply ^ operator on float/double value");
        }
    }
}