using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace CPF.Windows.MicroCom
{
    public static unsafe class MicroComRuntime
    {
        private static ConcurrentDictionary<Type, IntPtr> _vtables = new ConcurrentDictionary<Type, IntPtr>();

        private static ConcurrentDictionary<Type, Func<IntPtr, bool, object>> _factories =
            new ConcurrentDictionary<Type, Func<IntPtr, bool, object>>();
        private static ConcurrentDictionary<Type, Guid> _guids = new ConcurrentDictionary<Type, Guid>();
        private static ConcurrentDictionary<Guid, Type> _guidsToTypes = new ConcurrentDictionary<Guid, Type>();

        static MicroComRuntime()
        {
            Register(typeof(IUnknown), new Guid("00000000-0000-0000-C000-000000000046"),
                (ppv, owns) => new MicroComProxyBase(ppv, owns));
            RegisterVTable(typeof(IUnknown), MicroComVtblBase.Vtable);
        }

        public static void RegisterVTable(Type t, IntPtr vtable)
        {
            _vtables[t] = vtable;
        }

        public static void Register(Type t, Guid guid, Func<IntPtr, bool, object> proxyFactory)
        {
            _factories[t] = proxyFactory;
            _guids[t] = guid;
            _guidsToTypes[guid] = t;
        }

        public static Guid GetGuidFor(Type type) => _guids[type];

        public static T CreateProxyFor<T>(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
        public static T CreateProxyFor<T>(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle);

        public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle);

        public static IntPtr GetNativeIntPtr<T>(this T obj, bool owned = false) where T : IUnknown
            => new IntPtr(GetNativePointer(obj, owned));
        public static void* GetNativePointer<T>(T obj, bool owned = false) where T : IUnknown
        {
            if (obj == null)
                return null;
            if (obj is MicroComProxyBase proxy)
            {
                if (owned)
                    proxy.AddRef();
                return (void*)proxy.NativePointer;
            }

            if (obj is IMicroComShadowContainer container)
            {
                //container.Shadow ??= new MicroComShadow(container);
                if (container.Shadow==null)
                {
                    container.Shadow = new MicroComShadow(container);
                }
                void* ptr = null;
                var res = container.Shadow.GetOrCreateNativePointer(typeof(T), &ptr);
                if (res != 0)
                    throw new COMException(
                        "Unable to create native callable wrapper for type " + typeof(T) + " for instance of type " +
                        obj.GetType(),
                        res);
                if (owned)
                    container.Shadow.AddRef((Ccw*)ptr);
                return ptr;
            }
            throw new ArgumentException("Unable to get a native pointer for " + obj);
        }

        public static object GetObjectFromCcw(IntPtr ccw)
        {
            var ptr = (Ccw*)ccw;
            var shadow = (MicroComShadow)GCHandle.FromIntPtr(ptr->GcShadowHandle).Target;
            return shadow.Target;
        }

        public static bool TryGetTypeForGuid(Guid guid, out Type t) => _guidsToTypes.TryGetValue(guid, out t);

        public static bool GetVtableFor(Type type, out IntPtr ptr) => _vtables.TryGetValue(type, out ptr);

        public static void UnhandledException(object target, Exception e)
        {
            if (target is IMicroComExceptionCallback cb)
            {
                try
                {
                    cb.RaiseException(e);
                }
                catch
                {
                    // We've tried
                }
            }

        }

        public static T CloneReference<T>(this T iface) where T : IUnknown
        {
            var proxy = (MicroComProxyBase)(object)iface;
            var ownedPointer = GetNativePointer(iface, true);
            return CreateProxyFor<T>(ownedPointer, true);
        }

        public static T QueryInterface<T>(this IUnknown unknown) where T : IUnknown
        {
            var proxy = (MicroComProxyBase)unknown;
            return proxy.QueryInterface<T>();
        }

        public static void UnsafeAddRef(this IUnknown unknown)
        {
            ((MicroComProxyBase)unknown).AddRef();
        }

        public static void UnsafeRelease(this IUnknown unknown)
        {
            ((MicroComProxyBase)unknown).Release();
        }
    }
}
