using System;
using System.Collections;
using System.Collections.Generic;
using FastReport.Gauge;
using FastReport.Gauge.Simple.Progress;
#if MSCHART
using FastReport.MSChart;
#endif
using FastReport.Table;

namespace FastReport.AdvMatrix
{
    internal static class ResultBuilder
    {
        private static int EnumColumnData(HeaderData root, int columnIndex, int rowIndex, Action<HeaderData, int, int, bool> func)
        {
            foreach (HeaderData d in root)
            {
                func(d, columnIndex, rowIndex, true);
                if (d.Items.Count > 0)
                {
                    if (d.Descriptor.Stepped)
                        columnIndex = EnumColumnData(d, columnIndex + 1, rowIndex, func);
                    else
                        columnIndex = EnumColumnData(d, columnIndex, rowIndex + 1, func);
                }
                columnIndex++;
            }
            return columnIndex - 1;
        }

        private static int EnumRowData(HeaderData root, int columnIndex, int rowIndex, Action<HeaderData, int, int, bool> func)
        {
            foreach (HeaderData d in root)
            {
                func(d, columnIndex, rowIndex, false);
                if (d.Items.Count > 0)
                {
                    if (d.Descriptor.Stepped)
                        rowIndex = EnumRowData(d, columnIndex, rowIndex + 1, func);
                    else
                        rowIndex = EnumRowData(d, columnIndex + 1, rowIndex, func);
                }
                rowIndex++;
            }
            return rowIndex - 1;
        }

        private static void PrintHeaderCell(AdvMatrixObject matrix, HeaderData item, TableCellData resultCell, bool isColumn, bool isEven)
        {
            HeaderDescriptor descr = item.Descriptor;
            TableCell templateCell = descr.TemplateCell;

            if (matrix.DataSource != null)
                matrix.DataSource.CurrentRowNo = item.DataRowNo;
            matrix.RowNo = item.RowNo;
            matrix.ItemCount = item.ItemCount;
            MatrixCollapseButton btn = templateCell.GetCollapseButton();
            if (btn != null)
                matrix.ItemCount = btn.GetLinkedItemsCount(item);

            templateCell.SaveState();
            if (isEven)
                templateCell.ApplyEvenStyle();

            object value = item.Value;
            if (value != null)
            {
                templateCell.SetValue(value);
                templateCell.Text = templateCell.Format.FormatValue(value);
            }

            if (!String.IsNullOrEmpty(descr.DisplayText))
            {
                templateCell.Text = descr.DisplayText;
                templateCell.AllowExpressions = true;
            }

            templateCell.GetData();
            
            if (String.IsNullOrEmpty(templateCell.Hyperlink.Expression) &&
                (templateCell.Hyperlink.Kind == HyperlinkKind.DetailReport ||
                templateCell.Hyperlink.Kind == HyperlinkKind.DetailPage ||
                templateCell.Hyperlink.Kind == HyperlinkKind.Custom))
                templateCell.Hyperlink.Value = templateCell.Text;

            if (templateCell.Objects != null)
            {
                foreach (Base c in templateCell.Objects)
                {
                    if (c is MatrixButton)
                    {
                        MatrixButton b = c as MatrixButton;
                        b.IsColumn = isColumn;
                        b.Index = item.Index;
                    }
                    if (c is MatrixCollapseButton)
                    {
                        MatrixCollapseButton b = c as MatrixCollapseButton;
                        foreach (HeaderDataList dl in b.GetLinkedItems(item))
                        {
                            b.Collapsed = !dl.Visible;
                            break;
                        }
                    }
                    if (c is MatrixSortButton)
                    {
                        MatrixSortButton b = c as MatrixSortButton;
                        b.Sort = SortOrder.None;
                        InteractiveSortInfo info = matrix.Data.InteractiveSort;
                        if (isColumn && item.Index == info.Column.Index)
                            b.Sort = info.Column.Sort;
                        if (!isColumn && item.Index == info.Row.Index)
                            b.Sort = info.Row.Sort;
                    }
                }
            }

            resultCell.RunTimeAssign(templateCell, true);
            templateCell.RestoreState();
        }

        private static void PrintCornerCell(TableCell templateCell, TableCellData resultCell)
        {
            templateCell.SaveState();
            templateCell.GetData();
            resultCell.RunTimeAssign(templateCell, true);
            templateCell.RestoreState();
        }

        private static void PrintDataCell(AdvMatrixObject matrix, TableCell templateCell, TableCellData resultCell, bool isEven)
        {
            templateCell.SaveState();
            if (isEven)
                templateCell.ApplyEvenStyle();

            CellDescriptor descriptor = templateCell.GetDescriptor();
            switch (descriptor.ContentType)
            {
                case CellContentType.MixedText:
                    templateCell.AllowExpressions = true;
                    templateCell.Text = descriptor.Text;
                    break;

                case CellContentType.Aggregate:
                    templateCell.SetValue(descriptor.Aggregates[0].GetValue(matrix.Data.Context.ColumnItem.Index, matrix.Data.Context.RowItem.Index));
                    templateCell.Text = templateCell.FormatValue(templateCell.Value);
                    break;

                case CellContentType.SingleExpression:
                    templateCell.SetValue(matrix.Report.Calc(descriptor.Text));
                    templateCell.Text = templateCell.FormatValue(templateCell.Value);
                    break;
            }
            
            templateCell.GetData();
            ProcessCellObjects(templateCell);

            if (String.IsNullOrEmpty(templateCell.Hyperlink.Expression) &&
                (templateCell.Hyperlink.Kind == HyperlinkKind.DetailReport ||
                templateCell.Hyperlink.Kind == HyperlinkKind.DetailPage ||
                templateCell.Hyperlink.Kind == HyperlinkKind.Custom))
            {
                string hyperlinkValue = "";
                string separator = templateCell.Hyperlink.ValuesSeparator;
                foreach (object obj in matrix.ColumnValues)
                {
                    hyperlinkValue += obj.ToString() + separator;
                }
                foreach (object obj in matrix.RowValues)
                {
                    hyperlinkValue += obj.ToString() + separator;
                }
                templateCell.Hyperlink.Value = hyperlinkValue.Substring(0, hyperlinkValue.Length - separator.Length);
            }

            resultCell.RunTimeAssign(templateCell, true);
            templateCell.RestoreState();

            resultCell.ColSpan = templateCell.ColSpan;
            resultCell.RowSpan = templateCell.RowSpan;
        }

        private static IList ReduceValues(this IList list, int valuesCount)
        {
            int ratio = list.Count / valuesCount;
            if (ratio < 2)
                return list;

            ArrayList reducedList = new ArrayList();
            for (int i = 0; i < list.Count; i += ratio)
            {
                dynamic v = list[i];
                for (int j = 0; j < ratio && i + j < list.Count; j++)
                {
                    if ((dynamic)list[i + j] > v)
                        v = list[i + j];
                }
                reducedList.Add(v);
            }
            return reducedList;
        }

        private static void ProcessCellObjects(TableCell templateCell)
        {
            if (templateCell.Objects != null)
            {
                foreach (ReportComponentBase c in templateCell.Objects)
                {
#if MSCHART
                    MSChartObject chart = c as MSChartObject;
                    if (chart != null && chart.Series.Count == 1)
                    {
                        chart.Series[0].ClearValues();
                        if (templateCell.Value is IList)
                        {
                            IList list = (templateCell.Value as IList).ReduceValues((int)chart.Width / 3);
                            for (int i = 0; i < list.Count; i++)
                            {
                                chart.Series[0].AddValue(i, list[i]);
                            }
                        }
                        templateCell.Text = "";
                    }
#endif
                    
                    GaugeObject gauge = c as GaugeObject;
                    if (gauge != null)
                    {
                        if (templateCell.Value == null || templateCell.Value is DBNull)
                            gauge.Visible = false;
                        else
                            gauge.Value = (double)(dynamic)templateCell.Value;
                        templateCell.Text = "";
                    }
                }
            }
        }

        public static void BuildResult(AdvMatrixObject matrix)
        {
            TableResult resultTable = matrix.ResultTable;
            int cornerWidth = matrix.Data.Rows.Size;
            int cornerHeight = matrix.Data.Columns.Size;
            resultTable.FixedColumns = cornerWidth;
            resultTable.FixedRows = cornerHeight;

            // rows/columns in the corner area. Enumerate on descriptors to do it fast.
            // We need this code because some rows and columns may be invisible due to its collapsed state,
            // so its properties will not be assigned to resultTable
            resultTable.ColumnCount = cornerWidth;
            resultTable.RowCount = cornerHeight;
            Action<HeaderDescriptor, int, int, bool> func = (d, columnIndex, rowIndex, isColumn) =>
            {
                if (!isColumn && d.TemplateColumn != null)
                    resultTable.Columns[columnIndex].Assign(d.TemplateColumn);
                if (isColumn && d.TemplateRow != null)
                    resultTable.Rows[rowIndex].Assign(d.TemplateRow);
            };
            TemplateBuilder.EnumColumnDescriptors(matrix.Data.Columns.Descriptor, cornerWidth, 0, func);
            TemplateBuilder.EnumRowDescriptors(matrix.Data.Rows.Descriptor, 0, cornerHeight, func);

            // rows/columns
            Action<HeaderData, int, int, bool> func1 = (data, columnIndex, rowIndex, isColumn) =>
            {
                // create resultTable columns and rows
                while (columnIndex >= resultTable.Columns.Count)
                    resultTable.Columns.Add(new TableColumn());
                while (rowIndex >= resultTable.Rows.Count)
                    resultTable.Rows.Add(new TableRow());

                // assign properties from existing template columns/rows.
                // The same assignment may occur several times, but it's a cheap operation so it's ok
                HeaderDescriptor d = data.Descriptor;
                if (isColumn && d.TemplateColumn != null)
                    resultTable.Columns[columnIndex].Assign(d.TemplateColumn);
                if (!isColumn && d.TemplateRow != null)
                    resultTable.Rows[rowIndex].Assign(d.TemplateRow);

                // page breaks
                if (d.PageBreak && data.RowNo > 1)
                {
                    if (isColumn)
                        resultTable.Columns[columnIndex].PageBreak = true;
                    else
                        resultTable.Rows[rowIndex].PageBreak = true;
                }
            };
            EnumColumnData(matrix.Data.Columns.Data, cornerWidth, 0, func1);
            EnumRowData(matrix.Data.Rows.Data, 0, cornerHeight, func1);

            // corner cells
            for (int left = 0; left < cornerWidth; left++)
            {
                for (int top = 0; top < cornerHeight; top++)
                {
                    TableCellData resultCell = resultTable.GetCellData(left, top);
                    TableCell templateCell = matrix[left, top];
                    PrintCornerCell(templateCell, resultCell);
                    // do not allow spans to go outside corner area. Fix template cells as well (else we will get empty cells in the header area)
                    templateCell.ColSpan = Math.Min(templateCell.ColSpan, cornerWidth - left);
                    templateCell.RowSpan = Math.Min(templateCell.RowSpan, cornerHeight - top);
                    resultCell.ColSpan = templateCell.ColSpan;
                    resultCell.RowSpan = templateCell.RowSpan;
                }
            }

            // header cells
            Action<HeaderData, int, int, bool> func2 = (data, columnIndex, rowIndex, isColumn) =>
            {
                TableCellData resultCell = resultTable.GetCellData(columnIndex, rowIndex);
                // required check for MergeSingleItem
                if (resultTable.IsInsideSpan(resultCell.Cell))
                    return;
                
                PrintHeaderCell(matrix, data, resultCell, isColumn, (isColumn ? columnIndex - cornerWidth : rowIndex - cornerHeight) % 2 == 0);
                HeaderDescriptor descr = data.Descriptor;
                if (isColumn)
                {
                    resultCell.ColSpan = data.Span;
                    if (data.Items.Count == 0 || (descr.MergeSingleItem && data.TerminalItems.Count == 1) || descr.Stepped)
                        resultCell.RowSpan = cornerHeight - rowIndex;
                    else if (descr.MergeSingleItem && data.ItemCount == 1)
                        resultCell.RowSpan = 2;
                }
                else
                {
                    resultCell.RowSpan = data.Span;
                    if (data.Items.Count == 0 || (descr.MergeSingleItem && data.TerminalItems.Count == 1) || descr.Stepped)
                        resultCell.ColSpan = cornerWidth - columnIndex;
                    else if (descr.MergeSingleItem && data.ItemCount == 1)
                        resultCell.ColSpan = 2;
                }

                if (descr.ColSpan > 0)
                    resultCell.ColSpan = descr.ColSpan;
                if (descr.RowSpan > 0)
                    resultCell.RowSpan = descr.RowSpan;
            };
            EnumColumnData(matrix.Data.Columns.Data, cornerWidth, 0, func2);
            EnumRowData(matrix.Data.Rows.Data, 0, cornerHeight, func2);
 
            // data cells
            List<HeaderData> columnTerminalItems = matrix.Data.Columns.Data.TerminalItems;
            List<HeaderData> rowTerminalItems = matrix.Data.Rows.Data.TerminalItems;

            matrix.ColumnIndex = 0;
            foreach (HeaderData columnItem in columnTerminalItems)
            {
                matrix.RowIndex = 0;
                matrix.ColumnValues = columnItem.Values;

                foreach (HeaderData rowItem in rowTerminalItems)
                {
                    matrix.RowValues = rowItem.Values;

                    TableCellData resultCell = resultTable.GetCellData(matrix.ColumnIndex + cornerWidth, matrix.RowIndex + cornerHeight);
                    TableCell templateCell = matrix[columnItem.Descriptor.TemplateColumn.Index, rowItem.Descriptor.TemplateRow.Index];

                    // set context required by aggregate calculation
                    matrix.Data.Context.Descriptor = templateCell.GetDescriptor();
                    matrix.Data.Context.ColumnItem = columnItem;
                    matrix.Data.Context.RowItem = rowItem;

                    if (matrix.DataSource != null)
                        matrix.DataSource.CurrentRowNo = matrix.DataRowPriority == HeaderPriority.Columns ? columnItem.DataRowNo : rowItem.DataRowNo;
                    int evenStyleIndex = matrix.EvenStylePriority == HeaderPriority.Columns ? matrix.ColumnIndex : matrix.RowIndex;
                    PrintDataCell(matrix, templateCell, resultCell, evenStyleIndex % 2 == 0);

                    matrix.RowIndex++;
                }
               
                matrix.ColumnIndex++;
            }
        }
    }
}