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

namespace FastReport.AdvMatrix
{
    internal class CellData
    {
        private AdvMatrixObject matrix;
        private List<CellDescriptor> descriptors;
        private ExpressionCache expressionCache;
        private Hashtable processedIndices;

        public AggregateExpressionPairList SharedAggregates { get; private set; }

        private void BeforeProcess()
        {
            expressionCache.Clear();
            processedIndices.Clear();
        }

        private void ProcessDataRow(int columnIndex, int rowIndex)
        {
            // filter out repeated calculation with the same indices. It may occur if a cell has reference to column/row totals,
            // and a header has nested items. Repeated calculation will lead to doubling the total values.
            long compoundIndex = ((long)columnIndex << 32) + rowIndex;
            if (processedIndices.ContainsKey(compoundIndex))
                return;
            processedIndices[compoundIndex] = 1;

            // process all aggregates
            foreach (AggregateExpressionPair item in SharedAggregates)
            {
                item.ProcessDataRow(columnIndex, rowIndex, expressionCache.Calc(item.Expression));
            }
        }

        // searches for existing aggregate and returns it if any; otherwise returns null
        public AggregateExpressionPair FindAggregate(AggregateExpressionPair aggr)
        {
            if (aggr == null)
                return null;
            foreach (CellDescriptor d in descriptors)
            {
                AggregateExpressionPair result = d.Aggregates.Find(aggr.AggregateName, aggr.Expression);
                if (result != null)
                    return result;
            }
            return null;
        }

        public void InitDescriptors()
        {
            descriptors.Clear();
            ExpressionParser parser = new ExpressionParser(matrix);
            SharedAggregates = new AggregateExpressionPairList(matrix);

            int offsetX = matrix.Data.Rows.Size;
            int offsetY = matrix.Data.Columns.Size;
            for (int x = offsetX; x < matrix.Columns.Count; x++)
            {
                for (int y = offsetY; y < matrix.Rows.Count; y++)
                {
                    TableCell cell = matrix[x, y];
                    CellDescriptor d = parser.ProcessCell(cell);
                    descriptors.Add(d);

                    // share aggregates among cell descriptors
                    for (int i = 0; i < d.Aggregates.Count; i++)
                    {
                        d.Aggregates[i] = SharedAggregates.AddUnique(d.Aggregates[i]);
                    }

                    // forbid cell to process its expressions
                    cell.AllowExpressions = false;
                }
            }
        }

        public void GetExpressions(List<string> expressions)
        {
            foreach (CellDescriptor d in descriptors)
            {
                d.GetExpressions(expressions);
            }
        }

        public void ClearData()
        {
            foreach (CellDescriptor descr in descriptors)
            {
                descr.ClearData();
            }
        }

        public void Init()
        {
            ClearData();
            foreach (CellDescriptor d in descriptors)
            {
                d.ColumnIndex = matrix.Data.Columns.NextIndex;
                d.RowIndex = matrix.Data.Rows.NextIndex;
            }
        }

        public void ProcessDataRow(List<HeaderData> columnItems, List<HeaderData> rowItems)
        {
            BeforeProcess();
            foreach (HeaderData columnItem in columnItems)
            {
                foreach (HeaderData rowItem in rowItems)
                {
                    if (columnItem.Descriptor.IsTopNItem || rowItem.Descriptor.IsTopNItem)
                        continue;
                    TableCell cell = matrix[columnItem.Descriptor.TemplateColumn.Index, rowItem.Descriptor.TemplateRow.Index];
                    CellDescriptor descr = cell.GetDescriptor();
                    ProcessDataRow(columnItem.Index, rowItem.Index);
                    
                    // calculate column total, row total and grand total for the given cell
                    if (descr.HasColumnTotal)
                        ProcessDataRow(columnItem.Index, descr.RowIndex);
                    if (descr.HasRowTotal)
                        ProcessDataRow(descr.ColumnIndex, rowItem.Index);
                    if (descr.HasGrandTotal)
                        ProcessDataRow(descr.ColumnIndex, descr.RowIndex);
                }
            }
        }

        public CellData(AdvMatrixObject matrix)
        {
            this.matrix = matrix;
            descriptors = new List<CellDescriptor>();
            expressionCache = new ExpressionCache(matrix);
            processedIndices = new Hashtable();
        }


        // expression cache is used to store expression values between ProcessDataRow calls. It saves a lot of time if several cells share the same expression.
        private class ExpressionCache
        {
            private Dictionary<string, object> cache;
            private AdvMatrixObject matrix;

            public void Clear()
            {
                cache.Clear();
            }

            public object Calc(string expression)
            {
                Report report = matrix.Report;
                if (String.IsNullOrEmpty(expression) || report == null)
                    return null;

                object value;
                if (!cache.TryGetValue(expression, out value))
                {
                    value = report.Calc(expression);
                    cache[expression] = value;
                }
                return value;
            }

            public ExpressionCache(AdvMatrixObject matrix)
            {
                cache = new Dictionary<string, object>();
                this.matrix = matrix;
            }
        }
    }
}