/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.axis2.corba.idl.parser;

import antlr.collections.AST;
import org.apache.axis2.corba.exceptions.InvalidIDLException;
import org.apache.axis2.corba.idl.types.ConstType;
import org.apache.axis2.corba.idl.types.DataType;
import org.apache.axis2.corba.idl.types.Typedef;
import org.omg.CORBA.TCKind;
import org.omg.CORBA.TypeCode;

import java.math.BigDecimal;

public class ExpressionUtil {
    public static Object eval(AST expressionNode, DataType returnType, IDLVisitor visitor) throws InvalidIDLException {
        Object value;
        AST node1;
        AST node2;
        int expressionType = expressionNode.getType();
        TypeCode typeCode = returnType.getTypeCode();
        switch (expressionType) {

            case IDLTokenTypes.PLUS:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = node2 == null? eval(node1, returnType, visitor)
                        : add(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.MINUS:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = node2 == null? minus(eval(node1, returnType, visitor), typeCode)
                        : subtract(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.STAR:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = multiply(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.DIV:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = div(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.MOD:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = mod(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.OR:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = or(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.AND:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = and(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.RSHIFT:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = rshift(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.LSHIFT:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = lshift(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.XOR:
                node1 = expressionNode.getFirstChild();
                node2 = node1.getNextSibling();
                value = xor(eval(node1, returnType, visitor), eval(node2, returnType, visitor), typeCode);
                break;

            case IDLTokenTypes.TILDE:
                node1 = expressionNode.getFirstChild();
                Object boolObj = eval(node1, returnType, visitor);
                if (!(boolObj instanceof Boolean)) {
                    throw new InvalidIDLException("A boolean value is expected after (~) operator");
                }
                value = Boolean.valueOf(!((Boolean) boolObj).booleanValue());
                break;

            case IDLTokenTypes.INT:
            case IDLTokenTypes.FLOAT:
            case IDLTokenTypes.STRING_LITERAL:
            case IDLTokenTypes.WIDE_STRING_LITERAL:
            case IDLTokenTypes.CHAR_LITERAL:
            case IDLTokenTypes.WIDE_CHAR_LITERAL:
            case IDLTokenTypes.FIXED:
                value = getValueObject(expressionNode.getText(), returnType);
                break;

            case IDLTokenTypes.LITERAL_TRUE:
                value = Boolean.valueOf(true);
                break;

            case IDLTokenTypes.LITERAL_FALSE:
                value = Boolean.valueOf(true);
                break;

            case IDLTokenTypes.IDENT:
                value = getConstant(expressionNode.getText(), visitor);
                break;
            
            case IDLTokenTypes.LPAREN:
                value = eval(expressionNode.getFirstChild(), returnType, visitor);
                break;

            default:
                throw new InvalidIDLException("Unsupported IDL token " + expressionNode);
        }
        return value;
    }

    private static Object add(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() + ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() + ((Long) o2).longValue());
                break;
            case TCKind._tk_float:
                valueObj = new Float(((Float) o1).floatValue() + ((Float) o2).floatValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() + ((Short) o2).shortValue()));
                break;
            case TCKind._tk_double:
                valueObj = new Double(((Double) o1).doubleValue() + ((Double) o2).doubleValue());
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() + ((Byte) o2).byteValue()));
                break;
            case TCKind._tk_fixed:
                valueObj = ((BigDecimal) o1).add((BigDecimal) o2);
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object minus(Object o, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(-((Integer) o).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(-((Long) o).longValue());
                break;
            case TCKind._tk_float:
                valueObj = new Float(-((Float) o).floatValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (-((Short) o).shortValue()));
                break;
            case TCKind._tk_double:
                valueObj = new Double(- ((Double) o).doubleValue());
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (- ((Byte) o).byteValue()));
                break;
            case TCKind._tk_fixed:
                valueObj = ((BigDecimal) o).multiply(new BigDecimal(-1));
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object subtract(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() - ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() - ((Long) o2).longValue());
                break;
            case TCKind._tk_float:
                valueObj = new Float(((Float) o1).floatValue() - ((Float) o2).floatValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() - ((Short) o2).shortValue()));
                break;
            case TCKind._tk_double:
                valueObj = new Double(((Double) o1).doubleValue() - ((Double) o2).doubleValue());
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() - ((Byte) o2).byteValue()));
                break;
            case TCKind._tk_fixed:
                valueObj = ((BigDecimal) o1).subtract((BigDecimal) o2);
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object multiply(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() * ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() * ((Long) o2).longValue());
                break;
            case TCKind._tk_float:
                valueObj = new Float(((Float) o1).floatValue() * ((Float) o2).floatValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() * ((Short) o2).shortValue()));
                break;
            case TCKind._tk_double:
                valueObj = new Double(((Double) o1).doubleValue() * ((Double) o2).doubleValue());
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() * ((Byte) o2).byteValue()));
                break;
            case TCKind._tk_fixed:
                valueObj = ((BigDecimal) o1).multiply((BigDecimal) o2);
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object div(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() / ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() / ((Long) o2).longValue());
                break;
            case TCKind._tk_float:
                valueObj = new Float(((Float) o1).floatValue() / ((Float) o2).floatValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() / ((Short) o2).shortValue()));
                break;
            case TCKind._tk_double:
                valueObj = new Double(((Double) o1).doubleValue() / ((Double) o2).doubleValue());
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() / ((Byte) o2).byteValue()));
                break;
            case TCKind._tk_fixed:
                valueObj = ((BigDecimal) o1).divide((BigDecimal) o2);
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object rshift(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() >> ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() >> ((Long) o2).longValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() >> ((Short) o2).shortValue()));
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() >> ((Byte) o2).byteValue()));
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object lshift(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() << ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() << ((Long) o2).longValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() << ((Short) o2).shortValue()));
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() << ((Byte) o2).byteValue()));
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object xor(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() ^ ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() ^ ((Long) o2).longValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() ^ ((Short) o2).shortValue()));
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() ^ ((Byte) o2).byteValue()));
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object mod(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() % ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() % ((Long) o2).longValue());
                break;
            case TCKind._tk_float:
                valueObj = new Float(((Float) o1).floatValue() % ((Float) o2).floatValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() % ((Short) o2).shortValue()));
                break;
            case TCKind._tk_double:
                valueObj = new Double(((Double) o1).doubleValue() % ((Double) o2).doubleValue());
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() % ((Byte) o2).byteValue()));
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object or(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() | ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() | ((Long) o2).longValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() | ((Short) o2).shortValue()));
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() | ((Byte) o2).byteValue()));
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object and(Object o1, Object o2, TypeCode returnType) throws InvalidIDLException {
        TCKind kind = returnType.kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(((Integer) o1).intValue() & ((Integer) o2).intValue());
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(((Long) o1).longValue() & ((Long) o2).longValue());
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short((short) (((Short) o1).shortValue() & ((Short) o2).shortValue()));
                break;
            case TCKind._tk_octet:
                valueObj = new Byte((byte) (((Byte) o1).byteValue() & ((Byte) o2).byteValue()));
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token");
        }
        return valueObj;
    }

    private static Object getValueObject(String value, DataType type) throws InvalidIDLException {
        TCKind kind = type.getTypeCode().kind();
        Object valueObj;
        switch (kind.value()) {
            case TCKind._tk_long:
            case TCKind._tk_ulong:
                valueObj = new Integer(value);
                break;
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
                valueObj = new Long(value);
                break;
            case TCKind._tk_float:
                valueObj = new Float(value);
                break;
            case TCKind._tk_short:
            case TCKind._tk_ushort:
                valueObj = new Short(value);
                break;
            case TCKind._tk_char:
            case TCKind._tk_wchar:
                valueObj = new Character(value.charAt(0));
                break;
            case TCKind._tk_double:
                valueObj = new Double(value);
                break;
            case TCKind._tk_octet:
                valueObj = new Byte(value);
                break;
            case TCKind._tk_string:
            case TCKind._tk_wstring:
                valueObj = value;
                break;
            case TCKind._tk_alias:
                Typedef typedef = (Typedef) type;
                valueObj = getValueObject(value, typedef.getDataType());
                break;
            case TCKind._tk_fixed:
                valueObj = new BigDecimal(value);
                break;
            default:
                throw new InvalidIDLException("Unsupported IDL token ");
        }
        return valueObj;
    }

    private static Object getConstant(String expressionName, IDLVisitor visitor) throws InvalidIDLException {
        Object value;
        DataType dataType = visitor.getDataType(expressionName);
        if (dataType != null && dataType instanceof ConstType) {
            ConstType constType = (ConstType) dataType;
            value = constType.getValue();
        } else {
            throw new InvalidIDLException("Constant " + expressionName + " not found.");
        }
        return value;
    }
}

