/*
    LFSLapper, Insim Utilities for Live For Speed Game
    Copyright (C) 2007  Robert B. alias Gai-Luron and Monkster: lfsgailuron@free.fr

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Text;
using System.Text.RegularExpressions;
using LFSLapper;
using lexCfg;


namespace LFSLapper
{
    class Parseur
    {

        public enum tokenExpr
        {
            notfound,
            and,
            or,
            equal,
            diff,
            moreThan,
            lessThan,
            moreThanEqual,
            lessThanEqual,
            concat,
            plus,
            minus,
            mult,
            div,
            pow,
            nbVal,
        }
        public enum typVal
        {
            num,
            str,
            time,
            var,
            undef,
        }
        public class unionVal
        {
            public float fval;
            public string sval;
            public typVal typVal;
            public unionVal(float pfval, string psval, typVal ptypval)
            {
                this.fval = pfval;
                this.sval = psval;
                this.typVal = ptypval;
            }

        }
        public Parseur expra;
        public Parseur exprb;
        public tokenExpr operateur;
        public LFSClient parent;
        public infoPlayer currInfoPlayer;

        public unionVal val = new unionVal(0, "", typVal.undef);

        public Parseur(lexCfg.TOKENLIST ptokens, LFSClient pparent, infoPlayer pcurrInfoPlayer)
        {
            this.parent = pparent;
            this.currInfoPlayer = pcurrInfoPlayer;
            goParse(ptokens, 0, ptokens.TokenList.Count - 1);
        }
        public Parseur(lexCfg.TOKENLIST ptokens, LFSClient pparent, infoPlayer pcurrInfoPlayer,int pstartBloc, int pendBloc )
        {
            this.parent = pparent;
            this.currInfoPlayer = pcurrInfoPlayer;
            goParse(ptokens, pstartBloc, pendBloc );
        }
        public void goParse(lexCfg.TOKENLIST tokens, int startBloc, int endBloc)
        {
            TOKENLIST.token myToken;
            this.expra = null;
            this.exprb = null;
            this.operateur = tokenExpr.notfound;

            int nbToken = endBloc - startBloc + 1;

            if (false)
            {
                for (int i = startBloc; i <= endBloc; i++)
                {
                    myToken = (TOKENLIST.token)tokens.TokenList[i];
                    Console.Write(myToken.value);
                }
                Console.WriteLine();
            }
            if ( nbToken == 0 )
            {
                this.val.sval = "\"\"";
                this.val.typVal = typVal.str;
                return;
            }
            if ( nbToken == 1 )
            {
                myToken = (TOKENLIST.token)tokens.TokenList[startBloc];
                if ((myToken.typ == lexCfg.Scanner.E_HMS))
                {
                    this.val.fval = unitConv.HMSToLong(myToken.value);
                    this.val.typVal = typVal.time;
                }
                else if (myToken.typ == lexCfg.Scanner.E_STRING)
                {
                    this.val.sval = myToken.value;
                    this.val.typVal = typVal.str;
                }
                else if (myToken.typ == lexCfg.Scanner.E_VAR)
                {
                    string idVar = myToken.value.Substring(1);
                    this.val.typVal = typVal.str;
                    this.val.sval = idVar;
                    bool find = false;
                    string findedValue = "";
                    if (parent != null)
                    {
                        if (parent.newCfg.varsUserGlobal.Contains(idVar) && !find )
                        {
                            findedValue = (string)parent.newCfg.varsUserGlobal[idVar];
                            find = true;
                        }
                        try
                        {
                            if (currInfoPlayer.playerVars.Contains(idVar) && !find)
                            {
                                findedValue = (string)currInfoPlayer.playerVars[idVar];
                                find = true;
                            }
                        }
                        catch { }
                        if (parent.newCfg.varsLocal.Contains(idVar) && !find)
                        {
                            findedValue = (string)parent.newCfg.varsLocal[idVar];
                            find = true; 
                        }
                        if (parent.newCfg.varsGlobal.Contains(idVar) && !find)
                        {
                            findedValue = (string)parent.newCfg.varsGlobal[idVar];
                            find = true;
                        }
                        if (!find)
                        {
                            try
                            {
                                findedValue = parent.retreiveVarPlayer(idVar, currInfoPlayer);
                                find = true;
                            }
                            catch { }
                        }
                        if (!find)
                        {

                            Console.WriteLine("Syntax error in Sub or Event at line #"
                                                    + myToken.lineNumber + " " + idVar + " Not defined");
                        }
                        try
                        {
                            this.val.fval = float.Parse(findedValue);
                            this.val.typVal = typVal.num;
                        }
                        catch
                        {
                            this.val.sval = findedValue;
                            this.val.typVal = typVal.str;
                        }
                    }
                }
                else if (myToken.typ == lexCfg.Scanner.E_INTEGER || myToken.typ == lexCfg.Scanner.E_FLOAT)
                {
                    this.val.fval = float.Parse(myToken.value);
                    this.val.typVal = typVal.num;
                }
                else if (myToken.typ == lexCfg.Scanner.E_IDENT)
                {
                    this.val.typVal = typVal.str;
                    this.val.sval = myToken.value;
                }
                return;
            }
            myToken = (TOKENLIST.token)tokens.TokenList[startBloc];
            if (myToken.typ == lexCfg.Scanner.E_IDENT)
            {
                int par = 0;
                for (int i = startBloc+1; i <= endBloc; i++)
                {
                    if ((tokens.TokenList[i] as TOKENLIST.token).typ == lexCfg.Scanner.E_PAR_OPEN)
                        par++;
                    if ((tokens.TokenList[i] as TOKENLIST.token).typ == lexCfg.Scanner.E_PAR_CLOSE)
                        par--;
                }
                if (par == 0 && (tokens.TokenList[endBloc] as TOKENLIST.token).typ == lexCfg.Scanner.E_PAR_CLOSE)
                {
                    this.val.typVal = typVal.str;
                    this.val.sval = myToken.value;
                    // Rechercher tous les arguments et les évaluer

                    int start = startBloc + 2;
                    System.Collections.ArrayList argsVal = new System.Collections.ArrayList();
                    for (int i = startBloc + 2; i <= endBloc; i++)
                    {
                        myToken = (TOKENLIST.token)tokens.TokenList[i];
                        if (myToken.typ == lexCfg.Scanner.E_COLON || i == endBloc - 1)
                        {
                            if (i == endBloc - 1)
                                i++;
                            // Parser l'argument
                            Parseur toto = new Parseur(tokens,parent,currInfoPlayer,start,i-1 );
                            //                        Console.WriteLine(toto.ToRPN());
                            unionVal arg = toto.getval();
                            start = i + 1;
                            // Ajouter aux argments de fonction
                            argsVal.Add(arg);
                        }

                    }
                    // Lancer Ici la fonction et récupérer la vleur de retour
                    return;
                }
            }
            this.val.typVal = typVal.undef;
            int p = 0;
            int cp0 = 0;
            //on recupere la liste des operateurs
            bool[] operateurs = new bool[(int)tokenExpr.nbVal];

            for (int i = 0; i < (int)tokenExpr.nbVal; i++)
                operateurs[i] = true;

            //on recupere la liste des operateurs
            for (int i = startBloc; i <= endBloc; i++)
            {
                myToken = (TOKENLIST.token)tokens.TokenList[i];
                if (myToken.typ == lexCfg.Scanner.E_END)
                    break;
                if (myToken.typ == lexCfg.Scanner.E_PAR_OPEN)
                    p++;
                else if (myToken.typ == lexCfg.Scanner.E_PAR_CLOSE)
                    p--;
                else if (p == 0)
                {
                    tokenExpr token = tokenExpr.notfound;
                    if (myToken.typ == lexCfg.Scanner.E_AND)
                        token = tokenExpr.and;
                    else if (myToken.typ == lexCfg.Scanner.E_OR)
                        token = tokenExpr.or;
                    else if (myToken.typ == lexCfg.Scanner.E_EQUAL)
                        token = tokenExpr.equal;
                    else if (myToken.typ == lexCfg.Scanner.E_DIFF)
                        token = tokenExpr.diff;
                    else if (myToken.typ == lexCfg.Scanner.E_MORE_EQUAL)
                        token = tokenExpr.moreThanEqual;
                    else if (myToken.typ == lexCfg.Scanner.E_LESS_EQUAL)
                        token = tokenExpr.lessThanEqual;
                    else if (myToken.typ == lexCfg.Scanner.E_MORE)
                        token = tokenExpr.moreThan;
                    else if (myToken.typ == lexCfg.Scanner.E_LESS)
                        token = tokenExpr.lessThan;
                    else if (myToken.typ == lexCfg.Scanner.E_POINT)
                        token = tokenExpr.concat;
                    else if (myToken.typ == lexCfg.Scanner.E_PLUS)
                        token = tokenExpr.plus;
                    else if (myToken.typ == lexCfg.Scanner.E_MOINS)
                        token = tokenExpr.minus;
                    else if (myToken.typ == lexCfg.Scanner.E_MULT)
                        token = tokenExpr.mult;
                    else if (myToken.typ == lexCfg.Scanner.E_DIV)
                        token = tokenExpr.div;
                    else if (myToken.typ == lexCfg.Scanner.E_POW)
                        token = tokenExpr.pow;
                    if (token != tokenExpr.notfound)
                    {
                        for (int j = (int)token + 1; j < (int)tokenExpr.nbVal; j++)
                            operateurs[j] = false;
                    }
                }
            }
            if (p != 0)
            {
                throw new Exception("Nombre de parentheses incorrect");
            }
            for (int i = endBloc; i >= startBloc; i--)
            {
                myToken = (TOKENLIST.token)tokens.TokenList[i];
                if (myToken.typ == lexCfg.Scanner.E_END)
                    continue;

                if (p == 0)
                {
                    cp0++;
                }
                tokenExpr token = tokenExpr.notfound;
                if (myToken.typ == lexCfg.Scanner.E_AND)
                    token = tokenExpr.and;
                else if (myToken.typ == lexCfg.Scanner.E_OR)
                    token = tokenExpr.or;
                else if (myToken.typ == lexCfg.Scanner.E_EQUAL)
                    token = tokenExpr.equal;
                else if (myToken.typ == lexCfg.Scanner.E_DIFF)
                    token = tokenExpr.diff;
                else if (myToken.typ == lexCfg.Scanner.E_MORE_EQUAL)
                    token = tokenExpr.moreThanEqual;
                else if (myToken.typ == lexCfg.Scanner.E_LESS_EQUAL)
                    token = tokenExpr.lessThanEqual;
                else if (myToken.typ == lexCfg.Scanner.E_MORE)
                    token = tokenExpr.moreThan;
                else if (myToken.typ == lexCfg.Scanner.E_LESS)
                    token = tokenExpr.lessThan;
                else if (myToken.typ == lexCfg.Scanner.E_POINT)
                    token = tokenExpr.concat;
                else if (myToken.typ == lexCfg.Scanner.E_PLUS)
                    token = tokenExpr.plus;
                else if (myToken.typ == lexCfg.Scanner.E_MOINS)
                    token = tokenExpr.minus;
                else if (myToken.typ == lexCfg.Scanner.E_MULT)
                    token = tokenExpr.mult;
                else if (myToken.typ == lexCfg.Scanner.E_DIV)
                    token = tokenExpr.div;
                else if (myToken.typ == lexCfg.Scanner.E_POW)
                    token = tokenExpr.pow;

                if (myToken.typ == lexCfg.Scanner.E_PAR_OPEN)
                    p++;
                else if (myToken.typ == lexCfg.Scanner.E_PAR_CLOSE)
                    p--;
                else if (p == 0 && (token != tokenExpr.notfound) && (bool)operateurs[(int)token])
                {
                    this.operateur = token;
                    this.expra = new Parseur(tokens,parent,currInfoPlayer,startBloc,i-1);
                    this.exprb = new Parseur(tokens,parent,currInfoPlayer,i+1,endBloc);
                    break;
                }
            }

            if (this.expra == null && cp0 == 1)
            {
                myToken = (TOKENLIST.token)tokens.TokenList[startBloc];
                if (( endBloc - startBloc +1 ) == 3)
                {
                    TOKENLIST.token myToken2 = (TOKENLIST.token)tokens.TokenList[startBloc + 1];
                    if ((myToken2.typ == lexCfg.Scanner.E_HMS))
                    {
                        this.val.fval = unitConv.HMSToLong(utils.unquote(myToken2.value));
                        this.val.typVal = typVal.time;
                    }
                    else if (myToken2.typ == lexCfg.Scanner.E_STRING)
                    {
                        this.val.sval = myToken2.value;
                        this.val.typVal = typVal.str;
                    }
                    else if (myToken2.typ == lexCfg.Scanner.E_VAR)
                    {
                        this.val.sval = myToken2.value;
                        this.val.typVal = typVal.var;
                    }
                    else if (myToken2.typ == lexCfg.Scanner.E_INTEGER || myToken2.typ == lexCfg.Scanner.E_FLOAT)
                    {
                        this.val.fval = float.Parse(myToken2.value);
                        this.val.typVal = typVal.num;
                    }
                }
                else
                {
                    this.expra = new Parseur( tokens,parent,currInfoPlayer,startBloc+1,endBloc-1 );
                }
            }



        }

        public string ToRPN()
        {
            if (this.val.typVal != typVal.undef)
            {
                if (this.val.typVal == typVal.num)
                    return this.val.fval.ToString();
                else if (this.val.typVal == typVal.str)
                    return this.val.sval;
                else if (this.val.typVal == typVal.var)
                    return this.val.sval;
                else if (this.val.typVal == typVal.time)
                    return this.val.fval.ToString();
            }
            else if (this.exprb == null && this.expra != null)
            {
                return this.expra.ToRPN();
            }
            else if (this.expra == null && this.exprb != null)
            {
                if (this.operateur != tokenExpr.notfound)
                    return this.exprb.ToRPN() + " " + this.operateur;
                else
                    return this.exprb.ToRPN();
            }
            else if (this.expra != null && this.exprb != null)
            {
                return this.expra.ToRPN() + " " + this.exprb.ToRPN() + " " + this.operateur;
            }
            return "";
        }
        public float numval(string str)
        {
            try
            {
                float val = float.Parse(str);
                return val;
            }
            catch
            {
                return 0;
            }
        }

        public unionVal getval()
        {
            unionVal retVal = getval2();
            return retVal;
        }
        public unionVal getval2()
        {
            if (this.val.typVal != typVal.undef)
                return this.val;
            else if (this.exprb == null)
            {
                if (this.expra == null)
                    throw new Exception("Erreur probablement dans l\'enchainement des operateurs...");
                else
                    return this.expra.getval();
            }
            unionVal reta = this.expra.getval2();
            unionVal retb = this.exprb.getval2();
            if (this.operateur == tokenExpr.equal
                || this.operateur == tokenExpr.diff
                || this.operateur == tokenExpr.moreThan
                || this.operateur == tokenExpr.lessThan
                || this.operateur == tokenExpr.moreThanEqual
                || this.operateur == tokenExpr.lessThanEqual
                )
            {
                if (reta.typVal == typVal.str || retb.typVal == typVal.str)
                {
                    if (reta.typVal != typVal.str)
                    {
                        reta.typVal = typVal.str;
                        reta.sval = "\"" + reta.fval.ToString() + "\"";
                    }
                    if (retb.typVal != typVal.str)
                    {
                        retb.typVal = typVal.str;
                        retb.sval = "\"" + retb.fval.ToString() + "\"";
                    }
                }

            }
            if (this.operateur == tokenExpr.concat)
            {
                if (reta.typVal == typVal.num)
                {
                    reta.sval = "\"" + reta.fval.ToString() + "\"";
                    reta.typVal = typVal.str;
                }
                else if (reta.typVal == typVal.time)
                {
                    reta.sval = "\"" + unitConv.LongToHMS((long)reta.fval) + "\"";
                    reta.typVal = typVal.str;
                }

                if (retb.typVal == typVal.num)
                {
                    retb.sval = "\"" + retb.fval.ToString() + "\"";
                    retb.typVal = typVal.str;
                }
                else if (retb.typVal == typVal.time)
                {
                    retb.sval = "\"" + unitConv.LongToHMS((long)retb.fval) + "\"";
                    retb.typVal = typVal.str;
                }
            }
            if (this.operateur == tokenExpr.plus
                || this.operateur == tokenExpr.or
                || this.operateur == tokenExpr.and
                || this.operateur == tokenExpr.minus
                || this.operateur == tokenExpr.mult
                || this.operateur == tokenExpr.div
                || this.operateur == tokenExpr.pow
                )
            {
                if (reta.typVal == typVal.str)
                {
                    reta.fval = this.numval(utils.unquote(reta.sval));
                    reta.typVal = typVal.num;
                }
                if (retb.typVal == typVal.str)
                {
                    retb.fval = this.numval(utils.unquote(retb.sval));
                    retb.typVal = typVal.num;
                }
            }
            switch (this.operateur)
            {
                case tokenExpr.and:
                    if (reta.fval != 0 && retb.fval != 0)
                        return new unionVal(1, "", typVal.num);
                    else
                        return new unionVal(0, "", typVal.num);
                case tokenExpr.or:
                    if (reta.fval != 0 || retb.fval != 0)
                        return new unionVal(1, "", typVal.num);
                    else
                        return new unionVal(0, "", typVal.num);
                case tokenExpr.equal:
                    if (reta.typVal == typVal.str)
                    {
                        if (reta.sval == retb.sval)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                    else
                    {
                        if (reta.fval == retb.fval)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                case tokenExpr.diff:
                    if (reta.typVal == typVal.str)
                    {
                        if (reta.sval != retb.sval)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                    else
                    {
                        if (reta.fval != retb.fval)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                case tokenExpr.moreThan:
                    if (reta.typVal == typVal.str)
                    {
                        if (string.Compare(reta.sval, retb.sval) > 0)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                    else
                    {
                        if (reta.fval > retb.fval)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                case tokenExpr.moreThanEqual:
                    if (reta.typVal == typVal.str)
                    {
                        if (string.Compare(reta.sval, retb.sval) >= 0)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                    else
                    {
                        if (reta.fval >= retb.fval)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                case tokenExpr.lessThan:
                    if (reta.typVal == typVal.str)
                    {
                        if (string.Compare(reta.sval, retb.sval) < 0)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                    else
                    {
                        if (reta.fval < retb.fval)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                case tokenExpr.lessThanEqual:
                    if (reta.typVal == typVal.str)
                    {
                        if (string.Compare(reta.sval, retb.sval) <= 0)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }
                    else
                    {
                        if (reta.fval <= retb.fval)
                            return new unionVal(1, "", typVal.num);
                        else
                            return new unionVal(0, "", typVal.num);
                    }

                case tokenExpr.concat:
                    return new unionVal(0, "\"" + utils.unquote(reta.sval) + utils.unquote(retb.sval) + "\"", typVal.str);
                case tokenExpr.plus:
                    {
                        typVal typ;
                        if (reta.typVal == typVal.time || retb.typVal == typVal.time)
                            typ = typVal.time;
                        else
                            typ = typVal.num;
                        return new unionVal(reta.fval + retb.fval, "", typ);
                    }
                case tokenExpr.minus:
                    {
                        typVal typ;
                        if (reta.typVal == typVal.time || retb.typVal == typVal.time)
                            typ = typVal.time;
                        else
                            typ = typVal.num;
                        return new unionVal(reta.fval - retb.fval, "", typ);
                    }
                case tokenExpr.mult:
                    {
                        typVal typ;
                        if (reta.typVal == typVal.time || retb.typVal == typVal.time)
                            typ = typVal.time;
                        else
                            typ = typVal.num;
                        return new unionVal(reta.fval * retb.fval, "", typ);
                    }
                case tokenExpr.div:
                    if (retb.fval == 0)
                        throw new Exception("Division par 0");
                    {
                        typVal typ;
                        if (reta.typVal == typVal.time || retb.typVal == typVal.time)
                            typ = typVal.time;
                        else
                            typ = typVal.num;
                        return new unionVal(reta.fval / retb.fval, "", typ);
                    }
                case tokenExpr.pow:
                    {
                        typVal typ;
                        if (reta.typVal == typVal.time || retb.typVal == typVal.time)
                            typ = typVal.time;
                        else
                            typ = typVal.num;
                        if (retb.fval == 0)
                        {
                            return new unionVal(1, "", typ);
                        }
                        else if (reta.fval == 0)
                        {
                            return new unionVal(0, "", typ);
                        }
                        else if (retb.fval != Math.Ceiling(retb.fval) && reta.fval <= 0)
                        {
                            if ((1 / retb.fval) == Math.Ceiling(1 / retb.fval) && Math.Abs(1 / retb.fval) % 2 == 1)
                            {
                                return new unionVal((float)(-Math.Pow(-reta.fval, retb.fval)), "", typ);
                            }
                            else
                            {
                                throw new Exception("On ne peut pas prendre un exposant non entier sur un nombre negatif");
                            }
                        }
                        else
                        {
                            return new unionVal((float)Math.Pow(reta.fval, Math.Ceiling(retb.fval)), "", typ);
                        }
                    }
                default:
                    throw new Exception("operateur " + this.operateur + "non definit");
            }
        }


    }


}
