/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2000-2009 Sun Microsystems, Inc. All rights reserved. 
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License ("CDDL") (collectively, the "License").  You may
 * not use this file except in compliance with the License.  You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or mq/legal/LICENSE.txt.  See the License for the specific language
 * governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at mq/legal/LICENSE.txt.  Sun designates
 * this particular file as subject to the "Classpath" exception as provided by
 * Sun in the GPL Version 2 section of the License file that accompanied this
 * code.  If applicable, add the following below the License Header, with the
 * fields enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or  to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright holder. 
 */

/*
 * @(#)NumericValue.java	1.5 06/28/07
 */ 

package com.sun.messaging.jmq.jmsselector;

/**
 * Wrapper class for a NumericValue that provides math operations on numbers
 * 
 * This class is a necessary because Java does not support arithmetic operations
 * in java.lang.Number
 */
public class NumericValue {
    Number value = null;
    String image = null;
    int imageType = 0;
    
    private final static int ByteValue = 0;
    private final static int ShortValue = 1;
    private final static int IntValue = 2;
    private final static int FloatValue = 3;
    final static int LongValue = 4;
    final static int DoubleValue = 5;

    private final static int[][] returnTypes =  {
        {ByteValue, ShortValue, IntValue, FloatValue, LongValue, DoubleValue},
        {ShortValue, ShortValue, IntValue, FloatValue, LongValue, DoubleValue},
        {IntValue, IntValue, IntValue, FloatValue, LongValue, DoubleValue},
        {FloatValue, FloatValue, FloatValue, FloatValue, FloatValue, DoubleValue},
        {LongValue, LongValue, LongValue, FloatValue, LongValue, DoubleValue},
        {DoubleValue, DoubleValue, DoubleValue, DoubleValue, DoubleValue, DoubleValue}            
    };

    public NumericValue(Object obj) throws ClassCastException {
        //It is OK for a NumericValue to be null. If not null it must be a Number sub-class
        
        if (value != null && !(obj instanceof java.lang.Number)) {
            throw new ClassCastException("Cannot make a NumericValue from a " + obj.getClass().getName());
        }
        if (obj instanceof NumericValue) {
            NumericValue n = (NumericValue) obj;
            this.value = n.value;
            this.image = n.image;
            this.imageType = n.imageType;
        } else {
            this.value = (Number)obj;        
        }
    }
    
    public NumericValue(String image, int imageType) {
        this.image = image;
        this.imageType = imageType;
        this.value = null;
    }

    public Number getValue() {
        if (value == null && image != null) {
            switch (imageType) {
            case DoubleValue:
                value = new Double(image);
                break;
            case LongValue:
                //XX:JAVA2
                value = Long.decode(image);
                //XX:JAVA1.1
                //value = new Long(image);
                break;
            default:
            }
            image = null;
            imageType = -1;
        }

        return value;
    }
    
    private int getIndexForType(Number obj) {

        int index = -1;
        Class objClass = obj.getClass();
        if (objClass == Byte.class) {
            index = ByteValue;
        } else if (objClass == Short.class) {
            index = ShortValue;
        } else if (objClass == Integer.class) {
            index = IntValue;
        } else if (objClass == Float.class) {
            index = FloatValue;
        } else if (objClass == Long.class) {
            index = LongValue;
        } else if (objClass == Double.class) {
            index = DoubleValue;
        } else {
                System.err.println("Unexpected failure in getIndexForType objClass=" + 
                         obj.getClass().getName());
        }
    
        return index;
    }
    
    int getUnifiedTypeIndex(Number val1, Number val2) {
        int index = -1;
                    
        if ((val1 != null) && (val2 != null)) {
            int index1 = getIndexForType(val1);
            int index2 = getIndexForType(val2);
            index = returnTypes[index1][index2];
        }
        return index;
    }
    
    private Number convertNumber(Number val, int typeIndex) {
        switch (typeIndex) {
        case ByteValue:
            return new Byte(val.byteValue());
        case ShortValue:
            return new Short(val.shortValue());
        case IntValue:
            return new Integer(val.intValue());
        case LongValue:
            return new Long(val.longValue());
        case DoubleValue:
            return new Double(val.doubleValue());
        default:
            return null;
        }
    }


    //XX:JAVA2 can be removed in favor of Comparator.compare(obj1, obj2)
    public boolean between(NumericValue num1, NumericValue num2) {
        boolean result = false;

        if (getValue() != null && num1 != null && num2 != null) {
            Number value1 = num1.getValue();
            Number value2 = num2.getValue();
            int typeIndex1 = getUnifiedTypeIndex(value, value1);
            int typeIndex2 = getUnifiedTypeIndex(value, value2);
            /*
            if (typeIndex1 != -1) {
                System.err.println("between:num1 is of type" + indexToTypeMap[typeIndex1]);
            } else {
                System.err.println("between:num1 is undefined");
            }
            if (typeIndex1 != -1) {
                System.err.println("between:num2 is of type" + indexToTypeMap[typeIndex2]);
            } else {
                System.err.println("between:num2 is undefined");
            }
            */
            int typeIndex = (typeIndex1 >= typeIndex2 ? typeIndex1 : typeIndex2);

            //System.err.println("between:GCType is " + indexToTypeMap[typeIndex]);

            Number val = convertNumber(value, typeIndex);
            Number val1 = convertNumber(value1, typeIndex);
            Number val2 = convertNumber(value2, typeIndex);

            //System.err.println("value = " + value + " val1 = " + val1);
            //System.err.println("value2 = " + value2 + " val2 = " + val2);

            switch (typeIndex) {
                case ByteValue:
                    result = ((val.byteValue() >= val1.byteValue()) && (val.byteValue() <= val2.byteValue()) ||
                              (val.byteValue() <= val1.byteValue()) && (val.byteValue() >= val2.byteValue()));
                    break;
                case ShortValue:
                    result = ((val.shortValue() >= val1.shortValue()) && (val.shortValue() <= val2.shortValue()) ||
                              (val.shortValue() <= val1.shortValue()) && (val.shortValue() >= val2.shortValue()));
                    break;
                case IntValue:
                    result = ((val.intValue() >= val1.intValue()) && (val.intValue() <= val2.intValue()) ||
                              (val.intValue() <= val1.intValue()) && (val.intValue() >= val2.intValue()));
                    break;
                case FloatValue:
                    result = ((val.floatValue() >= val1.floatValue()) && (val.floatValue() <= val2.floatValue()) ||
                              (val.floatValue() <= val1.floatValue()) && (val.floatValue() >= val2.floatValue()));
                    break;
                case LongValue:
                    result = ((val.longValue() >= val1.longValue()) && (val.longValue() <= val2.longValue()) ||
                              (val.longValue() <= val1.longValue()) && (val.longValue() >= val2.longValue()));
                    break;
                case DoubleValue:
                    result = ((val.doubleValue() >= val1.doubleValue()) && (val.doubleValue() <= val2.doubleValue()) ||
                              (val.doubleValue() <= val1.doubleValue()) && (val.doubleValue() >= val2.doubleValue()));
                    break;
                default:
                    //Undefined value
                    break;
            }
        }
        
        return result;
    }

    public Number add(NumericValue num) {
        Number result = null;
        
        if (getValue() != null && num != null) {
            Number value2 = num.getValue();
            int typeIndex = getUnifiedTypeIndex(value, value2);
        
            Number val1 = convertNumber(value, typeIndex);
            Number val2 = convertNumber(value2, typeIndex);
        
            switch (typeIndex) {
                case ByteValue:
                    result = new Byte((byte)(val1.byteValue() + val2.byteValue()));
                    break;
                case ShortValue:
                    result = new Short((short)(val1.shortValue() + val2.shortValue()));
                    break;
                case IntValue:
                    result = new Integer(val1.intValue() + val2.intValue());
                    break;
                case FloatValue:
                    result = new Float(val1.floatValue() + val2.floatValue());
                    break;
                case LongValue:
                    result = new Long(val1.longValue() + val2.longValue());
                    break;
                case DoubleValue:
                    result = new Double(val1.doubleValue() + val2.doubleValue());
                    break;
                default:
                    //Undefined value
                    break;
            }
        }
        
        return result;
    }

    public Number subtract(NumericValue num) {
        Number result = null;

        if (getValue() != null && num != null) {
            Number value2 = num.getValue();
            int typeIndex = getUnifiedTypeIndex(value, value2);
        
            Number val1 = convertNumber(value, typeIndex);
            Number val2 = convertNumber(value2, typeIndex);
        
            switch (typeIndex) {
                case ByteValue:
                    result = new Byte((byte)(val1.byteValue() - val2.byteValue()));
                    break;
                case ShortValue:
                    result = new Short((short)(val1.shortValue() - val2.shortValue()));
                    break;
                case IntValue:
                    result = new Integer(val1.intValue() - val2.intValue());
                    break;
                case FloatValue:
                    result = new Float(val1.floatValue() - val2.floatValue());
                    break;
                case LongValue:
                    result = new Long(val1.longValue() - val2.longValue());
                    break;
                case DoubleValue:
                    result = new Double(val1.doubleValue() - val2.doubleValue());
                    break;
                default:
                    //Undefined value
                    break;
            }
        }
        return result;
    }

    public Number multiply(NumericValue num) {
        Number result = null;
                
        if (getValue() != null && num != null) {
            Number value2 = num.getValue();
            int typeIndex = getUnifiedTypeIndex(value, value2);
        
            Number val1 = convertNumber(value, typeIndex);
            Number val2 = convertNumber(value2, typeIndex);
        
            switch (typeIndex) {
                case ByteValue:
                    result = new Byte((byte)(val1.byteValue() * val2.byteValue()));
                    break;
                case ShortValue:
                    result = new Short((short)(val1.shortValue() * val2.shortValue()));
                    break;
                case IntValue:
                    result = new Integer(val1.intValue() * val2.intValue());
                    break;
                case FloatValue:
                    result = new Float(val1.floatValue() * val2.floatValue());
                    break;
                case LongValue:
                    result = new Long(val1.longValue() * val2.longValue());
                    break;
                case DoubleValue:
                    result = new Double(val1.doubleValue() * val2.doubleValue());
                    break;
                default:
                    //Undefined value
                    break;
            }
        }
        
        return result;
    }

    public Number divide(NumericValue num) {
        Number result = null;
                
        if (getValue() != null && num != null) {
            Number value2 = num.getValue();
            int typeIndex = getUnifiedTypeIndex(value, value2);
        
            Number val1 = convertNumber(value, typeIndex);
            Number val2 = convertNumber(value2, typeIndex);
        
            switch (typeIndex) {
                case ByteValue:
                    result = new Byte((byte)(val1.byteValue() / val2.byteValue()));
                    break;
                case ShortValue:
                    result = new Short((short)(val1.shortValue() / val2.shortValue()));
                    break;
                case IntValue:
                    result = new Integer(val1.intValue() / val2.intValue());
                    break;
                case FloatValue:
                    result = new Float(val1.floatValue() / val2.floatValue());
                    break;
                case LongValue:
                    result = new Long(val1.longValue() / val2.longValue());
                    break;
                case DoubleValue:
                    result = new Double(val1.doubleValue() / val2.doubleValue());
                    break;
                default:
                    //Undefined value
                    break;
            }
        }
        return result;
    }

    public Number negate() {
        Number result = null;
                    
        if (image != null) {
            image = "-" + image;
            return getValue();
        }

        if (getValue() != null) {
            int typeIndex = getIndexForType(value);
            
            switch (typeIndex) {
                case ByteValue:
                    result = new Byte((byte)(-value.byteValue()));
                    break;
                case ShortValue:
                    result = new Short((short)(-value.shortValue()));
                    break;
                case IntValue:
                    result = new Integer(-value.intValue());
                    break;
                case FloatValue:
                    result = new Float(-value.floatValue());
                    break;
                case LongValue:
                    result = new Long(-value.longValue());
                    break;
                case DoubleValue:
                    result = new Double(-value.doubleValue());
                    break;
                default:
                    //Undefined value
                    break;
            }
        }
        return result;
    }
    
    public String toString() {
        String str = "null";
        
        if (getValue() != null) {
            str = value.toString();
        }
        return str;
    }
    
/* public static void main(String[] args) {
.       NumericValue num1 = new NumericValue(new Float(3.65));
.       NumericValue num2 = new NumericValue(new Long(100L));
.       System.err.println(num1 + "+" + num2 + " = " + num1.add(num2));
.       System.err.println(num2 + "+" + num1 + " = " + num2.add(num1));
.
.       System.err.println(num1 + "-" + num2 + " = " + num1.subtract(num2));
.       System.err.println(num2 + "-" + num1 + " = " + num2.subtract(num1));
.       
.       System.err.println(num1 + "*" + num2 + " = " + num1.multiply(num2));
.       System.err.println(num2 + "*" + num1 + " = " + num2.multiply(num1));
.       
.       System.err.println(num1 + "/" + num2 + " = " + num1.divide(num2));
.       System.err.println(num2 + "/" + num1 + " = " + num2.divide(num1));
.       
.       System.err.println("-" + num1 + " = " + num1.negate());
.       System.err.println("-" + num2 + " = " + num2.negate());
.       
.
.       System.exit(0);
.   }
*/    

}
