using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace DevComponents.DotNetBar.SuperGrid
{
    /// 
    /// Expression evaluator
    /// 
    public class EEval
    {
        #region Private data
        private int _TCount;
        private int _PCount;
        private ETokens[] _PTokens = new ETokens[10];
        private List _PfTokens = new List();
        private GridCell _Cell;
        private GridPanel _GridPanel;
        private List _UsedCells;
        private string _Source;
        private List _StringPool = new List();
        private object _Tag;
        #endregion
        ///
        /// Expression evaluator constructor
        ///
        ///Associated GridPanel
        ///'Excel-like' expression (eg. =d4+d5)
        public EEval(GridPanel gridPanel, string source)
            : this(null, source, new List())
        {
            _GridPanel = gridPanel;
        }
        internal EEval(GridCell cell, string source)
            : this(cell, source, new List())
        {
        }
        private EEval(GridCell cell, string source, List usedCells)
        {
            _Cell = cell;
            _UsedCells = usedCells;
            if (_Cell != null)
                _GridPanel = cell.GridPanel;
            Source = source;
        }
        #region Public properties
        #region Cell
        ///
        /// Gets the associated Grid Cell, if any
        ///
        public GridCell Cell
        {
            get { return (_Cell); }
        }
        #endregion
        #region GridPanel
        ///
        /// Gets the associated Grid Panel
        ///
        public GridPanel GridPanel
        {
            get { return (_GridPanel); }
        }
        #endregion
        #region Source
        ///
        /// Gets or sets the expression source
        ///
        public string Source
        {
            get { return (_Source); }
            set
            {
                _Source = value;
                Tokenize(value);
            }
        }
        #endregion
        #region Tag
        ///
        /// Gets or sets user-defined data associated with the object
        ///
        public object Tag
        {
            get { return (_Tag); }
            set { _Tag = value; }
        }
        #endregion
        #endregion
        #region Tokenize code
        private void Tokenize(string source)
        {
            const string sref = @"([\.]|[a-zA-Z_]+|""[^""]+"")\s*([\.]|\d+)";
            Regex p = new Regex(
                "(" + sref + "\\s*:\\s*" + sref + ")|(" + sref + ")|" +
                "([a-zA-Z0-9.]+)|" +
                "\"([^\"]+)\"|" +
                "([()+\\-*/%,|&\\^])|(<<)|(>>)");
            MatchCollection mc = p.Matches(source);
            Expr(mc);
            ETokens t = GetToken(mc);
            if (t != ETokens.BadToken)
                throw new Exception("Expression error.");
        }
        #region Expr
        /// 
        /// Main expression entry routine
        /// 
        private void Expr(MatchCollection mc)
        {
            Term1(mc);
            ETokens t = GetToken(mc);
            while (t == ETokens.BitwiseOr)
            {
                Term1(mc);
                _PfTokens.Add(t);
                t = GetToken(mc);
            }
            PutToken(t);
        }
        #endregion
        #region Term1
        /// 
        /// Handles the ^ operator
        /// 
        private void Term1(MatchCollection mc)
        {
            Term2(mc);
            ETokens t = GetToken(mc);
            while (t == ETokens.BitwiseXor)
            {
                Term2(mc);
                _PfTokens.Add(t);
                t = GetToken(mc);
            }
            PutToken(t);
        }
        #endregion
        #region Term2
        /// 
        /// Handles the Ampersand operator
        /// 
        private void Term2(MatchCollection mc)
        {
            Term3(mc);
            ETokens t = GetToken(mc);
            while (t == ETokens.BitwiseAnd)
            {
                Term3(mc);
                _PfTokens.Add(t);
                t = GetToken(mc);
            }
            PutToken(t);
        }
        #endregion
        #region Term3
        /// 
        /// Handles the shift left and right operators
        /// 
        private void Term3(MatchCollection mc)
        {
            Term4(mc);
            ETokens t = GetToken(mc);
            while (t == ETokens.ShiftLeft || t == ETokens.ShiftRight)
            {
                Term4(mc);
                _PfTokens.Add(t);
                t = GetToken(mc);
            }
            PutToken(t);
        }
        #endregion
        #region Term4
        /// 
        /// Handles %, *, and / operators
        /// 
        private void Term4(MatchCollection mc)
        {
            Term5(mc);
            ETokens t = GetToken(mc);
            while (t == ETokens.Add || t == ETokens.Subtract)
            {
                Term5(mc);
                _PfTokens.Add(t);
                t = GetToken(mc);
            }
            PutToken(t);
        }
        #endregion
        #region Term5
        /// 
        /// Handles %, *, and / operators
        /// 
        private void Term5(MatchCollection mc)
        {
            Factor(mc);
            ETokens t = GetToken(mc);
            while (t == ETokens.Mod ||
                t == ETokens.Multiply || t == ETokens.Divide)
            {
                Factor(mc);
                _PfTokens.Add(t);
                t = GetToken(mc);
            }
            PutToken(t);
        }
        #endregion
        #region Factor
        /// 
        /// Handles factor processing
        /// 
        private void Factor(MatchCollection mc)
        {
            ETokens t = GetToken(mc);
            int n = 0;
            // Unary operators
            while (t == ETokens.Add || t == ETokens.Subtract)
            {
                if (t == ETokens.Subtract)
                    n++;
                t = GetToken(mc);
            }
            // Operand processing
            if (t < ETokens.Operator)
            {
                if (Function(mc, t) == false)
                    _PfTokens.Add(t);
            }
            else if (t == ETokens.LParen)
            {
                Expr(mc);
                t = GetToken(mc);
                if (t != ETokens.RParen)
                    throw new Exception("Expecting right parenthesis.");
            }
            else
            {
                PutToken(t);
            }
            if (n % 2 == 1)
            {
                if (_PfTokens.Count == 0)
                    throw new Exception("Invalid expression.");
                _PfTokens.Add(ETokens.Negate);
            }
        }
        #endregion
        #region Function
        /// 
        /// Handles function parsing
        /// 
        /// 
        /// 
        /// 
        private bool Function(MatchCollection mc, ETokens e)
        {
            string s = _StringPool[(int)e].ToUpper();
            ETokens t = GetFunction(mc, s);
            if (t != ETokens.BadToken)
            {
                _PfTokens.Add(t);
                t = GetToken(mc);
                if (t != ETokens.LParen)
                    throw new Exception("Expecting left parenthesis.");
                ParameterList(mc, e);
                t = GetToken(mc);
                if (t != ETokens.RParen)
                    throw new Exception("Expecting right parenthesis.");
                return (true);
            }
            return (false);
        }
        #region GetFunction
        /// 
        /// Determines whether the parsed string
        /// is a one of our function keywords
        /// 
        /// 
        /// 
        /// 
        private ETokens GetFunction(MatchCollection mc, string s)
        {
            ETokens t = GetToken(mc);
            if (t == ETokens.LParen)
            {
                PutToken(t);
                switch (s)
                {
                    case "AVG":
                        return (ETokens.Avg);
                    case "CEILING":
                        return (ETokens.Ceiling);
                    case "FLOOR":
                        return (ETokens.Floor);
                    case "MIN":
                        return (ETokens.Min);
                    case "MAX":
                        return (ETokens.Max);
                    case "ROUND":
                        return (ETokens.Round);
                    case "SUM":
                        return (ETokens.Sum);
                    default:
                        return (ETokens.User);
                }
            }
            PutToken(t);
            return (ETokens.BadToken);
        }
        #endregion
        #endregion
        #region ParameterList
        /// 
        /// Handles function parameters
        /// 
        private void ParameterList(MatchCollection mc, ETokens e)
        {
            int n = 0;
            if (_PfTokens[_PfTokens.Count - 1] == ETokens.User)
            {
                _PfTokens.Add(e);
                n++;
            }
            ETokens t = GetToken(mc);
            while (t != ETokens.RParen && t != ETokens.BadToken)
            {
                PutToken(t);
                int count = _PfTokens.Count;
                Expr(mc);
                if (_PfTokens.Count > count)
                    n++;
                t = GetToken(mc);
                if (t != ETokens.Comma)
                    break;
                t = GetToken(mc);
            }
            PutToken(t);
            _PfTokens.Add(ETokens.Function);
            _PfTokens.Add((ETokens) n);
        }
        #endregion
        #region GetToken
        /// 
        /// Gets the next parsed token
        /// 
        /// 
        private ETokens GetToken(MatchCollection mc)
        {
            ETokens t = ETokens.BadToken;
            if (_PCount > 0)
            {
                t = _PTokens[--_PCount];
            }
            else
            {
                if (_TCount < mc.Count)
                {
                    string s = mc[_TCount].Value;
                    switch (s)
                    {
                        case "|":
                            t = ETokens.BitwiseOr;
                            break;
                        case "^":
                            t = ETokens.BitwiseXor;
                            break;
                        case "&":
                            t = ETokens.BitwiseAnd;
                            break;
                        case "<<":
                            t = ETokens.ShiftLeft;
                            break;
                        case ">>":
                            t = ETokens.ShiftRight;
                            break;
                        case "+":
                            t = ETokens.Add;
                            break;
                        case "-":
                            t = ETokens.Subtract;
                            break;
                        case "*":
                            t = ETokens.Multiply;
                            break;
                        case "/":
                            t = ETokens.Divide;
                            break;
                        case "%":
                            t = ETokens.Mod;
                            break;
                        case "(":
                            t = ETokens.LParen;
                            break;
                        case ")":
                            t = ETokens.RParen;
                            break;
                        case ",":
                            t = ETokens.Comma;
                            break;
                        default:
                            int index = _StringPool.IndexOf(s);
                            if (index < 0)
                            {
                                _StringPool.Add(s);
                                index = _StringPool.Count - 1;
                            }
                            t = (ETokens)(index);
                            break;
                    }
                    _TCount++;
                }
            }
            return (t);
        }
        #endregion
        #region PutToken
        /// 
        /// Saves the given token for future use
        /// 
        /// 
        private void PutToken(ETokens t)
        {
            _PTokens[_PCount++] = t;
        }
        #endregion
        #endregion
        #region Evaluate
        /// 
        /// Evaluates the previously tokenized code
        /// 
        /// 
        public object Evaluate()
        {
            _UsedCells.Clear();
            if (_Cell != null)
                _UsedCells.Add(_Cell);
            return (EvaluateEx());
        }
        internal object EvaluateEx()
        {
            Stack myStack = new Stack();
            for (int i = 0; i < _PfTokens.Count; i++)
            {
                if (_PfTokens[i] < ETokens.Operator)
                {
                    myStack.Push(_StringPool[(int)_PfTokens[i]]);
                }
                else if (_PfTokens[i] > ETokens.Functions)
                {
                    myStack.Push(_PfTokens[i]);
                }
                else
                {
                    if (_PfTokens[i] == ETokens.Negate)
                    {
                        object value = ProcessValue(myStack.Pop());
                        if (value is double == false)
                            throw new Exception("Invalid negation value");
                        myStack.Push(-(double)value);
                    }
                    else if (_PfTokens[i] == ETokens.Function)
                    {
                        if (++i == _PfTokens.Count)
                            throw new Exception("Expecting function parameter count");
                        EvalFunction(myStack, (int)_PfTokens[i]);
                    }
                    else
                    {
                        object value1 = ProcessValue(myStack.Pop());
                        object value2 = ProcessValue(myStack.Pop());
                        if (value1 == null || value2 == null)
                        {
                            if (value1 == null && value2 == null)
                                value1 = value2 = 0d;
                            else if (value1 == null)
                            {
                                if (value2 is string)
                                    value1 = "";
                                else
                                    value1 = 0d;
                            }
                            else
                            {
                                if (value1 is string)
                                    value2 = "";
                                else
                                    value2 = 0d;
                            }
                        }
                        if (value1 is string || value2 is string)
                            OpStringValue(myStack, _PfTokens[i], value1.ToString(), value2.ToString());
                        else
                            OpDoubleValue(myStack, _PfTokens[i], (double)value1, (double)value2);
                    }
                }
            }
            if (myStack.Count > 0)
                return (ProcessValue(myStack.Pop()));
            return (0);
        }
        #region OpStringValue
        private void OpStringValue(Stack myStack, ETokens op, string s1, string s2)
        {
            switch (op)
            {
                case ETokens.Add:
                    myStack.Push(s2 + s1);
                    break;
                default:
                    throw new Exception("Invalid string operation.");
            }
        }
        #endregion
        #region OpDoubleValue
        private void OpDoubleValue(
            Stack myStack, ETokens op, double d1, double d2)
        {
            switch (op)
            {
                case ETokens.BitwiseAnd:
                    myStack.Push((double)((int)d2 & (int)d1));
                    break;
                case ETokens.BitwiseOr:
                    myStack.Push((double)((int)d2 | (int)d1));
                    break;
                case ETokens.BitwiseXor:
                    myStack.Push((double)((int)d2 ^ (int)d1));
                    break;
                case ETokens.Add:
                    myStack.Push(d2 + d1);
                    break;
                case ETokens.Subtract:
                    myStack.Push(d2 - d1);
                    break;
                case ETokens.Multiply:
                    myStack.Push(d2 * d1);
                    break;
                case ETokens.Divide:
                    myStack.Push(d2 / d1);
                    break;
                case ETokens.Mod:
                    myStack.Push(d2 % d1);
                    break;
                case ETokens.ShiftLeft:
                    myStack.Push((double)((int)d2 << (int)d1));
                    break;
                case ETokens.ShiftRight:
                    myStack.Push((double)((int)d2 >> (int)d1));
                    break;
            }
        }
        #endregion
        #region EvalFunction
        /// 
        /// Evaluates the current function
        /// 
        /// 
        /// 
        private void EvalFunction(Stack myStack, int count)
        {
            object[] args = new object[count];
            for (int i = count - 1; i >= 0; i--)
                args[i] = myStack.Pop();
            switch ((ETokens)myStack.Pop())
            {
                case ETokens.Avg:
                    myStack.Push(Avg(args));
                    break;
                case ETokens.Ceiling:
                    myStack.Push(Ceiling(args));
                    break;
                case ETokens.Floor:
                    myStack.Push(Floor(args));
                    break;
                case ETokens.Min:
                    myStack.Push(Min(args));
                    break;
                case ETokens.Max:
                    myStack.Push(Max(args));
                    break;
                case ETokens.Round:
                    myStack.Push(Round(args));
                    break;
                case ETokens.Sum:
                    myStack.Push(Sum(args));
                    break;
                case ETokens.User:
                    myStack.Push(User(args));
                    break;
            }
        }
        #region Average
        /// 
        /// Calculates the average of the given set of values
        /// 
        /// 
        /// 
        private double Avg(IEnumerable