using RandomizerCore.Collections;

namespace ArchipelagoMapMod;

public class PriorityEvent<T>
{
    public PriorityEvent(out IPriorityEventOwner p)
    {
        p = new PriorityEventOwner(this);
    }

    private readonly SortedArrayList<PriorityEntry> _entries = new(Comparer<PriorityEntry>.Default, EqualityComparer<PriorityEntry>.Default);
    private PriorityEntry[] _cachedSubscriberArray;
    private bool _cacheInvalidated = true;

    public void Subscribe(float key, T value)
    {
        _entries.Add(new(key, value));
        _cacheInvalidated = true;
    }

    public void Unsubscribe(float key, T value)
    {
        if (_entries.Remove(new(key, value)))
        {
            _cacheInvalidated = true;
        }
    }

    public interface IPriorityEventOwner
    {
        /// <summary>
        /// Returns an ordered enumerable of the event's subscribers at the moment of the request. 
        /// </summary>
        IEnumerable<T> GetSubscribers();
        /// <summary>
        /// Returns an ordered enumerable of the event's subscribers in the specified priority range, with inclusive bounds.
        /// </summary>
        IEnumerable<T> GetSubscriberRange(float min, float max);

    }

    private class PriorityEventOwner : IPriorityEventOwner
    {
        public PriorityEventOwner(PriorityEvent<T> _parent) => this._parent = _parent;
        private readonly PriorityEvent<T> _parent;
        public IEnumerable<T> GetSubscribers()
        {
            if (_parent._cacheInvalidated)
            {
                _parent._cachedSubscriberArray = _parent._entries.ToArray();
                _parent._cacheInvalidated = false;
            }

            return _parent._cachedSubscriberArray.Select(kvp => kvp.Value);
        }

        public IEnumerable<T> GetSubscriberRange(float min, float max)
        {
            int lb = _parent._entries.FindInclusiveLowerBound(new (min, default));
            int ub = _parent._entries.FindExclusiveUpperBound(new (max, default));

            T[] result = new T[ub - lb];
            for (int i = lb; i < ub; i++) result[i - lb] = _parent._entries[i].Value;
            return result;
        }
    }

    public readonly record struct PriorityEntry(float Key, T Value) : IComparable<PriorityEntry>
    {
        public int CompareTo(PriorityEvent<T>.PriorityEntry other)
        {
            return Key.CompareTo(other.Key);
        }
    }
}