using System;
using System.Collections.Generic;

namespace FastReport.AdvMatrix
{
    internal class AggregateExpressionPairList : List<AggregateExpressionPair>
    {
        private AdvMatrixObject matrix;
        
        public AggregateExpressionPair Find(string aggregateName, string expression)
        {
            foreach (AggregateExpressionPair item in this)
            {
                if (item.AggregateName == aggregateName && item.Expression == expression)
                    return item;
            }
            return null;
        }

        public AggregateExpressionPair Add(string aggregateName, string expression)
        {
            AggregateExpressionPair result = new AggregateExpressionPair(matrix, aggregateName, expression);
            Add(result);
            return result;
        }

        public AggregateExpressionPair AddUnique(string aggregateName, string expression)
        {
            AggregateExpressionPair result = Find(aggregateName, expression);
            if (result != null)
                return result;
            return Add(aggregateName, expression);
        }

        public AggregateExpressionPair AddUnique(AggregateExpressionPair ag)
        {
            AggregateExpressionPair result = Find(ag.AggregateName, ag.Expression);
            if (result != null)
                return result;
            Add(ag);
            return ag;
        }

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

    internal class AggregateExpressionPair
    {
        private AdvMatrixObject matrix;
        private CellDataStorage storage;

        public string AggregateName { get; private set; }
        public string Expression { get; private set; }

        private string ToStringLiteral(string text)
        {
            return "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
        }

        public string ToAggregateCall()
        {
            return matrix.Name + ".Data.Context.GetAggregate(" + ToStringLiteral(AggregateName) + ", " + ToStringLiteral(Expression) + ")";
        }

        public override string ToString()
        {
            return AggregateName + "(" + Expression + ")";
        }

        public void ProcessDataRow(int columnIndex, int rowIndex, object value)
        {
            storage.AddValue(columnIndex, rowIndex, value);
        }

        public object GetValue(int columnIndex, int rowIndex)
        {
            object value = storage.GetValue(columnIndex, rowIndex);
            if (value != null && storage.AggregateType == typeof(UserAggregate))
                value = matrix.Report.InvokeMethod(AggregateName, new object[] { value });
            return value;
        }

        public void Merge(int sourceColumnIndex, int sourceRowIndex, int targetColumnIndex, int targetRowIndex)
        {
            storage.Merge(sourceColumnIndex, sourceRowIndex, targetColumnIndex, targetRowIndex);
        }

        public void ClearData()
        {
            storage.Clear();
        }

        public AggregateExpressionPair(AdvMatrixObject matrix, string aggregateName, string expression)
        {
            this.matrix = matrix;
            AggregateName = aggregateName;
            Expression = expression;
            Type aggregateType = Aggregates.Find(aggregateName);
            if (aggregateType == null)
                aggregateType = typeof(UserAggregate);
            storage = new CellDataStorage(aggregateType);
        }

        private class CellDataStorage
        {
            // array of array: the simplest and most efficient way to store dense matrix data
            private List<List<AggregateBase>> rows;
            internal Type AggregateType { get; private set; }

            private AggregateBase GetCell(int columnIndex, int rowIndex, bool create)
            {
                if (rowIndex >= rows.Count)
                {
                    if (!create)
                        return null;

                    // append rows if row index is out of bounds
                    int delta = rowIndex - rows.Count + 1;
                    for (int i = 0; i < delta; i++)
                    {
                        rows.Add(new List<AggregateBase>());
                    }
                }

                List<AggregateBase> row = rows[rowIndex];

                if (columnIndex >= row.Count)
                {
                    if (!create)
                        return null;

                    // append columns if column index is out of bounds
                    int delta = columnIndex - row.Count + 1;
                    for (int i = 0; i < delta; i++)
                    {
                        row.Add(null);
                    }
                }

                AggregateBase aggregate = row[columnIndex];
                if (aggregate == null)
                {
                    if (!create)
                        return null;

                    // initial state - the cell is empty. Create aggregate instance
                    aggregate = Activator.CreateInstance(AggregateType) as AggregateBase;
                    row[columnIndex] = aggregate;
                }

                return aggregate;
            }

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

            public void AddValue(int columnIndex, int rowIndex, object value)
            {
                // do not put null values to the matrix!
                if (value == null || value == DBNull.Value)
                    return;

                AggregateBase aggregate = GetCell(columnIndex, rowIndex, true);
                aggregate.AddValue(value);
            }

            public object GetValue(int columnIndex, int rowIndex)
            {
                AggregateBase aggregate = GetCell(columnIndex, rowIndex, false);
                if (aggregate == null)
                    return null;
                return aggregate.GetValue();
            }

            public void Merge(int sourceColumnIndex, int sourceRowIndex, int targetColumnIndex, int targetRowIndex)
            {
                AggregateBase sourceAggr = GetCell(sourceColumnIndex, sourceRowIndex, false);
                AggregateBase targetAggr = GetCell(targetColumnIndex, targetRowIndex, true);
                targetAggr.Merge(sourceAggr);
            }

            public CellDataStorage(Type aggregateType)
            {
                AggregateType = aggregateType;
                rows = new List<List<AggregateBase>>();
            }
        }
    }
}