using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using FastReport;
using InABox.Clients;
using InABox.Core;
using InABox.Core.Reports;
using InABox.Scripting;
using InABox.Wpf;
using InABox.WPF;
using Microsoft.Win32;
using NPOI.HPSF;

namespace InABox.DynamicGrid
{
    public class DigitalFormExportData
    {
        public string Code { get; set; }

        public string Description { get; set; }

        public string AppliesTo { get; set; }

        public DigitalFormExportData() { }

        public DigitalFormExportData(DigitalForm form)
        {
            Code = form.Code;
            Description = form.Description;
            AppliesTo = form.AppliesTo;
        }

        public List<LayoutData> Layouts { get; set; }

        public List<VariableData> Variables { get; set; }

        public List<DocumentData> Documents { get; set; }

        public DigitalForm ToForm() => new DigitalForm
        {
            Code = Code,
            Description = Description,
            AppliesTo = AppliesTo
        };

        public class LayoutData
        {
            public string Code { get; set; }

            public string Description { get; set; }

            public DFLayoutType Type { get; set; }

            public string Layout { get; set; }

            public LayoutData() { }

            public LayoutData(DigitalFormLayout layout)
            {
                Code = layout.Code;
                Description = layout.Description;
                Type = layout.Type;
                Layout = layout.Layout;
            }

            public DigitalFormLayout ToLayout() => new DigitalFormLayout
            {
                Code = Code,
                Description = Description,
                Type = Type,
                Layout = Layout
            };
        }

        public class VariableData
        {
            public string Code { get; set; }

            public string Description { get; set; }

            public string VariableType { get; set; }

            public string Parameters { get; set; }

            public bool Required { get; set; }

            public bool Secure { get; set; }

            public bool Retain { get; set; }

            public bool Hidden { get; set; }

            public long Sequence { get; set; }

            public VariableData() {  }

            public VariableData(DigitalFormVariable variable)
            {
                Code = variable.Code;
                Description = variable.Description;
                VariableType = variable.VariableType;
                Parameters = variable.Parameters;
                Required = variable.Required;
                Secure = variable.Secure;
                Retain = variable.Retain;
                Hidden = variable.Hidden;
                Sequence = variable.Sequence;
            }

            public DigitalFormVariable ToVariable() => new DigitalFormVariable
            {
                Code = Code,
                Description = Description,
                VariableType = VariableType,
                Parameters = Parameters,
                Required = Required,
                Secure = Secure,
                Retain = Retain,
                Hidden = Hidden,
                Sequence = Sequence
            };
        }

        public class DocumentData
        {
            public string FileName { get; set; }

            public byte[] Data { get; set; }

            public Guid ID { get; set; }

            public DocumentData() { }

            public DocumentData(Document document)
            {
                FileName = document.FileName;
                Data = document.Data;
                ID = document.ID;
            }

            public Document ToDocument()
            {
                var doc = new Document()
                {
                    ID = ID
                };
                doc.CommitChanges();
                doc.FileName = FileName;
                doc.Data = Data;
                doc.TimeStamp = DateTime.Now;
                return doc;
            }
        }
    }

    public class DigitalFormGrid : DynamicDataGrid<DigitalForm>
    {
        private bool _showall;

        private Button CopyForm = null!; // Late-initialised

        protected override void Init()
        {
            base.Init();

            AddButton("Show All", null, ShowAllClick);
            // TODO: Add back in
            //ActionColumns.Add(new DynamicImageColumn(ReportImage, ReportClick));

            AddButton("Groups", null, EditGroupsClick);

            CopyForm = AddButton("Duplicate", InABox.Wpf.Resources.copy.AsBitmapImage(), CopyForm_Click);
            CopyForm.IsEnabled = false;
            if (!Security.CanEdit<DigitalForm>())
            {
                CopyForm.Visibility = Visibility.Collapsed;
            }

            OnCustomiseEditor += DigitalFormGrid_OnCustomiseEditor;
        }

        protected override void DoReconfigure(DynamicGridOptions options)
        {
            base.DoReconfigure(options);

            options.ImportData = true;
            options.ExportData = true;
            options.FilterRows = true;
            options.SelectColumns = true;
        }

        protected override void SelectItems(CoreRow[]? rows)
        {
            base.SelectItems(rows);

            CopyForm.IsEnabled = rows is not null && rows.Length == 1;
        }

        private bool CopyForm_Click(Button btn, CoreRow[] rows)
        {
            if(rows.Length != 1)
            {
                MessageWindow.ShowMessage("Please select one form to copy.", "Invalid selection.");
                return false;
            }

            var form = rows[0].ToObject<DigitalForm>();
            Client.EnsureColumns(form,
                Columns.None<DigitalForm>().Add(x => x.Description)
                    .Add(x => x.AppliesTo)
                    .Add(x => x.Secure)
                    .Add(x => x.Final)
                    .Add(x => x.Group.ID));

            var newForm = new DigitalForm();

            newForm.Description = form.Description;
            newForm.AppliesTo = form.AppliesTo;
            newForm.Secure = form.Secure;
            newForm.Final = form.Final;
            newForm.Group.ID = form.Group.ID;

            var children = Client.QueryMultiple(
                new KeyedQueryDef<DigitalFormLayout>(
                    new Filter<DigitalFormLayout>(x => x.Form.ID).IsEqualTo(form.ID),
                    Columns.None<DigitalFormLayout>().Add(x => x.Code)
                        .Add(x => x.Description)
                        .Add(x => x.Type)
                        .Add(x => x.Layout)
                        .Add(x => x.Active)),
                new KeyedQueryDef<DigitalFormVariable>(
                    new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(form.ID),
                    Columns.None<DigitalFormVariable>().Add(x => x.Code)
                        .Add(x => x.Description)
                        .Add(x => x.VariableType)
                        .Add(x => x.Group)
                        .Add(x => x.Parameters)
                        .Add(x => x.Required)
                        .Add(x => x.Secure)
                        .Add(x => x.Retain)
                        .Add(x => x.Hidden)
                        .Add(x => x.Sequence)),
                new KeyedQueryDef<ReportTemplate>(
                    new Filter<ReportTemplate>(x => x.Section).IsEqualTo(form.ID.ToString()),
                    Columns.None<ReportTemplate>().Add(x => x.Section)
                        .Add(x=>x.Name)
                        .Add(x=>x.Script)
                        .Add(x=>x.Visible)
                        .Add(x=>x.AllRecords)
                        .Add(x=>x.DataModel)
                        .Add(x=>x.PrinterName)
                        .Add(x=>x.SelectedRecords)
                        .Add(x=>x.RDL)
                        .Add(x=>x.Section)),
                new KeyedQueryDef<DigitalFormDocument>(
                    new Filter<DigitalFormDocument>(x => x.EntityLink.ID).IsEqualTo(form.ID),
                    Columns.None<DigitalFormDocument>().Add(x => x.Type)
                        .Add(x => x.DocumentLink.ID)
                        .Add(x => x.Superceded)
                        .Add(x => x.Thumbnail)
                        .Add(x => x.Notes)));

            if (EditItems(new[] { newForm }, type =>
            {
                return children.GetOrDefault(type.Name);
            }))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private bool EditGroupsClick(Button arg1, CoreRow[] arg2)
        {
            new MasterList(typeof(DigitalFormGroup)).ShowDialog();
            return false;
        }

        private BitmapImage? ReportImage(CoreRow arg)
        {
            return arg != null ? Wpf.Resources.printer.AsBitmapImage() : null;
        }

        /*private bool ReportClick(CoreRow arg)
        {
            if (arg == null)
                return false;

            var typename = arg.Get<DigitalForm, string>(c => c.AppliesTo);
            var formid = arg.Get<DigitalForm, Guid>(c => c.ID);

            // Get Applies To
            /*Type type = CoreUtils.GetEntity("Comal.Classes."+typename);
            CoreTable entity = new CoreTable();
            entity.LoadColumns(type);
    
            // Get Form Details
            CoreTable form = new CoreTable();
            form.Columns.Add(new CoreColumn() { ColumnName = "ID", DataType = typeof(Guid) });
            form.Columns.Add(new CoreColumn() { ColumnName = "Parent.ID", DataType = typeof(Guid) });
            form.Columns.Add(new CoreColumn() { ColumnName = CoreUtils.GetFullPropertyName<IDigitalFormInstance, String>(x => x.Form.Description, "."), DataType = typeof(String) });
            form.Columns.Add(new CoreColumn() { ColumnName = CoreUtils.GetFullPropertyName<IDigitalFormInstance, String>(x => x.FormCompletedBy.UserID, "."), DataType = typeof(String) });
            form.Columns.Add(new CoreColumn() { ColumnName = CoreUtils.GetFullPropertyName<IDigitalFormInstance, DateTime>(x => x.FormCompleted, "."), DataType = typeof(String) });
            form.Columns.Add(new CoreColumn() { ColumnName = CoreUtils.GetFullPropertyName<IDigitalFormInstance, String>(x => x.Location.Address, "."), DataType = typeof(String) });
            form.Columns.Add(new CoreColumn() { ColumnName = CoreUtils.GetFullPropertyName<IDigitalFormInstance, double>(x => x.Location.Latitude, "."), DataType = typeof(String) });
            form.Columns.Add(new CoreColumn() { ColumnName = CoreUtils.GetFullPropertyName<IDigitalFormInstance, double>(x => x.Location.Longitude, "."), DataType = typeof(String) });
    
            var variables = new Client<DigitalFormVariable>().Query(new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(formid));
            foreach (var row in variables.Rows)
            {
                form.Columns.Add(
                    new CoreColumn()
                    {
                        ColumnName = row.Get<DigitalFormVariable, String>(c => c.Code),
                        DataType = DigitalFormVariable.DataType(row.Get<DigitalFormVariable, DigitalFormVariableType>(c => c.VariableType))
                    }
                );
            }*


            // Create DataModel
            //DigitalFormReportDataModel model = new DigitalFormReportDataModel(form,entity,typename);
            AutoDataModel<DigitalForm> model = new AutoDataModel<DigitalForm>(new Filter<DigitalForm>(x => x.ID).IsEqualTo(formid));

            // Load Template
            var template = new Client<ReportTemplate>()
                .Load(new Filter<ReportTemplate>(x => x.Section).IsEqualTo("DigitalForms").And(x => x.Name).IsEqualTo(formid.ToString()))
                .FirstOrDefault();
            if (template == null)
            {
                template = new ReportTemplate
                {
                    Section = "DigitalForms",
                    Name = formid.ToString(),
                    Visible = false
                };
                new Client<ReportTemplate>().Save(template, "");
            }

            ReportUtils.DesignReport(template, model);

            // Preview Report
            return false;
        }*/

        private bool ShowAllClick(Button arg1, CoreRow[] arg2)
        {
            _showall = !_showall;
            UpdateButton(arg1, null, _showall ? "Hide Inactive" : "Show All");
            return true;
        }

        protected override void Reload(
            Filters<DigitalForm> criteria, Columns<DigitalForm> columns, ref SortOrder<DigitalForm>? sort, 
            CancellationToken token, Action<CoreTable?, Exception?> action)
        {
            if (!_showall)
                criteria.Add(new Filter<DigitalForm>(x => x.Active).IsEqualTo(true));
            base.Reload(criteria, columns, ref sort, token, action);
        }

        public override DynamicEditorPages LoadEditorPages(DigitalForm item)
        {
            var pages = base.LoadEditorPages(item);

            pages.Add(new DigitalFormReportGrid());

            return pages;
        }

        private DynamicVariableGrid? GetVariableGrid(IDynamicEditorForm sender)
            => sender.Pages?.FirstOrDefault(x => x is DynamicVariableGrid)
                as DynamicVariableGrid;

        private List<DigitalFormVariable> GetVariables(IDynamicEditorForm sender)
            => GetVariableGrid(sender)?.Items.ToList() ?? new List<DigitalFormVariable>();

        // Using the event because it also has the editor form 'sender'.
        private void DigitalFormGrid_OnCustomiseEditor(IDynamicEditorForm sender, DigitalForm[]? items, DynamicGridColumn column, BaseEditor editor)
        {
            if(editor is ExpressionEditor exp && ( new Column<DigitalForm>(x => x.DescriptionExpression).IsEqualTo(column.ColumnName) || new Column<DigitalForm>(x => x.ExportExpression).IsEqualTo(column.ColumnName)))
            {
                exp.OnGetVariables += () =>
                {
                    var variables = new List<string>();
                    foreach (var variable in GetVariables(sender))
                    {
                        foreach (var col in variable.GetVariableColumns())
                            variables.Add($"Data.{col.ColumnName}");
                    }

                    var appliesTo = items?.Select(x => x.AppliesTo).Distinct().SingleOrDefault();
                    if (!appliesTo.IsNullOrWhiteSpace() && DFUtils.GetFormInstanceType(appliesTo) is Type instanceType)
                    {
                        foreach(var property in DatabaseSchema.Properties(instanceType).Where(x => !x.Name.StartsWith("Parent.")))
                            variables.Add($"Form.{property.Name}");
                    }
                    
                    if (!appliesTo.IsNullOrWhiteSpace() && DFUtils.FormEntityType(appliesTo) is Type entityType)
                    {
                        foreach(var property in DatabaseSchema.Properties(entityType))
                            variables.Add($"{entityType.EntityName().Split('.').Last()}.{property.Name}");
                    }

                    variables.Sort();
                    return variables;
                };
            }
        }

        public override bool EditItems(DigitalForm[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
        {
            // Need to do this to make sure that the variables are available to the layouts (and vice versa?)
            return base.EditItems(items, PageDataHandler, true);
        }

        private const string ExportFileFilter = "Excel Files (*.xls, *xlsx)|*.xls;*.xlsx|Digital Forms (*.prs-form)|*.prs-form";

        protected override void DoImport()
        {
            var dialog = new OpenFileDialog
            {
                Filter = ExportFileFilter
            };
            if (dialog.ShowDialog() != true)
                return;

            String extension = Path.GetExtension(dialog.FileName) ?? "";
            if (extension.ToLower().Equals(".xls") || extension.ToLower().Equals(".xlsx"))
            {
                
                String appliesto = "";
                String code = "";
                var lookups = new DigitalFormCategoryLookups(null).Lookups();
                if (!DictionaryComboEdit.Execute<String>(lookups, ref appliesto, "Applies To", "Select Form Type") || String.IsNullOrWhiteSpace(appliesto))
                    return;
                Progress.ShowModal("Creating Form", (progress) =>
                {

                    var codes = new Client<DigitalForm>().Query(
                        null,
                        Columns.None<DigitalForm>().Add(x => x.Code),
                        null
                    ).Rows.Select(r => r.Get<DigitalForm, String>(c => c.Code)).ToArray();

                    int i = 1;
                    code = Path.GetFileNameWithoutExtension(dialog.FileName).ToUpper();
                    while (codes.Contains(code))
                        code = $"{Path.GetFileNameWithoutExtension(dialog.FileName).ToUpper()} ({i++})";
                    
                    DigitalForm form = new DigitalForm();
                    form.Code = code;
                    form.Description = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(Path.GetFileNameWithoutExtension(dialog.FileName));
                    form.AppliesTo = appliesto;
                    new Client<DigitalForm>().Save(form, $"Imported from {dialog.FileName}");

                    progress.Report("Importing Data");
                    DFLayout data;
                    var variables = new List<DigitalFormVariable>();
                    
                    var layout = new DigitalFormLayout();
                    layout.Form.ID = form.ID;
                    layout.Code = form.Code;
                    layout.Description = form.Description;
                    using (var fs = new FileStream(dialog.FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        var spreadsheet = new Spreadsheet(fs);
                        data = DigitalFormUtils.LoadLayout(spreadsheet);
                        layout.Layout = data.SaveLayout();
                    }
                    new Client<DigitalFormLayout>().Save(layout, $"Imported from {dialog.FileName}");

                    progress.Report("Setting Up Variables");

                    String group = "";
                    foreach (var element in data.Elements)
                    {
                        if (element is DFLayoutHeader header)
                        {
                            group = header.Header;
                        }
                        else if (element is DFLayoutField field)
                        {
                            var variable = new DigitalFormVariable();
                            variable.Form.ID = form.ID;
                            variable.SetFieldType(field.GetType());
                            variable.SaveProperties(field.GetProperties());
                            variable.Group = group;
                            variable.Code = field.Name;
                            variable.Description = field.Name;
                            variables.Add(variable);
                        }
                    }
                    if (variables.Any())
                        new Client<DigitalFormVariable>().Save(variables, $"Imported from {dialog.FileName}");

                    progress.Report("Creating Report");
                    var model = DigitalFormUtils.GetDataModel(appliesto, variables);
                    var template = DigitalFormUtils.GenerateReport(layout, model);
                    var report = new ReportTemplate();
                    report.Section = form.ID.ToString();
                    report.DataModel = model.Name;
                    report.Name = form.Description;
                    report.RDL = template?.SaveToString();
                    new Client<ReportTemplate>().Save(report, $"Imported from {dialog.FileName}");

                });

                MessageBox.Show($"[{code}] imported successully!");
                Refresh(false, true);
                return;
            }

            DigitalFormExportData? data;
            using(var stream = dialog.OpenFile())
            {
                data = Serialization.Deserialize<DigitalFormExportData>(stream);
                if (data is null)
                {
                    MessageBox.Show("File corrupt");
                    return;
                }
            }

            try
            {
                var form = data.ToForm();
                var layouts = data.Layouts.Select(x => x.ToLayout()).ToList();
                var variables = data.Variables.Select(x => x.ToVariable()).ToList();
                var documents = data.Documents.Select(x => x.ToDocument()).ToList();
                var formDocuments = new List<DigitalFormDocument>();

                foreach (var document in documents)
                {
                    new Client<Document>().Save(document, "");

                    var digitalFormDocument = new DigitalFormDocument();
                    digitalFormDocument.DocumentLink.ID = document.ID;
                    digitalFormDocument.DocumentLink.Synchronise(document);
                    formDocuments.Add(digitalFormDocument);
                }

                if (EditItems(new DigitalForm[] { form }, (type) =>
                {
                    var table = new CoreTable();
                    table.LoadColumns(type);
                    if(type == typeof(DigitalFormLayout))
                    {
                        table.LoadRows(layouts);
                    }
                    else if(type == typeof(DigitalFormVariable))
                    {
                        table.LoadRows(variables);
                    }
                    else if(type == typeof(DigitalFormDocument))
                    {
                        table.LoadRows(formDocuments);
                    }
                    return table;
                }))
                {
                    Refresh(false, true);
                }

                /*new Client<DigitalForm>().Save(form, "Imported by user");

                foreach (var layout in layouts)
                {
                    layout.Form.ID = form.ID;
                    new Client<DigitalFormLayout>().Save(layout, "");
                }
                foreach (var variable in variables)
                {
                    variable.Form.ID = form.ID;
                    new Client<DigitalFormVariable>().Save(variable, "");
                }
                foreach (var document in documents)
                {
                    new Client<Document>().Save(document, "");

                    var digitalFormDocument = new DigitalFormDocument();
                    digitalFormDocument.EntityLink.ID = form.ID;
                    digitalFormDocument.DocumentLink.ID = document.ID;

                    new Client<DigitalFormDocument>().Save(digitalFormDocument, "");
                }*/
            }
            catch(Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }

        protected override void DoExport()
        {
            var rows = SelectedRows;
            if(rows.Length == 0)
            {
                MessageBox.Show("Please select a form to export.");
                return;
            }
            else if(rows.Length > 1)
            {
                MessageBox.Show("Please select only one form to export.");
                return;
            }
            var formID = rows[0].Get<DigitalForm, Guid>(x => x.ID);

            var results = Client.QueryMultiple(
                new KeyedQueryDef<DigitalForm>(
                    new Filter<DigitalForm>(x => x.ID).IsEqualTo(formID),
                    Columns.None<DigitalForm>().Add(x => x.Code)
                        .Add(x => x.Description)
                        .Add(x => x.AppliesTo)),
                new KeyedQueryDef<DigitalFormLayout>(
                    new Filter<DigitalFormLayout>(x => x.Form.ID).IsEqualTo(formID),
                    Columns.None<DigitalFormLayout>().Add(x => x.Code)
                        .Add(x => x.Description)
                        .Add(x => x.Type)
                        .Add(x => x.Layout)),
                new KeyedQueryDef<DigitalFormVariable>(
                    new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(formID),
                    Columns.None<DigitalFormVariable>().Add(x => x.Code)
                        .Add(x => x.Description)
                        .Add(x => x.VariableType)
                        .Add(x => x.Parameters)
                        .Add(x => x.Required)
                        .Add(x => x.Secure)
                        .Add(x => x.Retain)
                        .Add(x => x.Hidden)
                        .Add(x => x.Sequence)),
                new KeyedQueryDef<Document>(
                    new Filter<Document>(x => x.ID).InQuery(
                        new Filter<DigitalFormDocument>(x => x.EntityLink.ID).IsEqualTo(formID),
                        x => x.DocumentLink.ID),
                    Columns.None<Document>().Add(x => x.FileName)
                        .Add(x => x.Data)));

            var data = new DigitalFormExportData(results.Get<DigitalForm>().Rows.First().ToObject<DigitalForm>())
            {
                Layouts = results.Get<DigitalFormLayout>().ToObjects<DigitalFormLayout>().Select(x => new DigitalFormExportData.LayoutData(x)).ToList(),
                Variables = results.Get<DigitalFormVariable>().ToObjects<DigitalFormVariable>().Select(x => new DigitalFormExportData.VariableData(x)).ToList(),
                Documents = results.Get<Document>().ToObjects<Document>().Select(x => new DigitalFormExportData.DocumentData(x)).ToList()
            };

            var filename = rows[0].Get<DigitalForm, string>(x => x.Description);
            if (string.IsNullOrWhiteSpace(filename))
                filename = rows[0].Get<DigitalForm, string>(x => x.Code);
            if (string.IsNullOrWhiteSpace(filename))
                filename = "form";

            var dialog = new SaveFileDialog
            {
                Filter = ExportFileFilter,
                FileName = $"{filename}.prs-form"
            };
            if(dialog.ShowDialog() == true)
            {
                using var stream = dialog.OpenFile();
                Serialization.Serialize(data, stream);
            }
        }

        protected override void DoValidate(DigitalForm[] items, List<string> errors)
        {
            base.DoValidate(items, errors);
            if (items.Any(x => string.IsNullOrWhiteSpace(x.AppliesTo)))
                errors.Add("[Applies To] must not be blank!");
        }
    }
}