using System;
using System.Collections;
using System.Collections.Generic;
namespace Engine.Core;
/// 
/// TODO This is VEERY experimental, and doesn't work well with the indices access. Use with caution
/// 
/// 
/// 
public class FastListOrdered : IList, IReadOnlyList, IEnumerable where TItem : notnull where TIndex : IComparable
{
    private readonly SortedDictionary> items = null!;
    private readonly Func getIndexFunc = null!;
    private readonly IComparer sortBy = null!;
    private int count = 0;
    public int Count => count;
    public bool IsReadOnly { get; set; } = false;
    public TItem this[int index]
    {
        get { (TIndex tIndex, int i) = GetAt(index); return items[tIndex][i]; }
        set
        {
            if (IsReadOnly)
                throw new System.Data.ReadOnlyException();
            (TIndex tIndex, int i) = GetAt(index); items[tIndex][i] = value;
        }
    }
    private (TIndex TIndex, int i) GetAt(Index index)
    {
        int actualIndex = index.IsFromEnd
                ? count - index.Value
                : index.Value;
        if (actualIndex < 0 || actualIndex >= count)
            throw new IndexOutOfRangeException();
        int leftIndex = actualIndex;
        foreach ((TIndex i, FastList list) in items)
        {
            if (leftIndex < list.Count)
                return (i, leftIndex);
            leftIndex -= list.Count;
        }
        throw new IndexOutOfRangeException();
    }
    public int IndexOf(TItem item)
    {
        int indexCounter = 0;
        foreach ((TIndex index, FastList list) in items)
        {
            int i = list.IndexOf(item);
            if (i != -1)
                return indexCounter + i;
            indexCounter += list.Count;
        }
        return -1;
    }
    public void Add(TItem item)
    {
        if (IsReadOnly)
            throw new System.Data.ReadOnlyException();
        TIndex key = getIndexFunc(item);
        if (!items.TryGetValue(key, out FastList? list))
            items[key] = list = [];
        list.Add(item);
        count++;
    }
    public void Insert(int index, TItem item)
    {
        if (IsReadOnly)
            throw new System.Data.ReadOnlyException();
        TIndex tIndex = getIndexFunc(item);
        if (!items.TryGetValue(tIndex, out FastList? list))
            items[tIndex] = list = [];
        list.Insert(index, item);
        count++;
    }
    public bool Remove(TItem item)
    {
        if (IsReadOnly)
            throw new System.Data.ReadOnlyException();
        TIndex index = getIndexFunc(item);
        if (!items.TryGetValue(index, out FastList? list))
            throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
        if (!list.Remove(item))
            return false;
        count--;
        return true;
    }
    public void RemoveAt(int index)
    {
        if (IsReadOnly)
            throw new System.Data.ReadOnlyException();
        (TIndex tIndex, int i) = GetAt(index);
        items[tIndex].RemoveAt(i);
        count--;
    }
    public void Clear()
    {
        if (IsReadOnly)
            throw new System.Data.ReadOnlyException();
        foreach ((TIndex index, FastList list) in items)
            list.Clear();
        count = 0;
    }
    public bool Contains(TItem item)
    {
        foreach ((TIndex index, FastList list) in items)
            if (list.Contains(item))
                return true;
        return false;
    }
    public void CopyTo(TItem[] array, int arrayIndex)
    {
        int indexCounter = 0;
        foreach ((TIndex index, FastList list) in items)
        {
            list.CopyTo(array, indexCounter);
            indexCounter += list.Count;
        }
    }
    public IEnumerator GetEnumerator()
    {
        foreach ((TIndex index, FastList list) in items)
            foreach (TItem item in list)
                yield return item;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    public FastListOrdered(Func getIndexFunc, Comparison sortBy)
    {
        this.getIndexFunc = getIndexFunc;
        this.sortBy = Comparer.Create(sortBy);
        items = new(this.sortBy);
    }
    public FastListOrdered(Func getIndexFunc, IComparer sortBy)
    {
        this.getIndexFunc = getIndexFunc;
        this.sortBy = sortBy;
        items = new(sortBy);
    }
}