using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace PkmnLibSharp.Utilities
{
    public class ReadOnlyNativePtrArray<T> : IReadOnlyList<T> where T : PointerWrapper
    {
        private readonly IntPtr _ptr;
        private T?[] _cache;
        private Type _type = typeof(T);

        internal ReadOnlyNativePtrArray()
        {
            _ptr = IntPtr.Zero;
            Count = 0;
            _cache = new T[Count];
        }

        internal ReadOnlyNativePtrArray(IntPtr ptr, int length)
        {
            _ptr = ptr;
            Count = length;
            _cache = new T[Count];
        }
        
        public IEnumerator<T> GetEnumerator()
        {
            for (var i = 0; i < Count; i++)
            {
                yield return this[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        
        public bool Contains(T item)
        {
            foreach (var v in this)
            {
                if (item == null)
                {
                    if (v.Ptr == IntPtr.Zero)
                        return true;
                    continue;
                }
                if (item.Ptr == v.Ptr)
                    return true;
            }
            return false;
        }
        
        public int Count { get; }

        public int IndexOf(T? item)
        {
            for (var i = 0; i < Count; i++)
            {
                var p = _ptr + (i * IntPtr.Size);
                if (item == null)
                    return i;
                if (p == item.Ptr)
                    return i;
            }
            return -1;
        }
        
        public T this[int index]
        {
            get
            {
                unsafe
                {
                    if (index >= Count)
                        throw new IndexOutOfRangeException();
                    // Where's your god now?
                    // (We add the offset of the index to the pointer, then dereference the pointer pointer to get the actual pointer to the object we want.)
                    var p = new IntPtr(*(void**)IntPtr.Add(_ptr, index * IntPtr.Size).ToPointer());
                    if (_cache[index]?.Ptr == p)
                        return _cache[index]!;
                    if (PointerWrapper.TryResolvePointer(p, out T? t)) 
                        return t!;
                    t = (T) FormatterServices.GetUninitializedObject(_type);
                    t.Initialize(p);
                    _cache[index] = t;
                    return t;
                }
            }
        }
    }
}