//
// Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
//
// WSO2 Inc. 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.
//
//
options
{
    LOOKAHEAD=3;
}
PARSER_BEGIN(ExpressionEvaluator)

package org.wso2.carbon.dataservices.expression;

import java.util.HashMap;
import java.util.Map;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Arrays;

//
// Expression evaluator class definition with required rules and methods
//
public class ExpressionEvaluator
{
    private static ExpressionData data;

    public static void init(ExpressionData dataIn) {
        data = dataIn;
    }

    public static String evaluate(String expression) throws ParseException
    {
        InputStream stream = new ByteArrayInputStream(expression.getBytes());
        ExpressionEvaluator parser = new ExpressionEvaluator(stream);
        return parser.program();
    }

    public static void parseSysIn() throws ParseException
    {
        ExpressionEvaluator parser = new ExpressionEvaluator(System.in);
        while (true)
        {
            System.out.println(parser.program());
        }
    }

    private static double getDoubleVal(String param) throws ParseException{
        Object value = data.getVariable(param);
        if (value instanceof Double) {
            return (Double)value;
        } else if (value instanceof Number){
            return (Double)value;
        }
        throw new ParseException("variable type missmatch : expected number but found "+value.getClass().getName());
    }

    private static String getVal(String param) throws ParseException{
        Object value = data.getVariable(param);
        if (value instanceof String) {
            return value.toString();
        }
        throw new ParseException("variable type missmatch expected String but found "+value.getClass().getName());
    }

    private static double getLength(String param) throws ParseException{
        Object value = data.getVariable(param);
        if (value instanceof String) {
            return value.toString().length();
        } else if( value.getClass().isArray()) {
            return (double)((String[])value).length;
        }
        throw new ParseException("variable type missmatch expected String but found "+value.getClass().getName());
    }
}
PARSER_END(ExpressionEvaluator)
SKIP :
{
    " "
|   "\r"
|   "\t"
}
TOKEN:
{
    < CONCAT: "concat" >
|   < SUBSTRING: "substring" >
|   < LENGTH: "length" >
|   < INTNO: (<DIGIT>)+ >
|   < NUMBER: (<DIGIT>)+ ( <DOT> (<DIGIT>)+ )? >
|   < VAR: (<DOLLARMRK> (<LETTER>)+ (<LETTER> | <DIGIT>)* (<DOT> (<LETTER> | <DIGIT>)+)*) >
|   < STRINGVAL: (["a"-"z"," ","A"-"Z","0"-"9"]|<DOT>)+ >
|   < DOLLARMRK: "$" >
|   < LETTER: ["a"-"z","A"-"Z"] >
|   < COMMA: "," >
|   < DOT: ["."] >
|   < LB: "(" >
|   < RB: ")" >
|   < SQ: "'" >
|   < PLUS: ["+"] >
|   < MINUS: ["-"] >
|   < MULT: "*" >
|   < DEVIDE: "/" >
|   < DIGIT: ["0"-"9"] >
|   < EOL: "\n" >
}
String program():
{
    String result;
}
{
    result = declaration() ( <EOL>|<EOF>) { return result; }
  | <EOL>               { return "line ends"; }
  | <EOF>               { System.exit(-1); }
}

String declaration():
{
    double statementRes;
    String result;
}
{
    statementRes = statement()
        {
            //System.out.println("******** declaration result "+statementRes);
            return Double.toString(statementRes);
        }
    | result=concatSub()
        {
            //System.out.println("******** declaration result "+result);
            return result;
        }
}

double statement():
{
    double lengthRes;
    double expRes;
}
{
    expRes = expression()
        {
            //System.out.println("******** statement expRes "+expRes);
            return expRes;
        }
    | lengthRes = length()
        {
            //System.out.println("******** statement lengthRes "+lengthRes);
            return lengthRes;
        }
}

String concatSub():
{
    String result;
}
{
    (result = concat() | result = substring())
        {
            //System.out.println("******** concatSub result "+result);
            return result;
        }
}

String concat():
{
    String first;
    String second;
}
{
    (<CONCAT> <LB> ( first = concatSub() | first = simpleExpression()) <COMMA> ( second = concatSub() | second = simpleExpression()) <RB>)
        {
            //System.out.println("******** concat first "+first);
            //System.out.println("******** concat second "+second);
            return first.concat(second);
        }
}

String substring():
{
    String string;
    Token begin;
    Token end;
    int beginInt;
    int endInt;
}
{
    <SUBSTRING> <LB> (string = simpleExpression() | string = concatSub()) <COMMA> begin = <INTNO>
        {
            beginInt = Integer.parseInt(begin.toString());
            //System.out.println("******** substring beginInt "+beginInt);
        }
    (<RB>
        {
            return string.substring(beginInt);
        }
    | <COMMA> end = <INTNO> <RB>
        {
            endInt = Integer.parseInt(end.toString());
            //System.out.println("******** substring endInt "+endInt);
            return string.substring(beginInt,endInt);
        }
    )
}

String simpleExpression():
{
    Token stringVal;
    Token var;
}
{
    <SQ> stringVal = <STRINGVAL> <SQ>
        {
            //System.out.println("******** simpleExpression stringVal "+stringVal.toString());
            return stringVal.toString();
        }
    | var = <VAR>
        {
            //System.out.println("******** simpleExpression var "+var.toString());
            return getVal(var.toString().substring(1));
        }
}

double length():
{
    Token stringVal;
    String string;
    Token varToken;
}
{
    (<LENGTH> <LB> (string = concatSub()
        {
            //System.out.println("******** length string "+string);
            return (double)string.length();
        }
    | <SQ> stringVal = <STRINGVAL> <SQ>
        {
             //System.out.println("******** length string "+string);
             return (double)stringVal.toString().length();
         }
    | varToken = <VAR> ) <RB>)
        {
            //System.out.println("******** length string "+string);
            return getLength(varToken.toString());
        }
}

double expression():
{
    double a;
    double b;
}
{
    (a = term() (<PLUS> b = expression() { a += b; } | <MINUS> b = expression() { a -= b; })*) { return a; }
}

double term():
{
    double a;
    double b;
}
{
    (a = unary() (<MULT> b = term() { a *=b; } | <DEVIDE> b = term() { a /= b; })*) { return a; }
}

double unary():
{
    double a;
}
{
    <MINUS> a=element()     { return -a; }
    |   a=element()         { return a; }
}

double element():
{
    double a;
}
{
     (a = intOrDouble() | a = varValue() | <LB> a = statement() <RB>) { return a; }
}

double intOrDouble():
{
    Token tokenNo;
    Token tokenInt;
}
{
    tokenInt = <INTNO>
        {
            //System.out.println("******** intOrDouble tokenInt "+tokenInt.toString());
            return Double.parseDouble(tokenInt.toString());
        }
    | tokenNo = <NUMBER>
        {
            //System.out.println("******** intOrDouble tokenNo "+tokenNo.toString());
            return Double.parseDouble(tokenNo.toString());
        }
}

double varValue():
{
    Token token;
}
{
    token = <VAR> { return getDoubleVal(token.toString().substring(1)); }
}