package org.jacorb.idl;

/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1997-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 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
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


import java.util.HashMap;

/**
 * Based on the MathEvaluator class by The-Son LAI,
 * <a href="mailto:Lts@writeme.com">Lts@writeme.com</a>
 *
 * <i>Expression evaluator for IDL constant expression</i>
 *
 * Supports the following functions:
 * <code>+, -, *, /, ^, %, ^, | , t {@literal <<},{@literal >>}</code><br>
 *
 * When the getValue() is called, a Double object is returned.
 * If it returns null, an error occured.<p>

 * @author  Gerald Brose,
 *          The-Son LAI <a href="mailto:Lts@writeme.com">Lts@writeme.com</a>
 */

public class ConstExprEvaluator
{
    private static   Operator[]   operators = null;
    private   Node    node                    = null;
    private   String  expression              = null;

    /**
     * creates a ConstExprEvaluator and assign the expression string.
     */

    public ConstExprEvaluator(String s)
    {
       if ( operators == null )
       {
          initializeOperators();
       }
       expression = s;
    }


    /**
     * evaluates and returns the value of the expression
     */

    public Double getValue()
    {
        if (expression == null)
        {
            return null;
        }

        try
        {
            node = new Node(expression);
            return evaluate(node);
        }
        catch (Exception e)
        {
            lexer.emit_error("unexpected exception: " + e.getMessage());
            e.printStackTrace();
            // TODO throw exception?
            return null;
        }
    }

    private static Double evaluate(Node n)
    {
        if ( n.hasOperator() && n.hasChild() )
        {
            if ( n.getOperator().getType() == 1 )
                n.setValue ( evaluateExpression( n.getOperator(), evaluate( n.getLeft() ), null ) );
            else if ( n.getOperator().getType() == 2 )
            {
                n.setValue( evaluateExpression( n.getOperator(), evaluate( n.getLeft() ), evaluate( n.getRight() ) ) );
            }
        }
        return n.getValue();
    }


    private static Double evaluateExpression( Operator o, Double f1, Double f2)
    {
        String op   = o.getOperator();
        Double res   = null;

        if     ( "+".equals(op) )
            res = new Double( f1.doubleValue() + f2.doubleValue() );
        else if  ( "-".equals(op) )
            res = new Double( f1.doubleValue() - f2.doubleValue() );
        else if  ( "*".equals(op) )
            res = new Double( f1.doubleValue() * f2.doubleValue() );
        else if  ( "/".equals(op) )
            res = new Double( f1.doubleValue() / f2.doubleValue() );
        else if  ( "%".equals(op) )
            res = new Double( f1.doubleValue() % f2.doubleValue() );
        else if  ( "|".equals(op) )
            res =   new Double(Double.longBitsToDouble( Double.doubleToLongBits( f1.doubleValue()) | Double.doubleToLongBits( f2.doubleValue() ) ));
        else if  ( "&".equals(op) )
            res =  new Double( Double.longBitsToDouble( Double.doubleToLongBits( f1.doubleValue() ) & Double.doubleToLongBits( f2.doubleValue() ) ));
        else if  ( "^".equals(op) )
            res =  new Double( Double.longBitsToDouble( Double.doubleToLongBits( f1.doubleValue() ) ^ Double.doubleToLongBits( f2.doubleValue() ) ));
        else if  ( "<<".equals(op) )
            res =  new Double( Double.longBitsToDouble( Double.doubleToLongBits( f1.doubleValue() ) << Double.doubleToLongBits( f2.doubleValue() ) ));
        else if  ( ">>".equals(op) )
            res =  new Double( Double.longBitsToDouble( Double.doubleToLongBits( f1.doubleValue() ) >> Double.doubleToLongBits( f2.doubleValue() ) ));

        return res;
    }

    private void initializeOperators()
    {
        operators     = new Operator[10];

        // bit-wise operators
        operators[0]  = new Operator("|"  , 2, 0);

        operators[1]  = new Operator("^"  , 2, 2);

        operators[2]  = new Operator("&"  , 2, 4);

        operators[3]  = new Operator(">>"  , 2, 6);
        operators[4]  = new Operator("<<"  , 2, 6);
        // arithmetic operators
        operators[5]  = new Operator("+"  , 2, 8);
        operators[6]  = new Operator("-"  , 2, 8);

        operators[7]  = new Operator("*"  , 2, 10);
        operators[8]  = new Operator("/"  , 2, 10);
        operators[9]  = new Operator("%"  , 2, 10);
    }

    private Double getDouble(String s)
    {
        if ( s == null )
            return null;

        Double res = null;
        try
        {
            res = new Double(Double.parseDouble(s));
        }
        catch(Exception e)
        {
        }

        return res;
    }


    static protected class Operator
    {
        private String op;
        private int type;
        private int priority;

        public Operator(String o, int t, int p)
        {
            op = o;
            type = t;
            priority = p;
        }

        public String getOperator()
        {
            return op;
        }

        public void setOperator(String o)
        {
            op = o;
        }

        public int getType()
        {
            return type;
        }

        public int getPriority()
        {
            return priority;
        }
    }

    protected class Node
    {
        public String   nString  = null;
        public Operator nOperator   = null;
        public Node   nLeft  = null;
        public Node   nRight  = null;
        public Node   nParent  = null;
        public int  nLevel  = 0;
        public Double    nValue  = null;

        public Node(String s) throws Exception
        {
            init(null, s, 0);
        }

        public Node(Node parent, String s, int level) throws Exception
        {
            init(parent, s, level);
        }

        private void init(Node parent, String s, int level) throws Exception
        {
            s = removeIllegalCharacters(s);
            s = removeBrackets(s);
            s = addZero(s);
            if ( checkBrackets(s) != 0 )
                throw new Exception("Wrong number of brackets in [" + s + "]");

            nParent          = parent;
            nString     = s;
            nValue    = getDouble(s);
            nLevel     = level;
            int sLength    = s.length();
            int inBrackets  = 0;
            int startOperator   = 0;

            for (int i=0; i<sLength; i++)
            {
                if ( s.charAt(i) == '(' )
                    inBrackets++;
                else if ( s.charAt(i) == ')' )
                    inBrackets--;
                else
                {
                    // the expression must be at "root" level
                    if ( inBrackets == 0 )
                    {
                        Operator o = getOperator(nString,i);
                        if ( o != null )
                        {
                            // if first operator or lower priority operator
                            if ( nOperator == null || nOperator.getPriority() >= o.getPriority() )
                            {
                                nOperator   = o;
                                startOperator   = i;
                            }
                        }
                    }
                }
            }

            if ( nOperator != null )
            {
                // one operand, should always be at the beginning
                if ( startOperator==0 && nOperator.getType() == 1 )
                {
                    // the brackets must be ok
                    if ( checkBrackets( s.substring( nOperator.getOperator().length() ) ) == 0 )
                    {
                        nLeft  =
                        new Node( this, s.substring( nOperator.getOperator().length() ) , nLevel + 1);
                        nRight = null;
                        return;
                    }

                    throw new Exception("Error during parsing... missing brackets in [" + s + "]");
                }
                // two operands
                else if ( startOperator > 0 && nOperator.getType() == 2 )
                {
                    nLeft   =
                    new Node( this, s.substring(0,  startOperator), nLevel + 1 );
                    nRight   =
                    new Node( this, s.substring(startOperator + nOperator.getOperator().length()), nLevel + 1);
                }
            }
        }

        private Operator getOperator(String s, int start)
        {
            String temp = s.substring(start);
            temp = getNextWord(temp);
            for (int i=0; i<operators.length; i++)
            {
                if ( temp.startsWith(operators[i].getOperator()) )
                    return operators[i];
            }
            return null;
        }

        private String getNextWord(String s)
        {
            int sLength = s.length();
            for (int i=1; i<sLength; i++)
            {
                char c = s.charAt(i);
                if ( (c > 'z' || c < 'a') && (c > '9' || c < '0') )
                    return s.substring(0, i);
            }
            return s;
        }

        /**
         * checks if there is any missing brackets
         * @return true if s is valid
         */
        protected int checkBrackets(String s)
        {
            int sLength    = s.length();
            int inBracket   = 0;

            for (int i=0; i<sLength; i++)
            {
                if  ( s.charAt(i) == '(' && inBracket >= 0 )
                    inBracket++;
                else if ( s.charAt(i) == ')' )
                    inBracket--;
            }

            return inBracket;
        }

        /**
         * returns a string that doesnt start with a + or a -
         */
        protected String addZero(String s)
        {
            if ( s.startsWith("+") || s.startsWith("-") )
            {
                int sLength    = s.length();
                for (int i=0; i<sLength; i++)
                {
                    if ( getOperator(s, i) != null )
                        return "0" + s;
                }
            }

            return s;
        }


        protected boolean hasChild()
        {
            return ( nLeft != null || nRight != null );
        }

        protected boolean hasOperator()
        {
            return ( nOperator != null );
        }

        protected boolean hasLeft()
        {
            return ( nLeft != null );
        }

        protected Node getLeft()
        {
            return nLeft;
        }

        protected boolean hasRight()
        {
            return ( nRight != null );
        }

        protected Node getRight()
        {
            return nRight;
        }

        protected Operator getOperator()
        {
            return nOperator;
        }

        protected int getLevel()
        {
            return nLevel;
        }

        protected Double getValue()
        {
            return nValue;
        }

        protected void setValue(Double f)
        {
            nValue = f;
        }

        protected String getString()
        {
            return nString;
        }

        /**
         * Removes spaces, tabs and brackets at the begining
         */

        public String removeBrackets(String s)
        {
            String res = s;
            if ( s.length() > 2 && res.startsWith("(") && res.endsWith(")") && checkBrackets(s.substring(1,s.length()-1)) == 0 )
            {
                res = res.substring(1, res.length()-1 );
            }
            if ( res != s )
                return removeBrackets(res);
            else
                return res;
        }

        /**
         * Removes illegal characters
         */

        public String removeIllegalCharacters(String s)
        {
            char[] illegalCharacters = { ' ' };
            String res = s;

            for ( int j=0; j<illegalCharacters.length; j++)
            {
                int i = res.lastIndexOf(illegalCharacters[j], res.length());
                while ( i != -1 )
                {
                    String temp = res;
                    res = temp.substring(0,i);
                    res += temp.substring(i + 1);
                    i = res.lastIndexOf(illegalCharacters[j], s.length());
                }
            }
            return res;
        }

    }
}
