using System;
using System.Collections.Concurrent;

namespace PkmnLibSharp.Utilities
{
    public abstract class PointerWrapper : IDisposable
    {
        private IntPtr _ptr;

        internal IntPtr Ptr
        {
            get
            {
                if (_isDeleted)
                {
                    throw new Exception(
                        "Pointer access after dispose detected. This is not legal, and will cause native exceptions.");
                }

                return _ptr;
            }
        }

        private bool _isDeleted = false;

        private static readonly ConcurrentDictionary<IntPtr, WeakReference<object>> Cached =
            new ConcurrentDictionary<IntPtr, WeakReference<object>>();

        private protected PointerWrapper()
        {
        }

        protected PointerWrapper(IntPtr ptr)
        {
            Initialize(ptr);
        }

        protected internal virtual void Initialize(IntPtr ptr)
        {
            _ptr = ptr;
            var weakRef = new WeakReference<object>(this);
            Cached.AddOrUpdate(ptr, weakRef, (intPtr, reference) => weakRef);
        }
        
        ~PointerWrapper()
        {
            if (!_isDeleted)
                Cached.TryRemove(Ptr, out _);
        }

        public static bool TryResolvePointer<T>(IntPtr p, out T? result) where T : PointerWrapper
        {
            if (p == IntPtr.Zero)
            {
                result = null;
                return true;
            }
            if (Cached.TryGetValue(p, out var val))
            {
                if (val.TryGetTarget(out var target))
                {
                    if (target is T r)
                    {
                        result = r;
                        return true;
                    }
                }
            }

            result = null;
            return false;
        }


        protected abstract void DeletePtr();

        public virtual void Dispose()
        {
            if (_isDeleted)
                return;
            DeletePtr();
            MarkAsDeleted();
        }

        protected internal virtual void MarkAsDeleted()
        {
            _isDeleted = true;
            Cached.TryRemove(_ptr, out _);
        }

        public static bool operator ==(PointerWrapper? a, PointerWrapper? b)
        {
            return a?._ptr == b?._ptr;
        }

        public static bool operator !=(PointerWrapper? a, PointerWrapper? b)
        {
            return !(a == b);
        }

        protected bool Equals(PointerWrapper other)
        {
            return _ptr.Equals(other._ptr);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((PointerWrapper) obj);
        }

        public override int GetHashCode()
        {
            return _ptr.GetHashCode();
        }

        public override string ToString()
        {
            return $"(#{_ptr}) -> {GetType().FullName}";
        }
    }
}