using System;
using System.Collections.Generic;
using FastReport.Code;
using FastReport.Utils;
using FastReport.Table;

namespace FastReport.AdvMatrix
{
    internal class ExpressionParser
    {
        private AdvMatrixObject matrix;
        internal static readonly string[] SpecialFunctionNames = {
            "ColumnTotal", "RowTotal", "GrandTotal", "GrandColumnTotal", "GrandRowTotal",
            "SpecificColumn", "SpecificRow", "FirstColumn", "FirstRow", "LastColumn", "LastRow", "PreviousColumn", "PreviousRow", "NextColumn", "NextRow",
            "ColumnMaxValue", "ColumnMinValue", "RowMaxValue", "RowMinValue",
            "PercentOfColumnTotal", "PercentOfRowTotal", "PercentOfGrandTotal", "PercentOfPreviousColumn", "PercentOfPreviousRow" };

        private bool IsAggregate(string ident)
        {
            return Aggregates.Find(ident) != null || ident.StartsWith("_");
        }

        private bool IsSpecialFunction(string ident)
        {
            foreach (string s in SpecialFunctionNames)
            {
                if (s == ident)
                    return true;
            }
            return false;
        }

        private string ToSpecialFunctionCall(string name)
        {
            return matrix.Name + ".Data.Context.Get" + name;
        }

        // parse expression:
        // - search for aggregates and collect them in the aggregates list
        // - replace aggregate calls: AggrName(expression) -> MatrixName.Data.Context.GetAggregate("AggrName", "expression")
        // - search for special functions like "PreviousColumn(...)" and collect them in the specialFunctions list
        // - replace special function calls: PreviousColumn() -> MatrixName.Data.Context.GetPreviousColumn()
        private string Parse(string expression, AggregateExpressionPairList aggregates, List<string> specialFunctions)
        {
            FindTextArgs args = new FindTextArgs();
            args.Text = new FastString(expression);
            args.OpenBracket = "(";
            args.CloseBracket = ")";

            // search for method calls 
            while (args.StartIndex < args.Text.Length)
            {
                if (CodeUtils.GetExpression(args, false) == null)
                    break;

                // get identifier at the left of (
                int i = args.StartIndex;
                while (i > 0)
                {
                    char c = args.Text[i - 1];
                    if (!(char.IsLetterOrDigit(c) || c == '_'))
                        break;
                    i--;
                }
                string ident = args.Text.Substring(i, args.StartIndex - i);
                // if ident has preceding . symbol, ignore it
                if (i > 0 && args.Text[i - 1] == '.')
                    ident = "";

                if (IsAggregate(ident))
                {
                    // ident is either one of predefined aggregates or a user aggregate
                    string newText = aggregates.AddUnique(ident, args.FoundText).ToAggregateCall();
                    args.Text.Remove(i, args.EndIndex - i);
                    args.Text.Insert(i, newText);
                    args.StartIndex = i + newText.Length;
                }
                else if (IsSpecialFunction(ident))
                {
                    // ident is a special function like PreviousColumn(...)
                    if (specialFunctions != null)
                        specialFunctions.Add(ident);
                    string newText = ToSpecialFunctionCall(ident);
                    args.Text.Remove(i, ident.Length);
                    args.Text.Insert(i, newText);
                    args.StartIndex = i + newText.Length;
                }
                else
                {
                    string newText = "(" + Parse(args.FoundText, aggregates, specialFunctions) + ")";
                    args.Text.Remove(args.StartIndex, args.EndIndex - args.StartIndex);
                    args.Text.Insert(args.StartIndex, newText);
                    args.StartIndex += newText.Length;
                }
            }

            return args.Text.ToString();
        }

        // parse expression with single aggregate
        public AggregateExpressionPair Parse(string expression)
        {
            AggregateExpressionPairList list = new AggregateExpressionPairList(matrix);
            Parse(expression, list, null);
            if (list.Count == 1)
                return list[0];
            return null;
        }

        public CellDescriptor ProcessCell(TableCell cell)
        {
            // create cell descriptor and attach it to a cell
            CellDescriptor cellDescriptor = new CellDescriptor(matrix, cell);

            string text = cell.Text;
            string[] brackets = cell.Brackets.Split(',');
            if (String.IsNullOrEmpty(text) || brackets.Length != 2)
                return cellDescriptor;

            // process all expressions in the cell's text
            FindTextArgs args = new FindTextArgs();
            args.Text = new FastString(text);
            args.OpenBracket = brackets[0];
            args.CloseBracket = brackets[1];

            while (args.StartIndex < args.Text.Length)
            {
                string expression = CodeUtils.GetExpression(args, false);
                if (expression == null)
                    break;

                expression = Parse(expression, cellDescriptor.Aggregates, cellDescriptor.SpecialFunctions);
                cellDescriptor.Expressions.Add(expression);

                expression = brackets[0] + expression + brackets[1];
                args.Text.Remove(args.StartIndex, args.EndIndex - args.StartIndex);
                args.Text.Insert(args.StartIndex, expression);
                args.StartIndex += expression.Length;
            }

            // set the new text to the cell descriptor
            cellDescriptor.Text = args.Text.ToString();
            cellDescriptor.UpdateContentType();
            
            return cellDescriptor;
        }

        public ExpressionParser(AdvMatrixObject matrix)
        {
            this.matrix = matrix;
        }
    }
}