using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using FastReport;
using FastReport.Table;
using FastReport.Utils;
using InABox.Core;
using InABox.Scripting;
using InABox.Wpf.Reports;
using InABox.Wpf.Reports.CustomObjects;
using UnderlineType = InABox.Core.UnderlineType;

namespace InABox.DynamicGrid
{

    public static class DigitalFormUtils
    {
        
        #region Layout Importer
        
                private class Cell
        {
            public string Content { get; set; }

            public int Row { get; set; }

            public int Column { get; set; }

            public int RowSpan { get; set; } = 1;

            public int ColumnSpan { get; set; } = 1;

            public ICell InnerCell { get; set; }

            public Cell(int row, int column, string content, ICell cell)
            {
                Row = row;
                Column = column;
                Content = content;
                InnerCell = cell;
            }
        }

        private static void DeleteColumn(List<Cell> cells, int column)
        {
            foreach(var cell in cells)
            {
                if(cell.Column <= column && cell.Column + cell.ColumnSpan - 1 >= column)
                {
                    --cell.ColumnSpan;
                }
                else if(cell.Column > column)
                {
                    --cell.Column;
                }
            }
            cells.RemoveAll(x => x.ColumnSpan < 0);
        }

        private static List<Cell> GetCells(ISheet sheet)
        {
            var grid = new Dictionary<int, Dictionary<int, Cell>>();

            for (int rowIdx = sheet.FirstRow; rowIdx <= sheet.LastRow; ++rowIdx)
            {
                var row = sheet.GetRow(rowIdx);
                if (row is not null && row.FirstColumn >= 0)
                {
                    var rowCells = new Dictionary<int, Cell>();
                    for (int colIdx = row.FirstColumn; colIdx <= row.LastColumn; ++colIdx)
                    {
                        var cell = row.GetCell(colIdx);
                        if (cell is not null)
                        {
                            rowCells.Add(colIdx, new Cell(rowIdx, colIdx, cell.GetValue(), cell));
                        }
                    }
                    grid.Add(rowIdx, rowCells);
                }
            }

            foreach (var region in sheet.GetMergedCells())
            {
                for (int r = region.FirstRow; r <= region.LastRow; ++r)
                {
                    if (!grid.TryGetValue(r, out var row)) continue;

                    for (int c = region.FirstColumn; c <= region.LastColumn; ++c)
                    {
                        if ((r - region.FirstRow) + (c - region.FirstColumn) != 0)
                        {
                            row.Remove(c);
                        }
                    }
                    if (row.Count == 0)
                    {
                        grid.Remove(r);
                    }
                }

                if (grid.TryGetValue(region.FirstRow, out var cRow) && cRow.TryGetValue(region.FirstColumn, out var cCell))
                {
                    cCell.RowSpan = region.LastRow - region.FirstRow + 1;
                    cCell.ColumnSpan = region.LastColumn - region.FirstColumn + 1;
                }
            }

            var cells = new List<Cell>();
            foreach (var row in grid.Values)
            {
                foreach (var cell in row.Values)
                {
                    cells.Add(cell);
                }
            }
            return cells;
        }

        private static Regex VariableRegex = new(@"^\[(?<VAR>[^:\]]+)(?::(?<TYPE>[^:\]]*))?(?::(?<PROPERTIES>[^\]]*))?\]$");
        private static Regex HeaderRegex = new(@"^{(?<HEADER>[^:}]+)(?::(?<COLLAPSED>[^}]*))?}$");

        public static DFLayout LoadLayout(ISpreadsheet spreadsheet)
        {
            var sheet = spreadsheet.GetSheet(0);

            var cells = GetCells(sheet);

            int firstRow = int.MaxValue;
            int lastRow = 0;
            int firstCol = int.MaxValue;
            int lastCol = 0;

            foreach (var cell in cells)
            {
                firstCol = Math.Min(cell.Column, firstCol);
                lastCol = Math.Max(cell.Column + cell.ColumnSpan - 1, lastCol);

                firstRow = Math.Min(cell.Row, firstRow);
                lastRow = Math.Max(cell.Row + cell.RowSpan - 1, lastRow);
            }

            var layout = new DFLayout();

            var columnWidths = new Dictionary<int, float>();
            var colOffset = 0;
            for (int col = firstCol; col <= lastCol; ++col)
            {
                var width = sheet.GetColumnWidth(col);
                if(width == float.MinValue)
                {
                    layout.ColumnWidths.Add("10*");
                }
                else if(width <= 0f)
                {
                    DeleteColumn(cells, col);
                }
                else
                {
                    layout.ColumnWidths.Add($"{width}*");
                }
            }

            for (int row = firstRow; row <= lastRow; ++row)
                layout.RowHeights.Add("Auto");

                foreach(var cell in cells)
            {
                var style = cell.InnerCell.GetStyle();

                if (string.IsNullOrWhiteSpace(cell.Content) && style.Foreground == Color.Empty) continue;

                DFLayoutControl? control;
                var content = cell.Content?.Trim() ?? "";

                var headermatch = HeaderRegex.Match(content);
                var variablematch = VariableRegex.Match(content);

                if (headermatch.Success)
                {
                    var text = headermatch.Groups["HEADER"];
                    var collapsed = headermatch.Groups["COLLAPSED"];
                    var header = new DFLayoutHeader()
                    {
                        Header = text.Value, 
                        Collapsed = collapsed.Success && String.Equals(collapsed.Value.ToUpper(),"COLLAPSED"),
                    };
                    header.Style.Synchronise(CreateStyle(style));
                    
                    control = header;
                }
                else if (variablematch.Success)
                {
                    var variableName = variablematch.Groups["VAR"];
                    var variableType = variablematch.Groups["TYPE"];
                    var variableProps = variablematch.Groups["PROPERTIES"];
                    
                    Type? fieldType = null;
                    if (variableType.Success)
                        fieldType = DFUtils.GetFieldType(variableType.Value);
                    fieldType ??= typeof(DFLayoutStringField);
                    
                    var field = (Activator.CreateInstance(fieldType) as DFLayoutField)!;
                    field.Name = variableName.Value;
                    
                    if (variableProps.Success)
                    {
                        if (field is DFLayoutOptionField option)
                            option.Properties.Options = variableProps.Value;
                        if (field is DFLayoutStringField text)
                            text.Properties.TextWrapping = style.WrapText;
                        // need to populate other variable types here
                    }
                    
                    
                    control = field;
                }
                else
                {
                    var label = new DFLayoutLabel
                    {
                        Caption = content
                    };
                    label.Style.Synchronise(CreateStyle(style));
                    control = label;
                }
                
                if(control is not null)
                {
                    control.Row = cell.Row - firstRow + 1;
                    control.Column = cell.Column - firstCol + 1 - colOffset;
                    control.RowSpan = cell.RowSpan;
                    control.ColumnSpan = cell.ColumnSpan;
                    layout.Elements.Add(control);
                }
            }

            return layout;
        }

        private static DFLayoutTextStyle CreateStyle(ICellStyle style)
        {
            if (style == null)
                return new DFLayoutTextStyle();

            var result = new DFLayoutTextStyle
            {
                FontSize = style.Font.FontSize,

                IsItalic = style.Font.Italic,
                IsBold = style.Font.Bold,
                Underline = style.Font.Underline switch
                {
                    Scripting.UnderlineType.None => UnderlineType.None,
                    Scripting.UnderlineType.Single or Scripting.UnderlineType.SingleAccounting => UnderlineType.Single,
                    Scripting.UnderlineType.Double or Scripting.UnderlineType.DoubleAccounting => UnderlineType.Double,
                    _ => UnderlineType.None
                },
                BackgroundColour = style.Background,
                ForegroundColour = style.Font.Colour,

                HorizontalTextAlignment = style.HorizontalAlignment switch
                {
                     CellAlignment.Middle => DFLayoutAlignment.Middle,
                     CellAlignment.End => DFLayoutAlignment.End,
                     CellAlignment.Justify => DFLayoutAlignment.Stretch,
                     _ => DFLayoutAlignment.Start
                },
                
                VerticalTextAlignment = style.VerticalAlignment switch
                {
                    CellAlignment.Start => DFLayoutAlignment.Start,
                    CellAlignment.End => DFLayoutAlignment.End,
                    CellAlignment.Justify => DFLayoutAlignment.Stretch,
                    _ => DFLayoutAlignment.Middle
                },
                
                TextWrapping = style.WrapText

            };
            return result;
        }
        
        #endregion
        
        #region Report Generator

        private static string ElementName(HashSet<string> names, string name)
        {
            int i = 0;
            if(names.Contains(name))
            {
                string newName;
                do
                {
                    newName = $"{name}{i}";
                    ++i;
                } while (names.Contains(newName));
                name = newName;
            }
            return name;
        }
        
        
        
        public static Report? GenerateReport(DigitalFormLayout layout, DataModel model)
        {
            
            bool IsValidChar(char c) =>  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789".Contains(c);
            
            var report = ReportUtils.SetupReport(null, model, true);

            var dfLayout = new DFLayout();
            dfLayout.LoadLayout(layout.Layout);

            var page = new ReportPage();
            page.Name = "Page1";
            page.PaperWidth = 210;
            page.PaperHeight = 297;
            page.Landscape = false;
            page.LeftMargin = 10;
            page.TopMargin = 10;
            page.RightMargin = 10;
            page.BottomMargin = 10;
            report.Pages.Add(page);

            var formData = report.GetDataSource("Form_Data");

            var band = new DataBand
            {
                Name = "Data1",
                Height = Units.Millimeters * (page.PaperHeight - (page.TopMargin + page.BottomMargin)),
                Width = Units.Millimeters * (page.PaperWidth - (page.LeftMargin + page.RightMargin)),
                PrintIfDatasourceEmpty = true,
                DataSource = formData,
                StartNewPage = true
            };
            page.AddChild(band);

            var logo = new PictureObject()
            {
                Height = 20F * Units.Millimeters,
                Width = 30F * Units.Millimeters,
                DataColumn = "CompanyLogo.Data"
            };
            band.AddChild(logo);

            var company = new TextObject()
            {
                Left = band.Width - (90F * Units.Millimeters),
                Width = 90F * Units.Millimeters,
                Height = 5F * Units.Millimeters,
                Text = "[CompanyInformation.CompanyName]",
                HorzAlign = HorzAlign.Right,
                VertAlign = VertAlign.Center,
                Font = new System.Drawing.Font("Arial", 12F, FontStyle.Bold)
            };
            band.AddChild(company);

            var address = new TextObject()
            {
                Left = band.Width - (90F * Units.Millimeters),
                Width = 90F * Units.Millimeters,
                Height = 15F * Units.Millimeters,
                Top = 5F * Units.Millimeters,
                Text = "[CompanyInformation.PostalAddress_Street]\n[CompanyInformation.PostalAddress_City] [CompanyInformation.PostalAddress_PostCode]",
                HorzAlign = HorzAlign.Right,
                VertAlign = VertAlign.Top,
                Font = new System.Drawing.Font("Arial", 12F)
            };
            band.AddChild(address);
            
            var instancetable = model
                .GetType()
                .GetInterfaces()
                .FirstOrDefault(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDataModel<>))?
                .GetGenericArguments()
                .FirstOrDefault()?
                .EntityName()
                .Split('.')
                .Last();

            var title = new TextObject()
            {
                Top = 23F * Units.Millimeters,
                Width = band.Width,
                Height = 6F * Units.Millimeters,
                Text = $"[Form_Data.{instancetable}.Number] - {layout.Form.Description}",
                HorzAlign = HorzAlign.Center,
                VertAlign = VertAlign.Center,
                Font = new System.Drawing.Font("Arial", 14F, FontStyle.Bold)
            };
            band.AddChild(title);
            /*
            <TextObject Name="Text2" Left="500.85" Top="28.35" Width="207.9" Height="56.7" Text="16 Madrid Place&#13;&#10;Maddington WA 6109&#13;&#10;Phone: (08) 9492 1200&#13;&#10;Email:  admin@com-al.com.au" HorzAlign="Right" Font="Arial, 9pt"/>
            <PictureObject Name="Picture1" Left="9.45" Top="9.45" Width="113.4" Height="75.6" DataColumn="CompanyLogo.Data"/>
            <TextObject Name="Text1" Left="444.15" Top="9.45" Width="264.6" Height="18.9" Text="Com-Al Windows Pty Ltd" HorzAlign="Right" Font="Arial, 10pt, style=Bold" TextFill.Color="RoyalBlue"/>
            <TextObject Name="Text3" Left="9.45" Top="103.95" Width="699.3" Height="28.35" Text="TEST &amp; TAG REPORT: [KanbanForm.Number]" HorzAlign="Center" VertAlign="Center" Font="Arial, 14pt, style=Bold"/>
            */
            
            var elementNames = new HashSet<string>();

            var table = new TableObject()
            {
                Name = "FormTable",
                ColumnCount = dfLayout.ColumnWidths.Count,
                RowCount = dfLayout.RowHeights.Count,
                Top = 32F * Units.Millimeters
            };
            band.AddChild(table);
            foreach(var element in dfLayout.Elements)
            {
                if (element.Row < 1 || element.Row + element.RowSpan - 1 > table.RowCount
                    || element.Column < 1 || element.Column + element.ColumnSpan - 1 > table.ColumnCount) continue;

                var row = table.Rows[element.Row - 1];
                if(row.ChildObjects[element.Column - 1] is TableCell cell)
                {
                    cell.Border.Lines = BorderLines.All;
                    cell.ColSpan = element.ColumnSpan;
                    cell.RowSpan = element.RowSpan;
                    if (element is DFLayoutField field)
                    {
                        var manualHeight = 0.0f;

                        cell.Name = ElementName(elementNames, $"Cell_{new string(field.Name.Where(c => IsValidChar(c)).ToArray())}");

                        var dataColumn = $"Form_Data.{field.Name}";
                        if(field is DFLayoutEmbeddedImage || field is DFLayoutSignaturePad)
                        {
                            var picture = new PictureObject
                            {
                                DataColumn = dataColumn,
                                //Dock = DockStyle.Fill,
                                //Padding = new Padding(5,5,5,5)
                            };
                            //cell.Padding = new Padding(5);
                            cell.AddChild(picture);

                            manualHeight = 40;
                        }
                        else if(field is DFLayoutMultiImage)
                        {
                            var image = new MultiImageObject
                            {
                                DataColumn = dataColumn,
                                //Dock = DockStyle.Fill,
                                //Padding = new Padding(5,5,5,5)
                            };
                            cell.AddChild(image);

                            manualHeight = 40;
                        }
                        else if(field is DFLayoutMultiSignaturePad)
                        {
                            var image = new MultiSignatureObject
                            {
                                DataColumn = dataColumn,
                                //Dock = DockStyle.Fill,
                                //Padding = new Padding(5,5,5,5)
                            };
                            cell.AddChild(image);

                            manualHeight = 40;
                        }
                        else
                        {
                            cell.Text = $"[{dataColumn}]";
                            cell.Font = new System.Drawing.Font(cell.Font.FontFamily, 10F, FontStyle.Italic);
                            cell.TextColor = Color.Navy;
                            cell.HorzAlign = HorzAlign.Left;
                            cell.VertAlign = VertAlign.Center;
                            if (field is DFLayoutStringField lsf)
                                cell.WordWrap = lsf.Properties.TextWrapping;
                        }

                        if(manualHeight > 0 && dfLayout.RowHeights[element.Row - 1] == "Auto")
                        {
                            dfLayout.RowHeights[element.Row - 1] = manualHeight.ToString();
                        }
                    }
                    else if (element is DFLayoutLabel label)
                    {
                        var background = label.Style.BackgroundColour;
                        if (background == Color.Empty)
                        {
                            label.Style.BackgroundColour = Color.WhiteSmoke;
                        }
                        cell.Text = label.Description;
                        cell.Name = ElementName(elementNames, "Label_" + element.Row + "_" + element.Column);
                        ApplyStyle(cell, label.Style);
                    }
                    else if (element is DFLayoutHeader header)
                    {
                        cell.Name = ElementName(elementNames, "Header_" + element.Row + "_" + element.Column);
                        cell.Text = header.Header;
                        ApplyStyle(cell, header.Style);
                    }
                }
            }

            ProcessColumnWidths(band.Width, dfLayout.ColumnWidths, table.Columns);
            ProcessRowHeights(band.Height, dfLayout.RowHeights, table.Rows);

            return report;
        }

        private static void ApplyStyle(TableCell cell, DFLayoutTextStyle style)
        {
            var background = style.BackgroundColour;
            if(background != Color.Empty) cell.FillColor = background;

            var foreground = style.ForegroundColour;
            if (foreground != Color.Empty) cell.TextColor = foreground;

            FontStyle fontstyle = System.Drawing.FontStyle.Regular;
            if (style.IsBold)
                fontstyle |= System.Drawing.FontStyle.Bold;
            if (style.IsItalic)
                fontstyle |= System.Drawing.FontStyle.Italic;
            if (style.Underline != UnderlineType.None)
                fontstyle |= FontStyle.Underline;
            float fontsize = (float)style.FontSize;
            fontsize = fontsize == 0F ? 10F : fontsize;
            cell.Font = new System.Drawing.Font(cell.Font.FontFamily, fontsize, fontstyle);
            cell.HorzAlign = style.HorizontalTextAlignment switch
            {
                DFLayoutAlignment.Start => HorzAlign.Left,
                DFLayoutAlignment.Middle => HorzAlign.Center,
                DFLayoutAlignment.End => HorzAlign.Right,
                DFLayoutAlignment.Stretch => HorzAlign.Justify,
                _ => HorzAlign.Left
            };
            cell.VertAlign = style.VerticalTextAlignment switch
            {
                DFLayoutAlignment.Start => VertAlign.Top,
                DFLayoutAlignment.Middle => VertAlign.Center,
                DFLayoutAlignment.End => VertAlign.Bottom,
                DFLayoutAlignment.Stretch => VertAlign.Center,
                _ => VertAlign.Center
            };
            cell.WordWrap = style.TextWrapping;
        }

        private static void ProcessRowHeights(float bandheight, List<string> values, TableRowCollection rows)
        {
            float fixedtotal = 0F;
            for (int iFixed = 0; iFixed < values.Count; iFixed++)
            {
                if (!values[iFixed].Contains("*"))
                {
                    if (!float.TryParse(values[iFixed], out float value))
                        value = 25F / Units.Millimeters;
                    else
                        value = value / (1.5f * Units.Millimeters);
                    rows[iFixed].Height = Units.Millimeters * value;
                    fixedtotal += Units.Millimeters * value;
                }
            }

            Dictionary<int, float> starvalues = new Dictionary<int, float>();
            float startotal = 0F;
            for (int iStar = 0; iStar < values.Count; iStar++)
            {
                if (values[iStar].Contains('*'))
                {
                    if (!float.TryParse(values[iStar].Replace("*", ""), out float value))
                        value += 1;
                    starvalues[iStar] = value;
                    startotal += value;
                }
            }
            foreach (var key in starvalues.Keys)
                rows[key].Height = (starvalues[key] / startotal) * (bandheight - fixedtotal);        
        }

        private static void ProcessColumnWidths(float bandwidth, List<string> values, TableColumnCollection columns)
        {
            float fixedtotal = 0F;
            for (int iFixed = 0; iFixed < values.Count; iFixed++)
            {
                if (!values[iFixed].Contains("*"))
                {
                    if (!float.TryParse(values[iFixed], out float value))
                        value = 40F;
                    columns[iFixed].Width = Units.Millimeters * value;
                    fixedtotal += Units.Millimeters * value;
                }
            }

            Dictionary<int, float> starvalues = new Dictionary<int, float>();
            float startotal = 0F;
            for (int iStar = 0; iStar < values.Count; iStar++)
            {
                if (values[iStar].Contains('*'))
                {
                    if (!float.TryParse(values[iStar].Replace("*", ""), out float value))
                        value += 1;
                    starvalues[iStar] = value;
                    startotal += value;
                }
            }
            
            foreach (var key in starvalues.Keys)
                columns[key].Width = (starvalues[key] / startotal) * (bandwidth - fixedtotal);
        }
        
        #endregion
        
        #region Data Model
        
        private static List<Type>? _entityForms;

        public static DataModel? GetDataModel(String appliesto, IEnumerable<DigitalFormVariable> variables)
        {
            _entityForms ??= CoreUtils.Entities
                .Where(x => x.IsSubclassOfRawGeneric(typeof(EntityForm<,,>)))
                .ToList();
            var entityForm = _entityForms
                .FirstOrDefault(x => x.GetSuperclassDefinition(typeof(EntityForm<,,>))
                    ?.GenericTypeArguments[0].Name == appliesto);
            if(entityForm is not null)
            {
                var model = (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>).MakeGenericType(entityForm), Filter.Create(entityForm).None(), null) as DataModel)!;
                (model as IDigitalFormReportDataModel)!.Variables = variables.ToArray();
                return model;
            }
            return null;
        }
        
        #endregion
        
    }
    
}