using InABox.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Document = InABox.Core.Document;

namespace InABox.DynamicGrid
{
    /// <summary>
    /// Interaction logic for EmbeddedDynamicEditorForm.xaml
    /// </summary>
    public partial class EmbeddedDynamicEditorForm : UserControl, IDynamicEditorForm
    {

        public event OnBeforeLoad? OnBeforeLoad;
        public void BeforeLoad() => OnBeforeLoad?.Invoke(this);

        public event OnAfterLoad? OnAfterLoad;
        public void AfterLoad() => OnAfterLoad?.Invoke(this);
        
        public delegate void OKEvent();

        public delegate void CancelEvent();

        public DynamicEditorPages Pages { get; private set; } = new();

        private BaseObject[] _items;

        public BaseObject[] Items
        {
            get => _items;
            set
            {
                _items = value;
                DynamicEditorFormModel.Slug = Items != null ? Items.Any() ? Items.First().GetType().EntityName().Split('.').Last() : "" : "";
                Editor.Load(Pages);
            }
        }

        public bool ReadOnly { get; set; }
        
        public static readonly DependencyProperty ButtonsVisibleProperty =
            DependencyProperty.Register(
                nameof(ButtonsVisible), 
                typeof(bool), 
                typeof(EmbeddedDynamicEditorForm), 
                new UIPropertyMetadata(true)
            );
        
        public bool ButtonsVisible
        {
            get => (bool)GetValue(ButtonsVisibleProperty);
            set
            {
                SetValue(ButtonsVisibleProperty, value);
                UpdateButtonsRowVisibility();
            }
        }
        
        private void UpdateButtonsRowVisibility()
        {
            ButtonRow.Height = ButtonsVisible
                ? new GridLength(40, GridUnitType.Pixel)
                : new GridLength(0, GridUnitType.Pixel);
        }
        
        public static readonly DependencyProperty TabsVisibleProperty =
            DependencyProperty.Register(
                nameof(TabsVisible), 
                typeof(bool), 
                typeof(EmbeddedDynamicEditorForm), 
                new UIPropertyMetadata(true)
            );
        
        public bool TabsVisible
        {
            get => (bool)GetValue(TabsVisibleProperty);
            set
            {
                SetValue(TabsVisibleProperty, value);
                UpdateTabsVisibility();
            }
        }


        private void UpdateTabsVisibility()
        {
            Editor.TabStripVisible = TabsVisible;
        }

        #region Events


        public event OnValidateData? OnValidateData;

        public OnCustomiseColumns? OnCustomiseColumns { get; set; }
        public OnDefineFilter? OnDefineFilter { get; set; }

        public OnDefineLookup? OnDefineLookups { get; set; }

        public DefineEditorEventHandler? OnDefineEditor { get; set; }
        public event OnFormCustomiseEditor? OnFormCustomiseEditor;
        public OnReconfigureEditors? OnReconfigureEditors { get; set; }

        public event EditorValueChangedHandler? OnEditorValueChanged;
        public event OnAfterEditorValueChanged? OnAfterEditorValueChanged;

        public IDynamicEditorForm.GetDocumentEvent? OnGetDocument { get; set; }
        public IDynamicEditorForm.FindDocumentEvent? OnFindDocument { get; set; }
        public IDynamicEditorForm.SaveDocumentEvent? OnSaveDocument { get; set; }
        
        public event OnSelectPage? OnSelectPage;

        public DynamicGridSaveEvent? OnSaveItem { get; set; }

        public DynamicEditorGrid.EditorCreatedHandler? OnEditorCreated;

        public event OKEvent? OnOK;
        public event CancelEvent? OnCancel;

        #endregion

        public EmbeddedDynamicEditorForm()
        {
            ReadOnly = false;
            InitializeComponent();
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            UpdateButtonsRowVisibility();
            UpdateTabsVisibility();
        }

        public EmbeddedDynamicEditorForm(Type type, DynamicEditorPages? pages = null, DynamicEditorButtons? buttons = null,
            Func<Type, CoreTable>? PageDataHandler = null, bool PreloadPages = false): this()
        {
            Setup(type, pages, buttons, PageDataHandler, PreloadPages);
        }

        private IFilter? Editor_OnDefineFilter(Type type)
        {
            return OnDefineFilter?.Invoke(type);
        }

        private void ClearEvents()
        {
            OnEditorValueChanged = null;
            OnAfterEditorValueChanged = null;
            OnSelectPage = null;
            OnValidateData = null;
        }

        public void Setup(Type type, DynamicEditorPages? pages = null, DynamicEditorButtons? buttons = null,
            Func<Type, CoreTable>? PageDataHandler = null, bool PreloadPages = false)
        {
            ClearEvents();
            
            Editor.UnderlyingType = type;

            Editor.OnLoadPage = page => { page.Load(Items.First(), PageDataHandler); };

            Editor.PreloadPages = PreloadPages;

            Pages = pages ?? new DynamicEditorPages();

            Buttons.Children.Clear();
            if (buttons != null)
                foreach (var button in buttons)
                {
                    var btn = new Button();
                    UpdateButton(btn, button.Image, button.Name);
                    btn.Tag = button;
                    btn.Margin = new Thickness(5, 5, 0, 5);
                    btn.Padding = new Thickness(5, 0, 5, 0);
                    btn.Click += Btn_Click;
                    Buttons.Children.Add(btn);
                    button.Button = btn;
                    button.Form = this;
                }
        }

        public void UnloadEditorPages(bool saved)
        {
            Editor.UnloadPages(saved);
        }

        protected void UpdateButton(Button button, BitmapImage? image, string text)
        {
            var stackPnl = new StackPanel();
            stackPnl.Orientation = Orientation.Horizontal;
            //stackPnl.Margin = new Thickness(2);

            if (image != null)
            {
                var img = new Image();
                img.Source = image;
                img.Margin = new Thickness(2);
                stackPnl.Children.Add(img);
            }

            if (!string.IsNullOrEmpty(text))
            {
                var lbl = new Label();
                lbl.Content = text;
                lbl.VerticalAlignment = VerticalAlignment.Stretch;
                lbl.VerticalContentAlignment = VerticalAlignment.Center;
                lbl.Margin = new Thickness(2, 0, 5, 0);
                stackPnl.Children.Add(lbl);
            }

            button.Content = stackPnl;
        }

        private Dictionary<string, object?> EditorValueChanged(object sender, string name, object value)
        {
            if (OnEditorValueChanged != null)
                return OnEditorValueChanged(sender, name, value);
            return DynamicGridUtils.UpdateEditorValue(_items, name, value);
        }

        private void Editor_OnEditorCreated(object sender, double height, double width)
        {
            OnEditorCreated?.Invoke(sender, height, width);

            Editor.VerticalAlignment = VerticalAlignment.Stretch;
            Editor.HorizontalAlignment = HorizontalAlignment.Stretch;

            OKButton.IsEnabled = !ReadOnly;
        }

        private void Editor_OnCustomiseColumns(object sender, DynamicGridColumns columns)
        {
            columns.Clear();
            if (_items != null && _items.Any())
                columns.ExtractColumns(_items.First().GetType());
            OnCustomiseColumns?.Invoke(this, columns);
        }

        private void Btn_Click(object sender, RoutedEventArgs e)
        {
            var button = (Button)sender;
            var deb = (DynamicEditorButton)button.Tag;
            deb.Click();
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            var errors = OnValidateData?.Invoke(this, Items);

            if (errors != null && errors.Any())
            {
                MessageBox.Show(
                    string.Format("The following errors have been found with your data!\nPlease correct them and try again.\n\n- {0}",
                        string.Join("\n- ", errors)), "Validation Error");
                return;
            }
            OnOK?.Invoke();

            // Don't Commit the changes here, because we want to refer back to thos changes when we save the item
            // to trigger specific processes in the database
            //Close();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            // However, if we cancel the edits, then we can safely revert the items back to their original (loaded) state
            foreach (var item in _items)
                item.CancelChanges();

            OnCancel?.Invoke();
            //Close();
        }

        public void SaveItem(CancelEventArgs e)
        {
            OnSaveItem?.Invoke(this, e);
        }

        public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
        {
            return Editor.TryFindEditor(columnname, out editor);
        }
        public IDynamicEditorControl FindEditor(string columnname)
        {
            return Editor.FindEditor(columnname);
        }
        public object? GetEditorValue(string columnName) => FindEditor(columnName).GetValue(columnName);
        public void SetEditorValue(string columnName, object? value) => FindEditor(columnName).SetValue(columnName, value);

        public void SetLayoutType<T>() where T : DynamicEditorGridLayout => Editor.SetLayoutType<T>();

        private void Editor_OnSelectPage(DynamicEditorGrid sender, BaseObject[] items)
        {
            OnSelectPage?.Invoke(sender, items);
        }

        private void Editor_OnUnloadPage(IDynamicEditorPage page, bool saved)
        {
            if (!saved)
                page.BeforeSave(Items.First());
            else
                page.AfterSave(Items.First());
        }

        private Dictionary<string, object?>? Editor_OnAfterEditorValueChanged(DynamicEditorGrid sender, string columnname)
        {
            return OnAfterEditorValueChanged?.Invoke(sender, columnname);
        }

        private void Editor_OnReconfigureEditors(DynamicEditorGrid sender)
        {
            OnReconfigureEditors?.Invoke(sender);
        }

        private void Editor_OnGridCustomiseEditor(DynamicEditorGrid sender, DynamicGridColumn column, BaseEditor editor)
        {
            OnFormCustomiseEditor?.Invoke(this, Items, column, editor);
        }

        private decimal Editor_OnGetSequence(DynamicGridColumn column)
        {
            if (_items.Any())
                return CoreUtils.GetPropertySequence(_items.First().GetType(), column.ColumnName);
            return 0.0M;
        }

        private void Editor_OnDefineLookups(ILookupEditorControl editor)
        {
            OnDefineLookups?.Invoke(editor);
        }

        private Document? Editor_OnGetDocument(Guid id)
        {
            return OnGetDocument?.Invoke(id);
        }

        private Document? Editor_OnFindDocument(string file)
        {
            return OnFindDocument?.Invoke(file);
        }

        private void Editor_OnSaveDocument(Document document)
        {
            OnSaveDocument?.Invoke(document);
        }

        private object?[] Editor_GetItems()
        {
            return _items;
        }

        private BaseEditor Editor_OnGetEditor(DynamicGridColumn column)
        {
            if (_items != null && _items.Any())
            {
                var property = DatabaseSchema.Property(Editor.UnderlyingType, column.ColumnName);
                if (property == null) return new NullEditor();

                if (property.Editor is NullEditor)
                    return property.Editor;

                BaseEditor editor;
                if (property is CustomProperty)
                {
                    editor = property.Editor.CloneEditor();
                }
                else
                {
                    editor = OnDefineEditor?.Invoke(_items[0], column) ?? column.Editor.CloneEditor();
                    var propEditor = property.Editor;
                    editor.Page = propEditor.Page;
                    editor.Caption = propEditor.Caption;
                }

                if (ReadOnly && editor.Editable.Equals(Editable.Enabled))
                    editor.Editable = Editable.Disabled;
                return editor;
            }

            return new NullEditor();
        }

        private object? Editor_OnGetPropertyValue(object sender, string column)
        {
            if (!_items.Any())
                return null;

            object? result;
            try
            {
                result = CoreUtils.GetPropertyValue(_items.First(), column);
            }
            catch
            {
                result = _items.First().UserProperties.ContainsKey(column) ? _items.First().UserProperties[column] : null;
            }

            if (result == null)
                return null;

            foreach (var _item in _items)
            {
                object? curvalue;
                try
                {
                    curvalue = CoreUtils.GetPropertyValue(_item, column);
                }
                catch
                {
                    curvalue = _item.UserProperties.ContainsKey(column) ? _item.UserProperties[column] : null;
                }

                if (curvalue == null)
                    return null;

                if (!curvalue.Equals(result))
                    return null;
            }

            return result;
        }

        private void Editor_OnSetPropertyValue(object sender, string column, object value)
        {
            foreach (var _item in _items)
                if (_item.UserProperties.ContainsKey(column))
                    _item.UserProperties[column] = value;
                else
                    CoreUtils.SetPropertyValue(_item, column, value);
        }

        //public void EditLayout() => Editor.EditLayout();
        //public void ResetLayout() => Editor.ResetLayout();

    }
}
