using System;
using System.Collections.Generic;

namespace FastReport.AdvMatrix
{
    internal static class TopNBuilder
    {
        private static void EnumTopNItems(HeaderData root, bool isColumn, Func<HeaderDataList, bool, int> func)
        {
            for (int i = 0; i < root.Items.Count; i++)
            {
                HeaderDataList dl = root.Items[i];

                // only serve items with TopN enabled and item count > TopN
                if (dl.Descriptor.TopN.Count > 0 && dl.Count > dl.Descriptor.TopN.Count)
                {
                    i = func(dl, isColumn);
                }
                foreach (HeaderData d in dl)
                {
                    EnumTopNItems(d, isColumn, func);
                }
            }
        }

        private static HeaderDataList FindDescriptorInternal(HeaderData root, string name)
        {
            if (root == null || String.IsNullOrEmpty(name))
                return null;

            foreach (HeaderDataList dl in root.Items)
            {
                if (dl.Descriptor.Name == name)
                    return dl;
                // in case we have topN items in parent groups, go deep
                foreach (HeaderData d in dl)
                {
                    HeaderDataList result = FindDescriptorInternal(d, name);
                    if (result != null)
                        return result;
                }
            }
            return null;
        }

        private static HeaderDataList FindDescriptor(HeaderData root, string name)
        {
            HeaderDataList result = FindDescriptorInternal(root, name);
            if (result == null)
                result = FindDescriptorInternal(root.Parent, name);
            return result;
        }

        private static void MergeAggregates(AdvMatrixObject matrix, HeaderDataList source, HeaderData target, bool isColumn)
        {
            if (isColumn)
                MergeColumnAggregates(matrix, source, target);
            else
                MergeRowAggregates(matrix, source, target);
        }

        private static void MergeColumnAggregates(AdvMatrixObject matrix, HeaderDataList source, HeaderData target)
        {
            foreach (HeaderData rowItem in matrix.Data.Rows.Data.AllTerminalItems)
            {
                foreach (HeaderData columnItem in source)
                {
                    foreach (AggregateExpressionPair ag in matrix.Data.CellData.SharedAggregates)
                    {
                        ag.Merge(columnItem.Index, rowItem.Index, target.Index, rowItem.Index);
                    }
                }
            }
        }

        private static void MergeRowAggregates(AdvMatrixObject matrix, HeaderDataList source, HeaderData target)
        {
            foreach (HeaderData columnItem in matrix.Data.Columns.Data.AllTerminalItems)
            {
                foreach (HeaderData rowItem in source)
                {
                    foreach (AggregateExpressionPair ag in matrix.Data.CellData.SharedAggregates)
                    {
                        ag.Merge(columnItem.Index, rowItem.Index, columnItem.Index, target.Index);
                    }
                }
            }
        }

        public static void Process(AdvMatrixObject matrix)
        {
            Func<HeaderDataList, bool, int> func = (topNGroup, isColumn) =>
            {
                HeaderDescriptor descr = topNGroup.Descriptor;
                HeaderData parent = topNGroup.Parent;
                MatrixHeader header = isColumn ? matrix.Data.Columns : matrix.Data.Rows;
                int index = parent.Items.IndexOf(topNGroup) + 1;

                HeaderDataList topNTotal = FindDescriptor(parent, descr.TopN.Total.Name);
                HeaderData topNTotalItem = topNTotal != null ? topNTotal[0] : null; // if owner is present, it will have at least one item
                HeaderDataList othersGroup = FindDescriptor(parent, descr.TopN.Others.Name);
                HeaderDataList othersTotal = FindDescriptor(parent, descr.TopN.OthersTotal.Name);
                HeaderData othersTotalItem = othersTotal != null ? othersTotal[0] : null; // same as above

                if (topNTotal == null || othersGroup == null || othersTotal == null)
                {
                    // simple mode: use mainItem as a template for everything

                    // TopN total
                    topNTotal = new HeaderDataList(parent, descr) { Visible = descr.TopN.Total.Visible };
                    topNTotalItem = new HeaderData(parent, descr) { Value = descr.TopN.Total.Text, Index = header.NextIndex };
                    topNTotal.Add(topNTotalItem);
                    parent.Items.Insert(index, topNTotal);
                    index++;

                    // Others
                    othersGroup = new HeaderDataList(parent, descr) { Visible = descr.TopN.Others.Visible };
                    parent.Items.Insert(index, othersGroup);
                    index++;

                    // Others total
                    othersTotal = new HeaderDataList(parent, descr) { Visible = descr.TopN.OthersTotal.Visible };
                    othersTotalItem = new HeaderData(parent, descr) { Value = descr.TopN.OthersTotal.Text, Index = header.NextIndex };
                    othersTotal.Add(othersTotalItem);
                    parent.Items.Insert(index, othersTotal);
                    index++;
                }
                else
                {
                    topNTotal.Visible = descr.TopN.Total.Visible;
                    othersGroup.Visible = descr.TopN.Others.Visible;
                    othersTotal.Visible = descr.TopN.OthersTotal.Visible;
                }

                // get sorted list of items
                List<HeaderData> list;
                if (topNGroup.Descriptor.SortAggregate != null)
                    list = topNGroup.GetSortedList(false);
                else
                {
                    list = new List<HeaderData>();
                    list.AddRange(topNGroup);
                }

                // leave topN items in the main item, move the rest to others item
                topNGroup.Clear();
                for (int i = 0; i < descr.TopN.Count; i++)
                {
                    topNGroup.Add(list[i]);
                }

                othersGroup.Clear();
                for (int i = descr.TopN.Count; i < list.Count; i++)
                {
                    othersGroup.Add(list[i]);
                }

                // calculate aggregates for total items
                MergeAggregates(matrix, topNGroup, topNTotalItem, isColumn);
                MergeAggregates(matrix, othersGroup, othersTotalItem, isColumn);

                return index;
            };
            EnumTopNItems(matrix.Data.Columns.Data, true, func);
            EnumTopNItems(matrix.Data.Rows.Data, false, func);
        }
    }
}