using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.DynamicGrid;

namespace InABox.Wpf;

public interface IPanelActionItem
{

}

public interface IPanelActionEntry
{
    object? Data { get; set; }
    string? Caption { get; set; }
    Bitmap? Image { get; set; }
    void Execute();
}

public class PanelActionEntry<T> : DependencyObject, IPanelActionEntry
{
    
    public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
        nameof(Data),
        typeof(T),
        typeof(PanelActionEntry<T>)
    );
    public T? Data { get; set; }

    object? IPanelActionEntry.Data
    {
        get => this.Data;
        set => this.Data = value == null ? default(T) : (T)value;
    }
    
    public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register(
        nameof(Caption),
        typeof(string),
        typeof(PanelActionEntry<T>),
        new PropertyMetadata("")
    );
    public string? Caption { get; set; }
    
    public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
        nameof(Image),
        typeof(Bitmap),
        typeof(PanelActionEntry<T>)
    );    
    public Bitmap? Image
    { 
        get => GetValue(ImageProperty) as Bitmap;
        set => SetValue(ImageProperty, value);
    }
    
    public Action<PanelActionEntry<T>>? OnExecute { get; set; }

    public PanelActionEntry()
    {
        
    }
    
    public PanelActionEntry(string? caption, Bitmap? image, T? data, Action<PanelActionEntry<T>>? onExecute)
    {
        Data = data;
        Caption = caption;
        Image = image;
        OnExecute = onExecute;
    }

    public void Execute()
    {
        OnExecute?.Invoke(this);
    }
    
}

public class PanelAction : DependencyObject, IPanelActionItem
{

    public Func<PanelAction,IPanelActionEntry[]>? OnPopulate { get; set; }

    public Action<PanelAction>? OnExecute { get; set; }
    
    
    public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register(
        nameof(IsEnabled),
        typeof(bool),
        typeof(PanelAction),
        new PropertyMetadata(true)
    );
    
    public bool IsEnabled 
    { 
        get => (bool)GetValue(IsEnabledProperty);
        set => SetValue(IsEnabledProperty, value);
    }
    
    
    public static readonly DependencyProperty VisibilityProperty = DependencyProperty.Register(
        nameof(Visibility),
        typeof(Visibility),
        typeof(PanelAction),
        new PropertyMetadata(Visibility.Visible)
    );
    
    public Visibility Visibility 
    { 
        get => (Visibility)GetValue(VisibilityProperty);
        set => SetValue(VisibilityProperty, value);
    }

    public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register(
        nameof(Caption),
        typeof(string),
        typeof(PanelAction),
        new PropertyMetadata("")
    );
    
    public string Caption 
    { 
        get => (string)GetValue(CaptionProperty);
        set => SetValue(CaptionProperty, value);
    }

    public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
        nameof(Image),
        typeof(Bitmap),
        typeof(PanelAction)
    );    
    public Bitmap? Image
    { 
        get => GetValue(ImageProperty) as Bitmap;
        set => SetValue(ImageProperty, value);
    }
    
    public static readonly DependencyProperty MenuProperty = DependencyProperty.Register(
        nameof(Menu),
        typeof(ContextMenu),
        typeof(PanelAction)
    );   
    
    public ContextMenu? Menu
    { 
        get => GetValue(MenuProperty) as ContextMenu;
        set => SetValue(MenuProperty, value);
    }

    public PanelAction()
    {
    }

    public PanelAction(string caption, Bitmap? image, Action<PanelAction>? onExecute,
        Func<PanelAction, IPanelActionEntry[]>? onPopulate = null)
    {
        Caption = caption;
        Image = image;
        OnPopulate = onPopulate;
        OnExecute = onExecute;
    }
    
    public void Execute()
    {
        OnExecute?.Invoke(this);
    }

    public IPanelActionEntry[]? Populate()
    {
        return OnPopulate?.Invoke(this);
    }
    
}
public class PanelActionSeparator : IPanelActionItem
{

}

public interface ICorePanel
{
    void Setup();

    /// <summary>
    /// Shutdown the panel.
    /// </summary>
    /// <param name="cancel">If the operation can be cancelled, this is not <see langword="null"/>.</param>
    void Shutdown(CancelEventArgs? cancel);

    void Refresh();
}

public interface IBasePanel : ICorePanel, IDataModelSource
{
    bool IsReady { get; set; }
    void CreateToolbarButtons(IPanelHost host);

    Dictionary<string, object[]> Selected();

    void Heartbeat(TimeSpan time);
}

public interface IPanel<T> : IBasePanel
{
}

public interface IPropertiesPanel<TProperties>
    where TProperties : BaseObject, IGlobalConfigurationSettings, new()
{
    public TProperties Properties { get; set; }
}

public interface IPropertiesPanel<TProperties, TSecurity> : IPropertiesPanel<TProperties>
    where TProperties : BaseObject, IGlobalConfigurationSettings, new()
    where TSecurity : ISecurityDescriptor, new()
{
}

public static class PanelUtils
{
    #region Properties

    public static void InitializePanelProperties(IBasePanel panel)
    {
        var propertiesInterface = panel.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<>));
        if (propertiesInterface is not null)
        {
            var propertiesType = propertiesInterface.GenericTypeArguments[0];
            var method = typeof(PanelUtils)
                .GetMethod(nameof(InitializePanelPropertiesGeneric), BindingFlags.Public | BindingFlags.Static)
                ?.MakeGenericMethod(panel.GetType(), propertiesType)
                .Invoke(null, new object?[] { panel });
        }
    }
    public static void InitializePanelPropertiesGeneric<TPanel, TProperties>(TPanel panel)
        where TPanel : IPropertiesPanel<TProperties>
        where TProperties : BaseObject, IGlobalConfigurationSettings, new()
    {
        panel.Properties = LoadPanelProperties<TPanel, TProperties>();
    }
    public static TProperties LoadPanelProperties<TPanel, TProperties>()
        where TPanel : IPropertiesPanel<TProperties>
        where TProperties : BaseObject, IGlobalConfigurationSettings, new()
    {
        var config = new GlobalConfiguration<TProperties>();
        return config.Load();
    }

    public static void SavePanelProperties<TPanel, TProperties>(TProperties properties)
        where TPanel : IPropertiesPanel<TProperties>
        where TProperties : BaseObject, IGlobalConfigurationSettings, new()
    {
        var config = new GlobalConfiguration<TProperties>();
        config.Save(properties);
    }
    public static void EditPanelProperties<TPanel, TProperties>()
        where TPanel : IPropertiesPanel<TProperties>
        where TProperties : BaseObject, IGlobalConfigurationSettings, new()
    {
        var properties = LoadPanelProperties<TPanel, TProperties>();

        bool result;
        if (DynamicGridUtils.TryFindDynamicGrid(typeof(DynamicGrid<>), typeof(TProperties), out var gridType))
        {
            var grid = (Activator.CreateInstance(gridType) as DynamicGrid<TProperties>)!;
            result = grid.EditItems(new TProperties[] { properties });
        }
        else
        {
            var grid = new DynamicItemsListGrid<TProperties>();
            result = grid.EditItems(new TProperties[] { properties });
        }

        if (result)
        {
            SavePanelProperties<TPanel, TProperties>(properties);
        }
    }

    public static void ConfigurePanel(IBasePanel panel)
    {
        var propertiesInterface = panel.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<>))!;
        var propertiesType = propertiesInterface.GenericTypeArguments[0];
        var basemethod = typeof(PanelUtils)
            .GetMethod(nameof(EditPanelProperties), BindingFlags.Public | BindingFlags.Static);
        if (basemethod == null)
            return;
        var method = basemethod?.MakeGenericMethod(panel.GetType(), propertiesType);
        if (method != null)
            method.Invoke(null, Array.Empty<object?>());
    }

    #endregion

    public static T LoadPanel<T>() where T : class, IBasePanel, new()
    {
        var panel = new T();
        InitializePanelProperties(panel);

        panel.IsReady = false;
        panel.Setup();
        panel.IsReady = true;

        return panel;
    }

    public static void UnloadPanel(IBasePanel panel, CancelEventArgs? cancel)
    {
        try
        {
            if(panel is ISubPanelHost host)
            {
                host.ShutdownSubPanels(cancel);
            }
            panel.Shutdown(cancel);
            if (cancel?.Cancel == true)
            {
                return;
            }
        }
        catch (Exception e)
        {
            Logger.Send(LogType.Error, ClientFactory.UserID, string.Format("Error in UnloadPanel(): {0}\n{1}", e.Message, e.StackTrace));
        }
    }
}

public interface IPanelHost
{
    void CreatePanelAction(PanelAction action);

    void CreateReport(PanelAction action);

    void CreateSetupAction(PanelAction action);

    void CreateSetupSeparator();
}

public static class IPanelHostExtensions
{
    public static void CreateSetupAction(this IPanelHost host, string caption, Bitmap? image, Action<PanelAction> onExecute, ContextMenu? menu = null)
    {
        host.CreateSetupAction(new PanelAction(caption, image, onExecute) { Menu = menu });
    }

    public static void CreateSetupActionIf(this IPanelHost host, string caption, Bitmap? image, Action<PanelAction> onExecute, bool canView, ContextMenu? menu = null)
    {
        if (canView)
        {
            host.CreateSetupAction(new PanelAction(caption, image, onExecute) { Menu = menu });
        }
    }

    public static void CreateSetupActionIf<TSecurity>(this IPanelHost host, string caption, Bitmap? image, Action<PanelAction> onExecute, ContextMenu? menu = null)
        where TSecurity : ISecurityDescriptor, new()
    {
        host.CreateSetupActionIf(caption, image, onExecute, Security.IsAllowed<TSecurity>(), menu);
    }

    public static void CreateSetupActionIfCanView<T>(this IPanelHost host, string caption, Bitmap? image, Action<PanelAction> onExecute, ContextMenu? menu = null)
        where T : Entity, new()
    {
        host.CreateSetupActionIf(caption, image, onExecute, Security.CanView<T>(), menu);
    }
}

/// <summary>
/// Implement this interface to cause a class to act as a host of <see cref="ISubPanel"/>. Then, you can use the extension functions
/// <see cref="ISubPanelHostExtensions.ShutdownSubPanels(ISubPanelHost, CancelEventArgs?)"/> and
/// <see cref="ISubPanelHostExtensions.AddSubPanel(ISubPanelHost, ISubPanel)"/> to interact with it.
/// </summary>
/// <remarks>
/// If you mark an <see cref="IPanel{T}"/> as an <see cref="ISubPanelHost"/>, then the shutdown method is called automatically by <see cref="IPanelHost"/>.
/// </remarks>
public interface ISubPanelHost
{
    List<ISubPanel> SubPanels { get; }

    static ISubPanelHost Global = new GlobalSubPanelHost();

    private class GlobalSubPanelHost : ISubPanelHost
    {
        public List<ISubPanel> SubPanels { get; set; } = new();
    }
}

public static class ISubPanelHostExtensions
{
    public static void ShutdownSubPanels(this ISubPanelHost host, CancelEventArgs? cancel)
    {
        ISubPanel[] panels;
        lock (host.SubPanels)
        {
            panels = host.SubPanels.ToArray();
            host.SubPanels.Clear();

            var isCancelled = false;

            foreach(var panel in panels)
            {
                if (isCancelled)
                {
                    host.SubPanels.Add(panel);
                }
                else
                {
                    panel.Shutdown(cancel);
                    if (cancel?.Cancel == true)
                    {
                        isCancelled = true;
                        host.SubPanels.Add(panel);
                    }
                }
            }
        }
    }

    public static void AddSubPanel(this ISubPanelHost host, ISubPanel panel)
    {
        host.SubPanels.Add(panel);
        panel.SubPanelClosed += p =>
        {
            lock (host.SubPanels)
            {
                host.SubPanels.Remove(p);
            }
        };
    }
}

/// <summary>
/// An <see cref="ISubPanel"/> is a non-modal window, which is tied to a parent 
/// </summary>
public interface ISubPanel
{
    public delegate void ClosedEvent(ISubPanel panel);

    /// <summary>
    /// Event to be called when a sub-panel closes itself; in this case, <see cref="Shutdown(CancelEventArgs?)"/> will not be called. This allows
    /// the host to get rid of the sub-panel, instead of keeping it forever.
    /// </summary>
    /// <remarks>
    /// You may call this after <see cref="Shutdown(CancelEventArgs?)"/> has been called.
    /// </remarks>
    public event ClosedEvent? SubPanelClosed;

    /// <summary>
    /// Shutdown the panel.
    /// </summary>
    /// <param name="cancel">If the operation can be cancelled, this is not <see langword="null"/></param>
    void Shutdown(CancelEventArgs? cancel);
}