using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
using System.Xml;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.WPF;
using NPOI.SS.Formula.Functions;

namespace InABox.DynamicGrid
{
    /// <summary>
    ///     Interaction logic for DynamicEditorGrid.xaml
    /// </summary>
    public delegate void OnUpdateOtherEditorHandler(string columnname, object value);

    public delegate Dictionary<string, object?> EditorValueChangedHandler(object sender, string name, object value);

//public delegate Dictionary<object, object> EditorGetLookupsHandler(object sender, String column);

    public partial class DynamicEditorGrid : UserControl
    {
        public delegate void EditorCreatedHandler(object sender, double height, double width);

        public delegate Document? FindDocumentEvent(string FileName);

        public delegate Document? GetDocumentEvent(Guid id);

        public delegate object? GetPropertyValueHandler(object sender, string name);

        public delegate void SaveDocumentEvent(Document document);

        public delegate void SetPropertyValueHandler(object sender, string name, object value);

        public delegate object?[] GetItemsEvent();

        // Column Definitions as defined by calling model
        private DynamicGridColumns _columns;

        private string _layoutname = "";

        private bool _loaded;

        private bool bChanging;

        // List of Changed Editor Values (used when redesigning screen) 
        private readonly Dictionary<string, object> changes = new();
        private Grid CustomGrid;
        private DynamicTabControl Details;

        private Grid EditorGrid;

        // Active Editor List
        private readonly List<IDynamicEditorControl> editors = new();

        private readonly Dictionary<IDynamicEditorPage, DynamicTabItem> pagemap = new();

        //Dictionary<IDynamicEditorPage, bool> loadedpages = new Dictionary<IDynamicEditorPage, bool>();

        public DynamicEditorGrid()
        {
            InitializeComponent();

            Loaded += DynamicEditorGrid_Loaded;
        }

        //private Dictionary<String, Tuple<FrameworkElement, Object, DynamicGridColumn>> editors = new Dictionary<string, Tuple<FrameworkElement, Object, DynamicGridColumn>>();
        public DynamicEditorPages? Pages { get; private set; }

        public bool PreloadPages { get; set; }

        public Type UnderlyingType { get; set; }

        public OnLoadPage? OnLoadPage { get; set; }
        public OnSelectPage? OnSelectPage { get; set; }
        public OnUnloadPage? OnUnloadPage { get; set; }

        //private void UnloadEditorValues()
        //{
        //    foreach (string columnname in editors.Keys)
        //    {
        //        bool changed = (bool)CoreUtils.GetPropertyValue(editors[columnname], "Changed");
        //        if (changed)
        //        {
        //            changes[columnname] = CoreUtils.GetPropertyValue(editors[columnname], "Value");

        //            Dictionary<string, object> othervalues = CoreUtils.GetPropertyValue(editors[columnname], "OtherValues") as Dictionary<string, object>;
        //            foreach (var field in othervalues.Keys)
        //            {
        //                List<String> comps = columnname.Split('.').ToList();
        //                comps[comps.Count - 1] = field;
        //                String actualname = String.Join(".", comps);
        //                changes[actualname] = othervalues[field];
        //            }
        //        }
        //    }
        //}

        public bool IsCustomLayout { get; private set; }

        public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
        {
            editor = editors.FirstOrDefault(x => x.ColumnName.Equals(columnname));
            return editor is not null;
        }
        public IDynamicEditorControl? FindEditor(string columnname)
        {
            TryFindEditor(columnname, out var editor);
            return editor;
        }

        public object? GetPropertyValue(string columnname)
        {
            return OnGetPropertyValue?.Invoke(this, columnname);
        }

        public event EditorCreatedHandler? OnEditorCreated;

        public event OnCustomiseColumns? OnCustomiseColumns;

        public event OnGetEditor? OnGetEditor;

        public event OnGridCustomiseEditor? OnGridCustomiseEditor;

        public event OnGetEditorSequence? OnGetSequence;
        public event GetPropertyValueHandler? OnGetPropertyValue;
        public event SetPropertyValueHandler? OnSetPropertyValue;

        public event EditorValueChangedHandler? OnEditorValueChanged;

        public event OnAfterEditorValueChanged? OnAfterEditorValueChanged;

        public event OnReconfigureEditors? OnReconfigureEditors;
        
        public event OnDefineFilter? OnDefineFilter;

        public event OnDefineLookup? OnDefineLookups;

        public event OnLookupsDefined? OnLookupsDefined;

        public event GetDocumentEvent? OnGetDocument;
        public event FindDocumentEvent? OnFindDocument;
        public event SaveDocumentEvent? OnSaveDocument;

        public event GetItemsEvent? GetItems;

        private void DynamicEditorGrid_Loaded(object sender, RoutedEventArgs e)
        {
            Reload();
        }

        public void Reload(bool refresh = false)
        {
            if (_loaded ^ refresh) return;

            ConfigureEditors();

            LoadEditorValues();

            ClearPages();
            AddPages();

            ReconfigureEditors();

            //MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
            //MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));

            //if (Keyboard.PrimaryDevice != null)
            //{
            //    if (Keyboard.PrimaryDevice.ActiveSource != null)
            //    {
            //        var e1 = new KeyEventArgs(Keyboard.PrimaryDevice, Keyboard.PrimaryDevice.ActiveSource, 0, Key.Tab) { RoutedEvent = Keyboard.KeyDownEvent };
            //        InputManager.Current.ProcessInput(e1);
            //    }
            //}
            editors.FirstOrDefault()?.SetFocus();

            _loaded = true;
        }

        #region Configure Editors

        private void LoadLookupColumns(DynamicGridColumn column, Dictionary<string, string> othercolumns)
        {
            othercolumns.Clear();
            var comps = column.ColumnName.Split('.').ToList();
            comps.RemoveAt(comps.Count - 1);
            var prefix = string.Format("{0}.", string.Join(".", comps));
            var cols = _columns.Where(x => !x.ColumnName.Equals(column.ColumnName) && x.ColumnName.StartsWith(prefix));
            foreach (var col in cols)
                othercolumns[col.ColumnName.Replace(prefix, "")] = col.ColumnName;
        }

        private string GetCodeColumn(DynamicGridColumn column)
        {
            var comps = column.ColumnName.Split('.').ToList();
            comps.RemoveAt(comps.Count - 1);
            var prefix = string.Format("{0}.", string.Join(".", comps));
            var cols = _columns.Where(x => !x.ColumnName.Equals(column.ColumnName) && x.ColumnName.StartsWith(prefix));
            foreach (var col in cols)
            {
                var editor = OnGetEditor?.Invoke(col);
                if (editor is CodeEditor || editor is UniqueCodeEditor)
                    return col.ColumnName.Split('.').Last();
            }

            return "";
        }

        private void ConfigureExpressionEditor(ExpressionEditorControl control)
        {
            control.GetItems += () => GetItems?.Invoke() ?? Array.Empty<object?>();
        }

        private void ConfigurePopupEditor(PopupEditorControl popup, DynamicGridColumn column, PopupEditor editor)
        {
            popup.ColumnName = column.ColumnName;

            LoadLookupColumns(column, popup.OtherColumns);
            if (popup.EditorDefinition is DataLookupEditor dataLookup)
                LoadLookupColumns(column, dataLookup.OtherColumns);

            popup.OnDefineFilter += (sender, type) => { return OnDefineFilter?.Invoke(sender, type); };
            popup.OnUpdateOtherEditor += Lookup_OnUpdateOtherEditor;
        }

        private void ConfigureCodePopupEditor(CodePopupEditorControl popup, DynamicGridColumn column, CodePopupEditor editor)
        {
            popup.ColumnName = column.ColumnName;

            LoadLookupColumns(column, popup.OtherColumns);
            if (popup.EditorDefinition is DataLookupEditor dataLookup)
                LoadLookupColumns(column, dataLookup.OtherColumns);

            popup.CodeColumn = !string.IsNullOrEmpty(editor.CodeColumn) ? editor.CodeColumn : GetCodeColumn(column);
            popup.OnDefineFilter += (sender, type) => { return OnDefineFilter?.Invoke(sender, type); };
            popup.OnUpdateOtherEditor += Lookup_OnUpdateOtherEditor;
        }

        private void ConfigureLookupEditor(LookupEditorControl lookup, DynamicGridColumn column, LookupEditor editor)
        {
            if (editor.LookupWidth != int.MaxValue)
                lookup.Width = editor.LookupWidth;
            lookup.ColumnName = column.ColumnName;
            LoadLookupColumns(column, lookup.OtherColumns);
            if (lookup.EditorDefinition is DataLookupEditor dataLookup)
                LoadLookupColumns(column, dataLookup.OtherColumns);
            lookup.OnUpdateOtherEditor += Lookup_OnUpdateOtherEditor;
            lookup.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
            lookup.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };
        }

        private void ConfigureEnumEditor(LookupEditorControl lookup, DynamicGridColumn column, EnumLookupEditor editor)
        {
            if (editor.LookupWidth != int.MaxValue)
                lookup.Width = editor.LookupWidth;
            lookup.ColumnName = column.ColumnName;
            lookup.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
            lookup.OnLookupsDefined += sender =>
            {
                //OnLookupsDefined?.Invoke(sender);
            };
        }

        private void ConfigureComboEditor(LookupEditorControl lookup, DynamicGridColumn column, ComboLookupEditor editor)
        {
            if (editor.LookupWidth != int.MaxValue)
                lookup.Width = editor.LookupWidth;
            lookup.ColumnName = column.ColumnName;
            lookup.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
            lookup.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };
        }

        private void ConfigureMultiLookupEditor(MultiLookupEditorControl lookup, DynamicGridColumn column, ComboMultiLookupEditor editor)
        {
            if (editor.LookupWidth != int.MaxValue)
                lookup.Width = editor.LookupWidth;
            lookup.ColumnName = column.ColumnName;
            lookup.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
            lookup.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };
        }

        private void ConfigureCheckListEditor(CheckListBoxEditorControl checks, DynamicGridColumn column, CheckListEditor editor)
        {
            checks.Width = editor.LookupWidth;
            checks.ColumnName = column.ColumnName;
            checks.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
            checks.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };
        }

        private void ConfigureDocumentEditor(DocumentEditorControl document, DynamicGridColumn column, BaseDocumentEditor editor)
        {
            document.ColumnName = column.ColumnName;

            LoadLookupColumns(column, document.OtherColumns);
            if (document.EditorDefinition is DataLookupEditor dataLookup)
                LoadLookupColumns(column, dataLookup.OtherColumns);

            document.OnGetDocument += id => { return OnGetDocument?.Invoke(id); };
            document.OnSaveDocument += doc => { OnSaveDocument?.Invoke(doc); };
            document.OnFindDocument += file => { return OnFindDocument?.Invoke(file); };
            document.OnUpdateOtherEditor += Lookup_OnUpdateOtherEditor;

            document.Filter = editor.FileMask;
        }

        private void Lookup_OnUpdateOtherEditor(string columnname, object value)
        {
            var editor = editors.FirstOrDefault(x => x.ColumnName.Equals(columnname));
            if (editor != null)
                CoreUtils.SetPropertyValue(editor, "Value", value);
        }

        private void ConfigurePasswordEditor(PasswordEditorControl passwordEditorControl, DynamicGridColumn column, PasswordEditor passwordEditor)
        {
            passwordEditorControl.ViewButtonVisible = passwordEditor.ViewButtonVisible;
        }

        //private IEnumerable<BaseDynamicEditorControl> FindEditors(FrameworkElement element, DynamicGridColumn column)
        //{

        //    if (element == null)
        //        return new List<BaseDynamicEditorControl>();
        //    if ((element is Border) && ((element as Border).Child is ScrollViewer))
        //        return FindEditors(((element as Border).Child as ScrollViewer), column);
        //    return element.FindVisualChildren<BaseDynamicEditorControl>().Where(x => x.ColumnName != null && x.ColumnName.Equals(column.ColumnName));
        //}

        private IEnumerable<BaseDynamicEditorControl> FindEditors(DynamicGridColumn column)
        {
            var results = new List<BaseDynamicEditorControl>();
            foreach (DynamicTabItem tab in Details.Items)
            {
                var border = tab.Content as Border;
                if (border != null)
                {
                    var scroll = border.Child as ScrollViewer;
                    if (scroll != null)
                    {
                        var grid = scroll.Content as Grid;
                        if (grid != null)
                            results.AddRange(grid.Children.OfType<BaseDynamicEditorControl>()
                                .Where(x => string.Equals(x.ColumnName, column.ColumnName)));
                    }
                }
            }

            //results.AddRange(FindEditors(tab.Content as FrameworkElement, column));
            return results;
        }

        private void ConfigureEditors()
        {
            if (editors.Any() && _columns != null)
                foreach (var column in _columns)
                {
                    var editorname = column.ColumnName.Replace(".", "_");

                    var
                        Editors = FindEditors(
                            column); //Details.FindVisualChildren<BaseDynamicEditorControl>().Where(x => x.ColumnName != null && x.ColumnName.Equals(column.ColumnName));
                    foreach (var Editor in Editors)
                    {
                        var editor = Editor.EditorDefinition;

                        //List<Object> parameters = editor.Parameters != null ? editor.Parameters.ToList() : new List<object>();
                        if (Editor is LookupEditorControl lookupControl)
                        {
                            if (editor is LookupEditor lookupEditor)
                                ConfigureLookupEditor(lookupControl, column, lookupEditor);

                            else if (editor is EnumLookupEditor enumEditor)
                                ConfigureEnumEditor(lookupControl, column, enumEditor);

                            else if (editor is ComboLookupEditor comboEditor)
                                ConfigureComboEditor(lookupControl, column, comboEditor);
                        }

                        else if(Editor is MultiLookupEditorControl multiLookupEditor && editor is ComboMultiLookupEditor comboMultiLookup)
                        {
                            ConfigureMultiLookupEditor(multiLookupEditor, column, comboMultiLookup);
                        }

                        else if (Editor is CheckListBoxEditorControl checkBoxControl && editor is CheckListEditor checkListEditor)
                        {
                            ConfigureCheckListEditor(checkBoxControl, column, checkListEditor);
                        }

                        else if (Editor is PopupEditorControl popupControl && editor is PopupEditor popupEditor)
                        {
                            ConfigurePopupEditor(popupControl, column, popupEditor);
                        }

                        else if (Editor is CodePopupEditorControl codePopupControl && editor is CodePopupEditor codePopupEditor)
                        {
                            ConfigureCodePopupEditor(codePopupControl, column, codePopupEditor);
                        }

                        else if (Editor is DocumentEditorControl documentEditorControl && editor is BaseDocumentEditor baseDocumentEditor)
                        {
                            ConfigureDocumentEditor(documentEditorControl, column, baseDocumentEditor);
                        }

                        else if (Editor is PasswordEditorControl passwordEditorControl && editor is PasswordEditor passwordEditor)
                        {
                            ConfigurePasswordEditor(passwordEditorControl, column, passwordEditor);
                        }

                        else if(Editor is ExpressionEditorControl expressionEditorControl && editor is ExpressionEditor expressionEditor)
                        {
                            ConfigureExpressionEditor(expressionEditorControl);
                        }

                        Editor.Configure();
                        if (!editors.Any(x => x.ColumnName.Equals(Editor.ColumnName)))
                            editors.Add(Editor);
                        Editor.Loaded = true;
                    }
                }
        }

        public virtual void ReconfigureEditors()
        {
            OnReconfigureEditors?.Invoke(this);
        }

        #endregion

        #region Editor

        private void EditorValueChanged(IDynamicEditorControl sender, Dictionary<string, object> values)
        {
            //Logger.Send(LogType.Information, "", string.Format("DynamicEditorGrid.EditorValueChanged({0})", values.Keys.Count));
            var changededitors = new Dictionary<string, object?>();

            void ExtractChanged(Dictionary<String, object?>? columns)
            {
                if (columns != null)
                    foreach (var (change, value) in columns)
                        if (editors.Any(x => x.ColumnName.Equals(change)) && !changededitors.ContainsKey(change) && !change.Equals(sender.ColumnName))
                            changededitors[change] = value;
            }

            foreach (var key in values.Keys)
            {
                var changedcolumns = OnEditorValueChanged?.Invoke(this, key, values[key]);
                ExtractChanged(changedcolumns);
            }

            var afterchanged = OnAfterEditorValueChanged?.Invoke(this, sender.ColumnName);
            ExtractChanged(afterchanged);

            if (changededitors.Any())
                LoadEditorValues(changededitors);

            ReconfigureEditors();
        }

        private void LoadEditorValues(Dictionary<string, object?>? changededitors = null)
        {
            var columnnames = changededitors != null ? changededitors.Keys.ToArray() : editors.Select(x => x.ColumnName).ToArray();
            foreach (var columnname in columnnames)
            {
                var editor = editors.FirstOrDefault(x => x.ColumnName.Equals(columnname));

                if (editor == null)
                    continue;

                var bLoaded = editor.Loaded;
                editor.Loaded = false;
                if (changededitors != null && changededitors.ContainsKey(columnname))
                {
                    CoreUtils.SetPropertyValue(editor, "Value", changededitors[columnname]);
                }
                else
                {
                    var curvalue = OnGetPropertyValue?.Invoke(this, columnname);

                    try
                    {
                        CoreUtils.SetPropertyValue(editor, "Value", curvalue);
                    }
                    catch (Exception e)
                    {
                        MessageBox.Show($"Unable to set editor value for {columnname} -> {curvalue}: {CoreUtils.FormatException(e)}");
                    }

                    CoreUtils.SetPropertyValue(editor, "Changed", false);
                }

                editor.Loaded = bLoaded;

                editor.OnEditorValueChanged += EditorValueChanged;
            }
        }

        #endregion

        #region Loading + Editing Layout

        private bool LoadLayout(string xaml)
        {
            if (!string.IsNullOrWhiteSpace(xaml))
                try
                {
                    IsCustomLayout = true;

                    Content = null;

                    Details = (XamlReader.Parse(xaml) as DynamicTabControl) ?? throw new Exception("XAML is not a DynamicTabControl");

                    Content = Details;

                    Details.ApplyTemplate();

                    var iHeight = Details.Height > 0 ? Details.Height : 600;
                    var iWidth = Details.Width > 0 ? Details.Width : 800;
                    Details.Height = double.NaN;
                    Details.Width = double.NaN;

                    OnEditorCreated?.Invoke(this, iHeight, iWidth);

                    return true;
                }
                catch (Exception e)
                {
                    MessageBox.Show(string.Format("Unable to Load XAML!\n\n{0}", e.Message));
                }

            return false;
        }

        private decimal GetSequence(DynamicGridColumn column)
        {
            if (OnGetSequence != null)
                return OnGetSequence.Invoke(column);
            return 999;
        }

        private void CreateLayout()
        {
            //Stopwatch sw = new Stopwatch();
            //sw.Start();

            IsCustomLayout = false;

            Content = null;

            Details = new DynamicTabControl();
            Details.VerticalAlignment = VerticalAlignment.Stretch;
            Details.HorizontalAlignment = HorizontalAlignment.Stretch;
            Details.Name = "Details";
            
            //Logger.Send(LogType.Information, "DEG.CreateLayout", String.Format("Created Tab Control: {0}", sw.ElapsedMilliseconds));
            //sw.Restart();

            var EditorGrids = new Dictionary<string, Grid>();

            CustomGrid = EnsureGrid(EditorGrids, "Custom Fields");
            EditorGrid = EnsureGrid(EditorGrids, "General");
            
            //Logger.Send(LogType.Information, "DEG.CreateLayout", String.Format("Ensured Grids: {0}", sw.ElapsedMilliseconds));
            //sw.Restart();

            Details.SelectionChanged += Details_SelectionChanged;

            Content = Details;

            editors.Clear();
            double fGeneralHeight = 30; // Allow for Tab Header

            //Logger.Send(LogType.Information, "DEG.CreateLayout", String.Format("Sorted Columns: {0}", sw.ElapsedMilliseconds));
            //sw.Restart();
            foreach (var column in _columns.OrderBy(x => GetSequence(x)))
            {
                var iProp = DatabaseSchema.Property(UnderlyingType, column.ColumnName);
                var editor = OnGetEditor?.Invoke(column);

                if (editor != null && iProp?.ShouldShowEditor() != true)
                {
                    editor.Visible = Visible.Hidden;
                    editor.Editable = Editable.Hidden;
                }

                if(editor is not null)
                {
                    OnGridCustomiseEditor?.Invoke(this, column, editor);
                }

                if (editor != null && editor.Editable != Editable.Hidden)
                {
                    var page = string.IsNullOrWhiteSpace(editor.Page) ? iProp is StandardProperty ? "General" : "Custom Fields" : editor.Page;
                    var grid = EnsureGrid(EditorGrids, page);

                    //List<Object> parameters = editor.Parameters != null ? editor.Parameters.ToList() : new List<object>();
                    //bool bParams = true;

                    BaseDynamicEditorControl? element = null;

                    element = editor switch
                    {
                        TextBoxEditor => new TextBoxEditorControl(),
                        Core.RichTextEditor => new RichTextEditorControl(),
                        URLEditor => new URLEditorControl(),
                        CodeEditor or UniqueCodeEditor => new CodeEditorControl(),
                        CheckBoxEditor => new CheckBoxEditorControl(),
                        DateTimeEditor => new DateTimeEditorControl(),
                        DateEditor dateEditor => new DateEditorControl { TodayVisible = dateEditor.TodayVisible },
                        TimeOfDayEditor => new TimeOfDayEditorControl { NowButtonVisible = false },
                        DurationEditor => new DurationEditorControl(),
                        NotesEditor => new NotesEditorControl(),
                        PINEditor => new PINEditorControl(),
                        CheckListEditor => new CheckListBoxEditorControl(),
                        MemoEditor => new MemoEditorControl(),
                        JsonEditor => new JsonEditorControl(),
                        LookupEditor => ClientFactory.IsSupported(((LookupEditor)editor).Type) ? new LookupEditorControl() : null,
                        PopupEditor => ClientFactory.IsSupported(((PopupEditor)editor).Type) ? new PopupEditorControl() : null,
                        CodePopupEditor => ClientFactory.IsSupported(((CodePopupEditor)editor).Type) ? new CodePopupEditorControl() : null,
                        EnumLookupEditor or ComboLookupEditor => new LookupEditorControl(),
                        ComboMultiLookupEditor => new MultiLookupEditorControl(),
                        EmbeddedImageEditor imageEditor => new EmbeddedImageEditorControl
                        {
                            MaximumHeight = imageEditor.MaximumHeight,
                            MaximumWidth = imageEditor.MaximumWidth,
                            MaximumFileSize = imageEditor.MaximumFileSize
                        },
                        FileNameEditor fileNameEditor => new FileNameEditorControl
                        {
                            Filter = fileNameEditor.FileMask,
                            AllowView = fileNameEditor.AllowView,
                            RequireExisting = fileNameEditor.RequireExisting
                        },
                        FolderEditor folderEditor => new FolderEditorControl
                        {
                            InitialFolder = folderEditor.InitialFolder
                        },
                        MiscellaneousDocumentEditor => new DocumentEditorControl(),
                        ImageDocumentEditor => new DocumentEditorControl(),
                        VectorDocumentEditor => new DocumentEditorControl(),
                        PDFDocumentEditor => new DocumentEditorControl(),
                        PasswordEditor => new PasswordEditorControl(),
                        CurrencyEditor => new CurrencyEditorControl(),
                        DoubleEditor => new DoubleEditorControl(),
                        IntegerEditor => new IntegerEditorControl(),
                        Core.ScriptEditor scriptEditor => new ScriptEditorControl
                        {
                            SyntaxLanguage = scriptEditor.SyntaxLanguage
                        },
                        TimestampEditor => new TimestampEditorControl(),
                        ColorEditor => new ColorEditorControl(),
                        FilterEditor filter => new FilterEditorControl { FilterType = filter.Type! },
                        ExpressionEditor expression => new ExpressionEditorControl(expression),
                        _ => null,
                    };
                    if (element != null)
                    {
                        element.EditorDefinition = editor; //22

                        element.IsEnabled = editor.Editable == Editable.Enabled;

                        if (!string.IsNullOrWhiteSpace(editor.ToolTip))
                        {
                            element.ToolTip = new ToolTip() { Content = editor.ToolTip };
                        }

                        var label = new Label();
                        label.Content = CoreUtils.Neatify(editor.Caption); // 2
                        label.Margin = new Thickness(0F, 0F, 0F, 0F);
                        label.HorizontalAlignment = HorizontalAlignment.Stretch;
                        label.VerticalAlignment = VerticalAlignment.Stretch;
                        label.HorizontalContentAlignment = HorizontalAlignment.Left;
                        label.VerticalContentAlignment = VerticalAlignment.Center;
                        label.SetValue(Grid.RowProperty, grid.RowDefinitions.Count);
                        label.SetValue(Grid.ColumnProperty, 0);
                        label.Visibility = string.IsNullOrWhiteSpace(editor.Caption) ? Visibility.Collapsed : Visibility.Visible;
                        grid.Children.Add(label);

                        element.ColumnName = column.ColumnName;
                        element.Color = editor is UniqueCodeEditor ? Color.FromArgb(0xFF, 0xF6, 0xC9, 0xE8) : Colors.LightYellow;

                        editors.Add(element);

                        element.Margin = new Thickness(5F, 2.5F, 5F, 2.5F);

                        double iHeight = element.DesiredHeight();
                        if (iHeight == int.MaxValue)
                        {
                            grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
                            fGeneralHeight += grid == EditorGrid ? element.MinHeight + 5.0F : 0.0F;
                        }
                        else
                        {
                            grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(iHeight + 5.0F) });
                            fGeneralHeight += grid == EditorGrid ? iHeight + 5.0F : 0.0F;
                        }

                        double iWidth = element.DesiredWidth();
                        if (iWidth == int.MaxValue)
                        {
                            element.HorizontalAlignment = HorizontalAlignment.Stretch;
                        }
                        else
                        {
                            element.HorizontalAlignment = HorizontalAlignment.Left;
                            element.Width = iWidth;
                        }

                        element.SetValue(Grid.RowProperty, grid.RowDefinitions.Count - 1);
                        element.SetValue(Grid.ColumnProperty, 1);
                        grid.Children.Add(element);
                    }
                }
            }

            //Logger.Send(LogType.Information, "DEG.CreateLayout", String.Format("Created Editors: {0}", sw.ElapsedMilliseconds));
            //sw.Restart();
            
            OnEditorCreated?.Invoke(this, fGeneralHeight, 800);
            if(Details.Items[^1] is DynamicTabItem custom)
            {
                custom.Visibility = CustomGrid.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
            }
            
            //Logger.Send(LogType.Information, "DEG.CreateLayout", String.Format("Finalised: {0}", sw.ElapsedMilliseconds));
            //sw.Stop();

        }

        private Grid EnsureGrid(Dictionary<string, Grid> grids, string caption)
        {
            if (grids.ContainsKey(caption))
                return grids[caption];

            // Create Editor, ScrollViewer and TabItem for Dynamic Editor
            var result = new Grid
            {
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,
                //Background = new SolidColorBrush(Colors.Blue),
                Margin = new Thickness(0, 2.5, 0, 2.5)
            };
            result.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
            result.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });

            var scroll = new ScrollViewer
            {
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,
                VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
                Padding = new Thickness(2)
            };

            scroll.Content = result;

            var border = new Border
            {
                BorderBrush = new SolidColorBrush(Colors.Gray),
                Background = new SolidColorBrush(Colors.White),
                BorderThickness = new Thickness(0.75)
            };
            border.Child = scroll;

            var tab = new DynamicTabItem();
            tab.Header = caption;
            tab.Content = border;
            if (Details.Items.Count == 0)
                Details.Items.Add(tab);
            else
                Details.Items.Insert(Details.Items.Count - 1, tab);

            grids[caption] = result;

            return result;
        }

        private string FormatXML(string xml)
        {
            var result = "";

            var mStream = new MemoryStream();
            var writer = new XmlTextWriter(mStream, Encoding.Unicode);
            var document = new XmlDocument();

            try
            {
                // Load the XmlDocument with the XML.
                document.LoadXml(xml);

                writer.Formatting = Formatting.Indented;

                // Write the XML into a formatting XmlTextWriter
                document.WriteContentTo(writer);
                writer.Flush();
                mStream.Flush();

                // Have to rewind the MemoryStream in order to read
                // its contents.
                mStream.Position = 0;

                // Read MemoryStream contents into a StreamReader.
                var sReader = new StreamReader(mStream);

                // Extract the text from the StreamReader.
                var formattedXml = sReader.ReadToEnd();

                result = formattedXml;
            }
            catch (XmlException)
            {
                // Handle the exception
            }

            mStream.Close();
            writer.Close();

            return result;
        }

        public void EditLayout()
        {
            ClearPages();

            //UnloadEditorValues();

            var xaml = new GlobalConfiguration<ScreenLayout>(_layoutname).Load().XAML;
            if (string.IsNullOrWhiteSpace(xaml))
            {
                Details.Height = Details.ActualHeight;
                Details.Width = Details.ActualWidth;
                //xaml = XamlWriter.Save(GetParentWindow(Details));
                xaml = XamlWriter.Save(Details);
            }

            xaml = FormatXML(xaml);

            var scripteditor = new ScriptEditor(xaml, SyntaxLanguage.XAML);
            if (scripteditor.ShowDialog() == true)
            {
                var layout = new ScreenLayout { XAML = scripteditor.Script };
                new GlobalConfiguration<ScreenLayout>(_layoutname).Save(layout);

                Content = null;
                if (!LoadLayout(layout.XAML))
                    if (!LoadLayout(xaml))
                        CreateLayout();
                Details.ApplyTemplate();
                Application.Current.Dispatcher.Invoke(() =>
                {
                    ConfigureEditors();
                    LoadEditorValues();
                    AddPages();
                    ReconfigureEditors();
                }, DispatcherPriority.Render);
            }
        }

        internal void ResetLayout()
        {
            new GlobalConfiguration<ScreenLayout>(_layoutname).Delete();

            ClearPages();
            //UnloadEditorValues();
            CreateLayout();
            LoadEditorValues();
            AddPages();
            ReconfigureEditors();
        }

        #endregion

        #region Pages

        //List<TabItem> configuredpages = new List<TabItem>();

        private void Details_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (bChanging || Details?.SelectedItem == null || e.OriginalSource != Details)
                return;


            bChanging = true;
            try
            {
                var tab = Details.SelectedItem as DynamicTabItem;
                if(tab is not null)
                {
                    var page = tab.Content as IDynamicEditorPage;
                    if (page is not null)
                    {
                        if (!page.Ready)
                            using (new WaitCursor())
                            {
                                OnLoadPage?.Invoke(page);
                            }
                    }
                    else
                    {
                        if (!_loaded || e.RemovedItems.Count == 0 || e.AddedItems.Count == 0 || e.AddedItems?[0] == e.RemovedItems?[0])
                            return;


                        //if (!configuredpages.Contains(tab))
                        //{
                        //    ConfigureEditors(eds);
                        //    configuredpages.Add(tab);
                        //}

                        var selectedGrid = ((tab.Content as Border)?.Child as ScrollViewer)?.Content;

                        var eds = editors
                            .Where(x => x is BaseDynamicEditorControl control &&
                                        control.Parent == selectedGrid)
                            .Select(x => (BaseDynamicEditorControl)x);
                        foreach (var ed in eds)
                        {
                            var editorvalue = ed.GetValue();
                            var entityvalue = OnGetPropertyValue?.Invoke(this, ed.ColumnName);
                            if (!Equals(editorvalue, entityvalue))
                            {
                                ed.Loaded = false;
                                ed.SetValue(entityvalue);
                                ed.Loaded = true;
                            }
                        }
                    }

                    OnSelectPage?.Invoke(tab, null);
                }
            }
            finally
            {
                bChanging = false;
            }
        }

        public void UnloadPages(bool saved)
        {
            if(Pages is not null)
                foreach (var page in Pages)
                    if (page.Ready)
                        OnUnloadPage?.Invoke(page, saved);
        }

        private void AddPages()
        {
            if (Pages != null)
                using (new WaitCursor())
                {
                    foreach (var page in Pages.OrderBy(x => x.Order()).ThenBy(x => x.Caption()))
                    {
                        var tab = new DynamicTabItem();
                        tab.Header = page.Caption();
                        tab.Content = page;
                        Details.Items.Insert(Details.Items.Count - 1, tab);
                        pagemap[page] = tab;
                        if (PreloadPages)
                            OnLoadPage?.Invoke(page);
                        page.EditorGrid = this;
                    }
                }
            //if (Details.Items.Count <= 2)
            //    ((TabItem)Details.Items[0]).Visibility = Visibility.Collapsed;
        }

        private void ClearPages()
        {
            foreach (var page in pagemap.Keys)
            {
                var tab = pagemap[page];
                tab.Content = null;
                Details.Items.Remove(tab);
                page.Ready = false;
            }

            pagemap.Clear();

            //if (_pages != null)
            //{
            //    foreach (var page in _pages)
            //    {
            //        var tab = Details.Items.OfType<TabItem>().FirstOrDefault(x => x.Content.Equals(page));
            //        if (tab != null)
            //        {
            //            tab.Content = null;
            //            Details.Items.Remove(tab);
            //        }
            //    }
            //}
        }

        public void Load(string layoutname, DynamicEditorPages? pages = null)
        {
            _layoutname = layoutname;
            Pages = pages;

            //Stopwatch sw = new Stopwatch();
            //sw.Start();
            _columns = OnCustomiseColumns?.Invoke(this, null) ?? new DynamicGridColumns();
            //Logger.Send(LogType.Information, "DEG.Load", String.Format("Loaded Columns: {0}", sw.ElapsedMilliseconds));
            //sw.Restart();
            
            var layout = new GlobalConfiguration<ScreenLayout>(_layoutname).Load();
            //Logger.Send(LogType.Information, "DEG.Load", String.Format("Loaded Layout: {0}", sw.ElapsedMilliseconds));
            //sw.Restart();

            if (!LoadLayout(layout.XAML))
                CreateLayout();

            //Logger.Send(LogType.Information, "DEG.Load", String.Format("Created Layout: {0}", sw.ElapsedMilliseconds));
            //sw.Restart();

        }

        public bool Unload()
        {
            ClearPages();

            var bChanged = false;

            foreach (var columnname in changes.Keys)
            {
                OnSetPropertyValue?.Invoke(this, columnname, changes[columnname]);
                bChanged = true;
            }

            return bChanged;
        }

        #endregion

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
            foreach (var columnname in editors.Select(x => x.ColumnName).ToArray())
            {
                var editor = editors.FirstOrDefault(x => x.ColumnName.Equals(columnname));
                if(editor is not null)
                {
                    editor.Loaded = true;
                }
            }
        }

    }
}