//using Ghostscript.NET;
//using Ghostscript.NET.Processor;

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using FastReport;
using FastReport.Data;
using FastReport.Export.Pdf;
using FastReport.Utils;
using InABox.Clients;
using InABox.Core;
using InABox.Core.Reports;
using InABox.Scripting;
using InABox.Wpf.Reports.CustomObjects;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Parsing;
using XmlDocument = System.Xml.XmlDocument;

namespace InABox.Wpf.Reports
{
    public enum ReportExportType
    {
        PDF,
        HTML,
        RTF,
        Excel,
        Text,
        Image
    }

    public delegate void ReportExportDefinitionClicked();

    public class ReportExportDefinition
    {
        public ReportExportDefinition(string caption, Bitmap image, ReportExportType type, Action<DataModel, byte[]> action)
        {
            Caption = caption;
            Image = image;
            Type = type;
            Action = action;
        }

        public ReportExportDefinition(string caption, ContentControl control, ReportExportType type, Action<DataModel, byte[]> action)
        {
            Caption = caption;
            Type = type;
            Action = action;
            Control = control;
        }

        public event ReportExportDefinitionClicked OnReportDefinitionClicked;

        public string Caption { get; }

        public Bitmap Image { get; }

        public ContentControl Control { get; }

        public ReportExportType Type { get; }

        public Action<DataModel, byte[]> Action { get; }
    }

    [LibraryInitializer]
    public static class ReportUtils
    {
        public static List<ReportExportDefinition> ExportDefinitions { get; } = new();

        public static void RegisterClasses()
        {
            foreach (string printer in PrinterSettings.InstalledPrinters)
                ReportPrinters.Register(printer);

            Application.Current.Dispatcher.BeginInvoke(() =>
            {
                RegisteredObjects.Add(typeof(MultiImageObject), "ReportPage", Wpf.Resources.multi_image, "Multi-Image");
                RegisteredObjects.Add(typeof(MultiSignatureObject), "ReportPage",Wpf.Resources.signature, "Multi-Signature");
                RegisteredObjects.Add(typeof(HTMLView), "ReportPage",Wpf.Resources.view, "HTML");
            });
        }

        public static void PreviewReport(ReportTemplate template, DataModel data, bool printandclose = false, bool allowdesigner = false)
        {
            if (template.IsRDL)
            {
                MessageBox.Show("RDL Reports are no longer supported!");
            }
            else
            {
                PreviewWindow window = new(template, data)
                {
                    AllowDesign = allowdesigner,
                    Title = string.Format("Report Preview - {0}", template.Name),
                    Height = 800,
                    Width = 1200,
                    WindowState = WindowState.Maximized,
                };
                window.Show();
            }

        }

        public static Report SetupReport(ReportTemplate? template, DataModel data, bool repopulate)
        {
            var templaterdl = template != null ? string.IsNullOrWhiteSpace(template.RDL) ? "" : template.RDL : "";
            var tables = new List<string>();
            if (!string.IsNullOrWhiteSpace(templaterdl))
            {
                var xml = new XmlDocument();
                xml.LoadString(templaterdl);
                var datasources = xml.GetElementsByTagName("TableDataSource");
                for (var i = 0; i < datasources.Count; i++)
                {
                    var datasource = datasources[i];
                    tables.Add(datasource.Attributes.GetNamedItem("Name").Value);
                }
            }
            else
            {
                foreach(var table in data.DefaultTables)
                {
                    tables.Add(table.TableName);
                }
            }

            var report = new Report();
            report.Log += (sender, args) =>
                Logger.Send(args.IsError ? LogType.Error : LogType.Information, "", args.Message);
            Config.ReportSettings.ShowProgress = false;
            report.LoadFromString(templaterdl);
            report.FileName = template?.Name ?? "";

            foreach(var tableName in data.TableNames)
            {
                var modelTable = data.GetDataModelTable(tableName);
                var dataSource = report.GetDataSource(tableName);
                if (dataSource != null && modelTable.Type is not null)
                {
                    var columnNames = CoreUtils.GetColumnNames(modelTable.Type, x => true);
                    foreach (var column in dataSource.Columns)
                    {
                        if(column is FastReport.Data.Column col && !col.Enabled)
                        {
                            //columns.Add(col.Name.Replace('_','.'));
                            columnNames.Remove(col.Name.Replace('_', '.'));
                        }
                        /*if (column is FastReport.Data.Column col && col.DataType != null && col.DataType.IsAssignableTo(typeof(IEnumerable<byte[]>)))
                        {
                            col.BindableControl = ColumnBindableControl.Custom;
                            col.CustomBindableControl = "MultiImageObject";
                        }*/
                    }
                    modelTable.Columns = Columns.None(modelTable.Type).Add(columnNames);
                }
            }

            ScriptDocument? script = null;
            bool ScriptOK = false;
            if (!string.IsNullOrWhiteSpace(template?.Script))
            {
                script = new ScriptDocument(template.Script);
                if (script.Compile())
                {
                    script.SetValue("Model", data);
                    ScriptOK = script.Execute("Report", "Init");
                }
            }

            if (repopulate)
                data.LoadModel(tables);

            if (ScriptOK && script is not null)
            {
                script.SetValue("RequireTables", tables);
                script.Execute("Report", "Populate");
            }

            var ds = data.AsDataSet();
            report.RegisterData(ds);

            foreach (var tableName in data.TableNames)
            {
                var columnNames = data.GetColumns(tableName)?.ColumnNames().ToHashSet();
                var dataSource = report.GetDataSource(tableName);
                if(dataSource != null)
                {
                    foreach (var column in dataSource.Columns)
                    {
                        if (column is FastReport.Data.Column col)
                        {
                            if (col.DataType != null && col.DataType.IsAssignableTo(typeof(IEnumerable<byte[]>)))
                            {
                                col.BindableControl = ColumnBindableControl.Custom;
                                col.CustomBindableControl = "MultiImageObject";
                            }
                            col.Enabled = columnNames is null || columnNames.Contains(col.Name.Replace('_', '.'));
                        }
                    }
                }
            }

            if (string.IsNullOrWhiteSpace(templaterdl))
            {
                foreach (var table in data.DefaultTables)
                {
                    var dataSource = report.GetDataSource(table.TableName);
                    dataSource.Enabled = true;
                }

                foreach (Relation relation in report.Dictionary.Relations)
                    if (data.DefaultTables.Any(x => x.TableName.Equals(relation.ParentDataSource.Alias)) &&
                        data.DefaultTables.Any(x => x.TableName.Equals(relation.ChildDataSource.Alias)))
                        relation.Enabled = true;
            }


            return report;
        }

        public static void DesignReport(ReportTemplate template, DataModel data, bool populate = false, Action<ReportTemplate>? saveTemplate = null)
        {
            var isrdl = template != null && template.IsRDL;
            var templaterdl = template != null ? string.IsNullOrWhiteSpace(template.RDL) ? "" : template.RDL : "";
            if (isrdl)
                MessageBox.Show("RDL Reports are not supported!");
            else
            {
                PreviewWindow window = new(template, data)
                {
                    AllowDesign = true,
                    Title = string.Format("Report Designer - {0}", template.Name),
                    Height = 800,
                    Width = 1200,
                    WindowState = WindowState.Maximized,
                    IsPreview = false,
                    ShouldPopulate = populate,
                    SaveTemplate = saveTemplate
                };
                window.Show();
            }
                
        }

        public static byte[] CompressPDF(byte[] original)
        {

                //Load the existing PDF document
                PdfLoadedDocument loadedDocument = new PdfLoadedDocument(original);

                //Create a new compression option.
                PdfCompressionOptions options = new PdfCompressionOptions();
                //Enable the compress image.
                options.CompressImages = true;
                //Set the image quality.
                options.ImageQuality = 50;
                
                //Assign the compression option to the document.
                loadedDocument.CompressionOptions = options;
                
                //Creating the stream object.
                using (MemoryStream stream = new MemoryStream())
                {
                    //Save the document into stream.
                    loadedDocument.Save(stream);
                    return stream.GetBuffer();
                }
            
        }

        public static byte[] ReportToPDF(ReportTemplate template, DataModel data, bool repopulate = true)
        {
            byte[] result = null;
            if (template.IsRDL)
                MessageBox.Show("RDL Reports are not supported!");
            else
                using (var report = SetupReport(template, data, repopulate))
                {
                    report.Prepare();
                    var ms = new MemoryStream();
                    report.Export(new PDFExport() { JpegCompression = true, JpegQuality = 65, Compressed = true }, ms);
                    result = ms.GetBuffer();
                }

            return result;
        }

        public static Filter<ReportTemplate> GetReportFilter(string sectionName, DataModel model)
        {
            return new Filter<ReportTemplate>(x => x.DataModel).IsEqualTo(model.Name)
                    .And(x => x.Section).IsEqualTo(sectionName)
                    .And(x => x.Visible).IsEqualTo(true);
        }

        public static IEnumerable<ReportTemplate> LoadReports(string sectionName, DataModel model, Columns<ReportTemplate>? columns = null)
        {
            return new Client<ReportTemplate>().Query(
                GetReportFilter(sectionName, model),
                columns,
                new SortOrder<ReportTemplate>(x => x.Name))
                .ToObjects<ReportTemplate>();
        }

        private static void PopulateMenu(ItemsControl menu, string sectionName, DataModel model, bool allowdesign, bool populate = false)
        {
            var reports = LoadReports(sectionName, model);
            foreach (var report in reports)
            {
                var print = new MenuItem
                {
                    Header = report.Name,
                    Tag = report
                };
                print.Click += (sender, args) =>
                {
                    PreviewReport(report, model, false, allowdesign);
                };
                menu.Items.Add(print);
            }

            if (allowdesign)
            {
                if (menu.Items.Count > 0 && menu.Items[^1] is not Separator)
                {
                    menu.Items.Add(new Separator());
                }
                var manage = new MenuItem
                {
                    Header = "Manage Reports"
                };
                manage.Click += (sender, args) =>
                {
                    var manager = new ReportManager()
                    {
                        DataModel = model,
                        Section = sectionName,
                        Populate = populate
                    };
                    manager.ShowDialog();
                };
                menu.Items.Add(manage);
            }
        }

        /// <summary>
        ///     Populate the <paramref name="menu"/> with a list of reports attached to this data model, loaded through <see cref="LoadReports(string, DataModel, Columns{ReportTemplate}?)"/>.
        /// </summary>
        /// <remarks>
        ///     This is used for the various places we have context menus for printing reports.
        /// </remarks>
        /// <param name="allowdesign">Allow designing reports; if <see langword="true"/>, then also adds a "Manage Reports" button.</param>
        /// <param name="populate">Determines whether the datamodel should be reloaded when printing a report.<br/>Set to <see langword="false"/> if all the data has already been loaded.</param>
        public static void PopulateMenu(MenuItem menu, string sectionName, DataModel model, bool allowdesign, bool populate = false) => 
            PopulateMenu(menu as ItemsControl, sectionName, model, allowdesign, populate);
        /// <summary>
        ///     Populate the <paramref name="menu"/> with a list of reports attached to this data model, loaded through <see cref="LoadReports(string, DataModel, Columns{ReportTemplate}?)"/>.
        /// </summary>
        /// <remarks>
        ///     This is used for the various places we have context menus for printing reports.
        /// </remarks>
        /// <param name="allowdesign">Allow designing reports; if <see langword="true"/>, then also adds a "Manage Reports" button.</param>
        /// <param name="populate">Determines whether the datamodel should be reloaded when printing a report.<br/>Set to <see langword="false"/> if all the data has already been loaded.</param>
        public static void PopulateMenu(ContextMenu menu, string sectionName, DataModel model, bool allowdesign, bool populate = false) => 
            PopulateMenu(menu as ItemsControl, sectionName, model, allowdesign, populate);

        public static void PrintMenu(FrameworkElement? element, string sectionName, DataModel model, bool allowdesign, bool populate = false)
        {
            var menu = new ContextMenu();

            PopulateMenu(menu, sectionName, model, allowdesign, populate);

            if (menu.Items.Count > 0)
            {
                menu.PlacementTarget = element;
                menu.IsOpen = true;
            }
        }

        public static void PrintMenu<TType>(FrameworkElement? element, string sectionName, DataModel<TType> model, bool allowdesign, bool populate = false)
            where TType : Entity, IRemotable, IPersistent, new()
        {
            PrintMenu(element, sectionName, model, allowdesign, populate);
        }      
    }
}