using System;
using System.Collections.Generic;
using Engine.Core.Debug;
namespace Engine.Core;
// TODO!: every reverse loop has a chance to have more than 1 unsubscription,
// for (int i = listeners.Count - 1; i >= 0; i--) 
// can be replaced with 
// for (int i = listeners.Count - 1; i >= 0; i = Math.Min(i - 1, listeners.Count - 1))
// but this would causes possible double calls on already called callbacks, find a better method.
/// 
/// Represents a simple event with no parameters.
/// Example usage: 
/// 
/// public class MyBehaviour : Behaviour, IUpdate
/// {
///     public readonly Event MyEvent = new();
/// 
///     public MyBehaviour()
///     {
///         MyEvent.AddListener(OnEventTriggered);
///         MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
///     }
/// 
///     public void Update()
///     {
///         MyEvent.Invoke();
///     }
/// 
///     private void OnEventTriggered()
///     {
///         Console.WriteLine($"Event occurred!");
///     }
/// 
///     private static void OnEventTriggeredOneTime()
///     {
///         Console.WriteLine($"Event called once!");
///     }
/// }
/// 
/// The output of the example code above would be: 
/// 
/// Event occurred!
/// Event called once!
/// Event occurred!
/// Event occurred!
/// Event occurred!
/// ...
/// 
/// 
public class Event
{
    // We use Ascending order because draw calls are running from last to first
    private static readonly Comparer SortByAscendingPriority = Comparer.Create((x, y) => x.Priority.CompareTo(y.Priority));
    private ILogger _logger = ILogger.Shared;
    public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
    private readonly List listeners = null!;
    private readonly List onceListeners = null!;
    /// 
    /// Subscribes the callback to be invoked whenever the event is triggered.
    /// 
    /// The callback to be called when the event is triggered.
    /// Priority of the callback.
    public void AddListener(EventHandler listener, int priority = 0)
    {
        ListenerData listenerData = new(listener, priority);
        int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
        if (insertIndex < 0)
            insertIndex = ~insertIndex;
        listeners.Insert(insertIndex, listenerData);
    }
    /// 
    /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
    /// 
    /// The callback to be called the next time the event is triggered.
    /// Priority of the callback.
    public void AddOneTimeListener(EventHandler listener, int priority = 0)
    {
        ListenerData listenerData = new(listener, priority);
        int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
        if (insertIndex < 0)
            insertIndex = ~insertIndex;
        onceListeners.Insert(insertIndex, listenerData);
    }
    /// 
    /// Unsubscribes the callback that was previously registered by .
    /// 
    /// The callback that was previously registered by 
    public void RemoveListener(EventHandler listener)
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
            if (listeners[i].Callback == listener)
            {
                listeners.RemoveAt(i);
                return;
            }
    }
    /// 
    /// Unsubscribes the callback that was previously registered by .
    /// 
    /// The callback that was previously registered by 
    public void RemoveOneTimeListener(EventHandler listener)
    {
        for (int i = 0; i < onceListeners.Count; i++)
            if (onceListeners[i].Callback == listener)
            {
                onceListeners.RemoveAt(i);
                return;
            }
    }
    /// 
    /// Unsubscribes all listeners that was previously registered by either  or .
    /// 
    public void Clear() { listeners.Clear(); onceListeners.Clear(); }
    /// 
    /// Triggers the event.
    /// 
    public void Invoke()
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
            try { listeners[i].Callback.Invoke(); }
            catch (Exception exception)
            {
                string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()";
                EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
            }
        for (int i = onceListeners.Count - 1; i >= 0; i--)
        {
            try { onceListeners[i].Callback.Invoke(); }
            catch (Exception exception)
            {
                string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()";
                EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
            }
            onceListeners.RemoveAt(i);
        }
    }
    public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
    {
        listeners = new(initialListenerCount);
        onceListeners = new(initialOnceListenerCount);
    }
    public Event()
    {
        listeners = new(4);
        onceListeners = new(2);
    }
    public delegate void EventHandler();
    private record struct ListenerData(EventHandler Callback, int Priority);
}
/// 
/// Represents an event with only sender parameters.
/// Example usage: 
/// 
/// public class MyBehaviour : Behaviour, IUpdate
/// {
///     public readonly Event<MyBehaviour> MyEvent = new();
/// 
///     public MyBehaviour()
///     {
///         MyEvent.AddListener(OnEventTriggered);
///         MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
///     }
/// 
///     public void Update()
///     {
///         MyEvent.Invoke(this);
///     }
/// 
///     private void OnEventTriggered(MyBehaviour sender)
///     {
///         Console.WriteLine($"{sender.Id}'s event occurred!");
///     }
/// 
///     private static void OnEventTriggeredOneTime(MyBehaviour sender)
///     {
///         Console.WriteLine($"{sender.Id}'s event called once!");
///     }
/// }
/// 
/// The output of the example code above would be: 
/// 
/// [Id]'s event occurred!
/// [Id]'s event called once!
/// [Id]'s event occurred!
/// [Id]'s event occurred!
/// [Id]'s event occurred!
/// ...
/// 
/// 
/// 
/// Sender type
public class Event where TSender : class
{
    // We use Ascending order because draw calls are running from last to first
    private static readonly Comparer SortByAscendingPriority = Comparer.Create((x, y) => x.Priority.CompareTo(y.Priority));
    private ILogger _logger = ILogger.Shared;
    public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
    private readonly List listeners = null!;
    private readonly List onceListeners = null!;
    /// 
    /// Subscribes the callback to be invoked whenever the event is triggered.
    /// 
    /// The callback to be called when the event is triggered.
    /// Priority of the callback.
    public void AddListener(EventHandler listener, int priority = 0)
    {
        ListenerData listenerData = new(listener, priority);
        int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
        if (insertIndex < 0)
            insertIndex = ~insertIndex;
        listeners.Insert(insertIndex, listenerData);
    }
    /// 
    /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
    /// 
    /// The callback to be called the next time the event is triggered.
    /// Priority of the callback.
    public void AddOneTimeListener(EventHandler listener, int priority = 0)
    {
        ListenerData listenerData = new(listener, priority);
        int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
        if (insertIndex < 0)
            insertIndex = ~insertIndex;
        onceListeners.Insert(insertIndex, listenerData);
    }
    /// 
    /// Unsubscribes the callback that was previously registered by .
    /// 
    /// The callback that was previously registered by 
    public void RemoveListener(EventHandler listener)
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
            if (listeners[i].Callback == listener)
            {
                listeners.RemoveAt(i);
                return;
            }
    }
    /// 
    /// Unsubscribes the callback that was previously registered by .
    /// 
    /// The callback that was previously registered by 
    public void RemoveOneTimeListener(EventHandler listener)
    {
        for (int i = 0; i < onceListeners.Count; i++)
            if (onceListeners[i].Callback == listener)
            {
                onceListeners.RemoveAt(i);
                return;
            }
    }
    /// 
    /// Unsubscribes all listeners that was previously registered by either  or .
    /// 
    public void Clear() { listeners.Clear(); onceListeners.Clear(); }
    /// 
    /// Triggers the event.
    /// 
    /// The caller that's triggering this event.
    public void Invoke(TSender sender)
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
            try { listeners[i].Callback.Invoke(sender); }
            catch (Exception exception)
            {
                string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})";
                EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
            }
        for (int i = onceListeners.Count - 1; i >= 0; i--)
        {
            try { onceListeners[i].Callback.Invoke(sender); }
            catch (Exception exception)
            {
                string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})";
                EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
            }
            onceListeners.RemoveAt(i);
        }
    }
    public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
    {
        listeners = new(initialListenerCount);
        onceListeners = new(initialOnceListenerCount);
    }
    public Event()
    {
        listeners = new(4);
        onceListeners = new(2);
    }
    public delegate void EventHandler(TSender sender);
    private record struct ListenerData(EventHandler Callback, int Priority);
}
/// 
/// Represents an event with sender and argument parameters.
/// Example usage: 
/// 
/// public class MyBehaviour : Behaviour, IUpdate
/// {
///     public readonly Event<MyBehaviour, MyArguments> MyEvent = new();
/// 
///     private int myInt = 0;
///     private bool myBool = false;
/// 
///     public MyBehaviour()
///     {
///         MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
///         MyEvent.AddListener(OnEventTriggered);
///     }
/// 
///     public void Update()
///     {
///         MyEvent.Invoke(this, new MyArguments(myInt, myBool));
///         myInt++;
///         myBool = !myBool;
///     }
/// 
///     private void OnEventTriggered(MyBehaviour sender, MyArguments args)
///     {
///         Console.WriteLine($"{sender.Id}'s event occurred with MyInt: {args.MyInt} and MyBool {args.MyBool}!");
///     }
/// 
///     private static void OnEventTriggeredOneTime(MyBehaviour sender, MyArguments args)
///     {
///         Console.WriteLine($"{sender.Id}'s event called once with MyInt: {args.MyInt} and MyBool {args.MyBool}!");
///     }
/// 
///     public readonly record struct MyArguments(int MyInt, bool MyBool);
/// }
/// 
/// The output of the example code above would be: 
/// 
/// [Id]'s event occurred with MyInt: 0 and MyBool False!
/// [Id]'s event called once with MyInt: 0 and MyBool False!
/// [Id]'s event occurred with MyInt: 1 and MyBool True!
/// [Id]'s event occurred with MyInt: 2 and MyBool False!
/// [Id]'s event occurred with MyInt: 3 and MyBool True!
/// ...
/// 
/// 
/// 
/// Sender type
public class Event where TSender : class
{
    // We use Ascending order because draw calls are running from last to first
    private static readonly Comparer SortByAscendingPriority = Comparer.Create((x, y) => x.Priority.CompareTo(y.Priority));
    private ILogger _logger = ILogger.Shared;
    public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
    private readonly List listeners = null!;
    private readonly List onceListeners = null!;
    /// 
    /// Subscribes the callback to be invoked whenever the event is triggered.
    /// 
    /// The callback to be called when the event is triggered.
    /// Priority of the callback.
    public void AddListener(EventHandler listener, int priority = 0)
    {
        ListenerData listenerData = new(listener, priority);
        int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
        if (insertIndex < 0)
            insertIndex = ~insertIndex;
        listeners.Insert(insertIndex, listenerData);
    }
    /// 
    /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
    /// 
    /// The callback to be called the next time the event is triggered.
    /// Priority of the callback.
    public void AddOneTimeListener(EventHandler listener, int priority = 0)
    {
        ListenerData listenerData = new(listener, priority);
        int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
        if (insertIndex < 0)
            insertIndex = ~insertIndex;
        onceListeners.Insert(insertIndex, listenerData);
    }
    /// 
    /// Unsubscribes the callback that was previously registered by .
    /// 
    /// The callback that was previously registered by 
    public void RemoveListener(EventHandler listener)
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
            if (listeners[i].Callback == listener)
            {
                listeners.RemoveAt(i);
                return;
            }
    }
    /// 
    /// Unsubscribes the callback that was previously registered by .
    /// 
    /// The callback that was previously registered by 
    public void RemoveOneTimeListener(EventHandler listener)
    {
        for (int i = 0; i < onceListeners.Count; i++)
            if (onceListeners[i].Callback == listener)
            {
                onceListeners.RemoveAt(i);
                return;
            }
    }
    /// 
    /// Unsubscribes all listeners that was previously registered by either  or .
    /// 
    public void Clear() { listeners.Clear(); onceListeners.Clear(); }
    /// 
    /// Triggers the event.
    /// 
    /// The caller that's triggering this event.
    /// The arguments provided for this event.
    public void Invoke(TSender sender, TArguments args)
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
            try { listeners[i].Callback.Invoke(sender, args); }
            catch (Exception exception)
            {
                string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender}, {args})";
                EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
            }
        for (int i = onceListeners.Count - 1; i >= 0; i--)
        {
            try { onceListeners[i].Callback.Invoke(sender, args); }
            catch (Exception exception)
            {
                string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender}, {args})";
                EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
            }
            onceListeners.RemoveAt(i);
        }
    }
    public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
    {
        listeners = new(initialListenerCount);
        onceListeners = new(initialOnceListenerCount);
    }
    public Event()
    {
        listeners = new(4);
        onceListeners = new(2);
    }
    public delegate void EventHandler(TSender sender, TArguments args);
    private record struct ListenerData(EventHandler Callback, int Priority);
}
internal static class EventHelpers
{
    public static void LogInvocationException(object sender, ILogger logger, Exception exception, string methodCallRepresentation)
    {
        logger.LogException(sender, exception);
        logger.LogError(sender, $"Unexpected exception on invocation of method {methodCallRepresentation}");
    }
}