/*
* JBoss, Home of Professional Open Source
* Copyright 2008-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package org.jboss.byteman.rule.expression;

import org.jboss.byteman.rule.type.Type;
import org.jboss.byteman.rule.Rule;
import org.jboss.byteman.rule.grammar.ParseNode;

import java.io.StringWriter;

/**
 * generic operator expression subsumes unary, binary and ternary operators
 */
public abstract class OperExpression extends Expression
{
    OperExpression(Rule rule, int oper, Type type, ParseNode token)
    {
        super(rule, type, token);

        this.oper = oper;
    }

    protected int oper;

    /**
     * return the operand with the given index or null if the index is out of range
     * @param index the index
     * @return the operand with the given index
     */
    public abstract Expression getOperand(int index);

    public void writeTo(StringWriter stringWriter) {
        if ((oper & UNARY) != 0) {
            stringWriter.write(getOperandString());
            stringWriter.write("(");
            getOperand(0).writeTo(stringWriter);
            stringWriter.write(")");
        } else if ((oper & BINARY) != 0) {
            stringWriter.write("(");
            getOperand(0).writeTo(stringWriter);
            stringWriter.write(" ");
            stringWriter.write(getOperandString());
            stringWriter.write(" ");
            getOperand(1).writeTo(stringWriter);
            stringWriter.write(")");
        } else if (oper == COND) {
            // we only have one ternary operator
            stringWriter.write("(");
            getOperand(0).writeTo(stringWriter);
            stringWriter.write(" ? ");
            getOperand(1).writeTo(stringWriter);
            stringWriter.write(" : ");
            getOperand(2).writeTo(stringWriter);
            stringWriter.write(")");
        } else {
            stringWriter.write("*** error unknown operator *** " + oper);
        }
    }

    private String getOperandString()
    {
        for (int i = 0; i < operands.length; i++)
        {
            if (operands[i] == oper) {
                return operandNames[i];
            }
        }
        return "*** error unknown operator ***";
    }

    public static int convertOper(int parserOper)
    {
        for (int i = 0; i < parserOperands.length; i++) {
            if (parserOperands[i] == parserOper) {
                return operands[i];
            }
        }

        return -1;
    }

    final public static int UNARY       = 0x10000;
    final public static int BINARY      = 0x20000;
    final public static int TERNARY     = 0x40000;

    final public static int NOT         = 0x0010 | UNARY;

    final public static int TWIDDLE     = 0x0020 | UNARY;

    final public static int OR          = 0x0040 | BINARY;
    final public static int AND         = 0x0041 | BINARY;

    final public static int EQ          = 0x0080 | BINARY;
    final public static int NE          = 0x0081 | BINARY;
    final public static int GT          = 0x0082 | BINARY;
    final public static int LT          = 0x0083 | BINARY;
    final public static int GE          = 0x0084 | BINARY;
    final public static int LE          = 0x0085 | BINARY;

    final public static int BOR         = 0x0100 | BINARY;
    final public static int BAND        = 0x0101 | BINARY;
    final public static int BXOR        = 0x0102 | BINARY;
    final public static int URSH        = 0x0103 | BINARY;
    final public static int RSH         = 0x0104 | BINARY;
    final public static int LSH         = 0x0105 | BINARY;

    final public static int INSTANCEOF  = 0x0200 | BINARY;

    final public static int UMINUS      = 0x0400 | UNARY;

    final public static int MUL         = 0x0401 | BINARY;
    final public static int DIV         = 0x0402 | BINARY;
    final public static int PLUS        = 0x0403 | BINARY;
    final public static int MINUS       = 0x0404 | BINARY;
    final public static int MOD         = 0x0405 | BINARY;

    final public static int ASSIGN      = 0x0800 | BINARY;

    final public static int COND        = 0x1000 | TERNARY;

    final private static int[] operands = {
            NOT,
            TWIDDLE,
            OR,
            AND,
            EQ,
            NE,
            GT,
            LT,
            GE,
            LE,
            BOR,
            BAND,
            BXOR,
            URSH,
            RSH,
            LSH,
            INSTANCEOF,
            UMINUS,
            MUL,
            DIV,
            PLUS,
            MINUS,
            MOD,
            ASSIGN,
            COND,
    };

    /* parser operands are not allocated rationally so we convert using this table */

    final private static int[] parserOperands = {
            org.jboss.byteman.rule.grammar.ParseNode.NOT,
            org.jboss.byteman.rule.grammar.ParseNode.TWIDDLE,
            org.jboss.byteman.rule.grammar.ParseNode.OR,
            org.jboss.byteman.rule.grammar.ParseNode.AND,
            org.jboss.byteman.rule.grammar.ParseNode.EQ,
            org.jboss.byteman.rule.grammar.ParseNode.NE,
            org.jboss.byteman.rule.grammar.ParseNode.GT,
            org.jboss.byteman.rule.grammar.ParseNode.LT,
            org.jboss.byteman.rule.grammar.ParseNode.GE,
            org.jboss.byteman.rule.grammar.ParseNode.LE,
            org.jboss.byteman.rule.grammar.ParseNode.BOR,
            org.jboss.byteman.rule.grammar.ParseNode.BAND,
            org.jboss.byteman.rule.grammar.ParseNode.BXOR,
            org.jboss.byteman.rule.grammar.ParseNode.URSH,
            org.jboss.byteman.rule.grammar.ParseNode.RSH,
            org.jboss.byteman.rule.grammar.ParseNode.LSH,
            org.jboss.byteman.rule.grammar.ParseNode.INSTANCEOF,
            org.jboss.byteman.rule.grammar.ParseNode.UMINUS,
            org.jboss.byteman.rule.grammar.ParseNode.MUL,
            org.jboss.byteman.rule.grammar.ParseNode.DIV,
            org.jboss.byteman.rule.grammar.ParseNode.PLUS,
            org.jboss.byteman.rule.grammar.ParseNode.MINUS,
            org.jboss.byteman.rule.grammar.ParseNode.MOD,
            org.jboss.byteman.rule.grammar.ParseNode.ASSIGN,
            org.jboss.byteman.rule.grammar.ParseNode.TERNOP
    };

    final private static String[] operandNames = {
            "!",
            "~",
            "||",
            "&&",
            "==",
            "!=",
            ">",
            "<",
            ">=",
            "<=",
            "|",
            "&",
            "^",
            ">>>",
            ">>",
            "<<",
            "instanceof",
            "-",
            "*",
            "/",
            "+",
            "-",
            "%",
            "=",
            "? :"
    };
}
