using System;
using FastReport.Data;
using FastReport.Table;

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

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

        private static void CopyFromResultTable(AdvMatrixObject matrix, TableResult resultTable)
        {
            matrix.ColumnCount = resultTable.ColumnCount;
            matrix.RowCount = resultTable.RowCount;
            matrix.CreateUniqueNames();

            for (int x = 0; x < matrix.Columns.Count; x++)
            {
                matrix.Columns[x].Assign(resultTable.Columns[x]);
            }

            for (int y = 0; y < matrix.Rows.Count; y++)
            {
                matrix.Rows[y].Assign(resultTable.Rows[y]);
            }

            for (int x = 0; x < matrix.Columns.Count; x++)
            {
                for (int y = 0; y < matrix.Rows.Count; y++)
                {
                    TableCell cell = matrix[x, y];
                    // call AssignAll with assignName parameter to preserve names of objects contained in a cell
                    resultTable[x, y].Name = cell.Name;
                    cell.AssignAll(resultTable[x, y], true);
                    cell.SetFlags(Flags.CanEdit, true);
                }
            }
        }

        private static TableCell CreateHeaderCell(AdvMatrixObject matrix, HeaderDescriptor descr)
        {
            TableCell cell = new TableCell();
            cell.HorzAlign = HorzAlign.Center;
            cell.VertAlign = VertAlign.Center;
            ApplyStyle(matrix, cell, "Header");

            if (descr != null)
            {
                if (descr.IsGroup)
                    cell.Text = descr.Expression;
                else
                    cell.Text = descr.DisplayText;
            }
            return cell;
        }

        private static TableCell CreateCell(AdvMatrixObject matrix)
        {
            TableCell cell = new TableCell();
            cell.HorzAlign = HorzAlign.Right;
            cell.VertAlign = VertAlign.Center;
            ApplyStyle(matrix, cell, "Body");
            return cell;
        }

        private static void ApplyStyle(AdvMatrixObject matrix, TableCell cell, string styleName)
        {
            Style style = null;
            int styleIndex = matrix.StyleSheet.IndexOf(matrix.Style);
            if (styleIndex != -1)
            {
                StyleCollection styles = matrix.StyleSheet[styleIndex];
                style = styles[styles.IndexOf(styleName)];
            }

            if (style != null)
                cell.ApplyStyle(style);
        }

        public static void UpdateStyle(AdvMatrixObject matrix)
        {
            int cornerWidth = matrix.Data.Rows.Size;
            int cornerHeight = matrix.Data.Columns.Size;
            for (int y = 0; y < matrix.RowCount; y++)
            {
                for (int x = 0; x < matrix.ColumnCount; x++)
                {
                    string style = x < cornerWidth || y < cornerHeight ? "Header" : "Body";
                    ApplyStyle(matrix, matrix[x, y], style);
                }
            }
        }

        public static void UpdateDescriptors(AdvMatrixObject matrix)
        {
            int cornerWidth = matrix.Data.Rows.Size;
            int cornerHeight = matrix.Data.Columns.Size;

            // update header descriptors
            Action<HeaderDescriptor, int, int, bool> func = (d, columnIndex, rowIndex, isColumn) =>
            {
                d.Matrix = matrix;
                d.IsColumn = isColumn;
                d.TemplateColumn = matrix.Columns[columnIndex];
                d.TemplateRow = matrix.Rows[rowIndex];
                d.TemplateCell = matrix[columnIndex, rowIndex];
                if (d.IsGroup)
                    d.TemplateCell.AllowExpressions = false;
                if (!d.IsGroup && String.IsNullOrEmpty(d.DisplayText))
                    d.DisplayText = d.TemplateCell.Text;
            };
            EnumColumnDescriptors(matrix.Data.Columns.Descriptor, cornerWidth, 0, func);
            EnumRowDescriptors(matrix.Data.Rows.Descriptor, 0, cornerHeight, func);

            // create cell descriptors
            matrix.Data.CellData.InitDescriptors();
        }

        public static void BuildTemplate(AdvMatrixObject matrix)
        {
            TableResult resultTable = new TableResult();
            int cornerWidth = matrix.Data.Rows.Size;
            int cornerHeight = matrix.Data.Columns.Size;

            TableColumn[] cornerColumns = new TableColumn[cornerWidth];
            TableRow[] cornerRows = new TableRow[cornerHeight];

            Action<HeaderDescriptor, int, int, bool> func = (d, columnIndex, rowIndex, isColumn) =>
            {
                // create resultTable columns and rows. Assign properties from existing template columns/rows
                while (columnIndex >= resultTable.Columns.Count)
                    resultTable.Columns.Add(new TableColumn());
                while (rowIndex >= resultTable.Rows.Count)
                    resultTable.Rows.Add(new TableRow());
                // the same assignment may occur several times, but it's a cheap operation so it's ok
                if (d.TemplateColumn != null)
                {
                    resultTable.Columns[columnIndex].Assign(d.TemplateColumn);
                    if (columnIndex < cornerWidth)
                        cornerColumns[columnIndex] = d.TemplateColumn;
                }
                if (d.TemplateRow != null)
                {
                    resultTable.Rows[rowIndex].Assign(d.TemplateRow);
                    if (rowIndex < cornerHeight)
                        cornerRows[rowIndex] = d.TemplateRow;
                }
            };
            EnumColumnDescriptors(matrix.Data.Columns.Descriptor, cornerWidth, 0, func);
            EnumRowDescriptors(matrix.Data.Rows.Descriptor, 0, cornerHeight, func);

            // header cells
            Action<HeaderDescriptor, int, int, bool> func1 = (d, columnIndex, rowIndex, isColumn) =>
            {
                TableCellData resultCell = resultTable.GetCellData(columnIndex, rowIndex);
                if (d.TemplateCell != null)
                    resultCell.RunTimeAssign(d.TemplateCell, true);
                else
                    resultCell.RunTimeAssign(CreateHeaderCell(matrix, d), true);

                if (d.IsGroup)
                    resultCell.Text = d.Expression;
                if (!d.IsGroup && !String.IsNullOrEmpty(d.DisplayText))
                    resultCell.Text = d.DisplayText;

                if (isColumn)
                {
                    resultCell.ColSpan = d.Span;
                    if (d.Items.Count == 0 || d.Stepped)
                        resultCell.RowSpan = cornerHeight - rowIndex;
                }
                else
                {
                    resultCell.RowSpan = d.Span;
                    if (d.Items.Count == 0 || d.Stepped)
                        resultCell.ColSpan = cornerWidth - columnIndex;
                }
            };
            EnumColumnDescriptors(matrix.Data.Columns.Descriptor, cornerWidth, 0, func1);
            EnumRowDescriptors(matrix.Data.Rows.Descriptor, 0, cornerHeight, func1);

            // corner
            int left, top;
            for (left = 0; left < cornerWidth; left++)
            {
                for (top = 0; top < cornerHeight; top++)
                {
                    TableCellData resultCell = resultTable.GetCellData(left, top);
                    if (cornerColumns[left] != null && cornerRows[top] != null)
                    {
                        TableCell templateCell = matrix[cornerColumns[left].Index, cornerRows[top].Index];
                        resultCell.RunTimeAssign(templateCell, true);
                        // do not allow spans to go outside corner area
                        resultCell.ColSpan = Math.Min(templateCell.ColSpan, cornerWidth - left);
                        resultCell.RowSpan = Math.Min(templateCell.RowSpan, cornerHeight - top);
                    }
                    else
                    {
                        resultCell.RunTimeAssign(CreateHeaderCell(matrix, null), true);
                    }
                }
            }

            // cells
            left = cornerWidth;
            foreach (HeaderDescriptor columnItem in matrix.Data.Columns.Descriptor.TerminalItems)
            {
                top = cornerHeight;
                foreach (HeaderDescriptor rowItem in matrix.Data.Rows.Descriptor.TerminalItems)
                {
                    TableCellData resultCell = resultTable.GetCellData(left, top);
                    if (columnItem.TemplateColumn != null && rowItem.TemplateRow != null)
                    {
                        TableCell templateCell = matrix[columnItem.TemplateColumn.Index, rowItem.TemplateRow.Index];
                        resultCell.RunTimeAssign(templateCell, true);
                        resultCell.ColSpan = templateCell.ColSpan;
                        resultCell.RowSpan = templateCell.RowSpan;
                    }
                    else
                    {
                        resultCell.RunTimeAssign(CreateCell(matrix), true);
                    }
                    top++;
                }
                left++;
            }

            // copy resultTable to matrix
            CopyFromResultTable(matrix, resultTable);
            UpdateDescriptors(matrix);
            resultTable.Dispose();
        }
    }
}