using System.Collections.Concurrent;
using System.Drawing;
using Dashboard.OpenGL;
using Dashboard.Windowing;
using OpenTK.Mathematics;
using OpenTK.Platform;
using TK = OpenTK.Platform.Toolkit;

namespace Dashboard.OpenTK.PAL2
{
    public class OpenGLDeviceContext : IGLContext, IGLDisposable
    {
        public OpenGLContextHandle ContextHandle { get; }
        public WindowHandle WindowHandle { get; }

        public ISwapGroup SwapGroup { get; }
        public int ContextGroup { get; }

        public Size FramebufferSize
        {
            get
            {
                TK.Window.GetFramebufferSize(WindowHandle, out Vector2i size);
                return new Size(size.X, size.Y);
            }
        }

        public event Action? Disposed;

        public OpenGLDeviceContext(WindowHandle window, OpenGLContextHandle context, ISwapGroup? group = null)
        {
            WindowHandle = window;
            ContextHandle = context;
            SwapGroup = group ?? new DummySwapGroup(context);
            ContextGroup = GetContextGroup(context);
        }

        public void MakeCurrent()
        {
            TK.OpenGL.SetCurrentContext(ContextHandle);
        }

        private bool _isDisposed = false;

        public void Dispose() => Dispose(true);

        public void Dispose(bool safeExit)
        {
            if (_isDisposed) return;
            _isDisposed = true;

            if (SwapGroup is IGLDisposable glDisposable)
            {
                glDisposable.Dispose(safeExit);
            }
            else if (SwapGroup is IDisposable disposable)
            {
                disposable.Dispose();
            }

            Disposed?.Invoke();
        }

        private static int _contextGroupId = 0;
        private static ConcurrentDictionary<OpenGLContextHandle, int> _contextGroupRootContexts = new ConcurrentDictionary<OpenGLContextHandle, int>();

        private static int GetContextGroup(OpenGLContextHandle handle)
        {
            OpenGLContextHandle? shared = TK.OpenGL.GetSharedContext(handle);

            if (shared == null)
            {
                if (_contextGroupRootContexts.TryGetValue(handle, out int group))
                    return group;

                group = Interlocked.Increment(ref _contextGroupId);
                _contextGroupRootContexts.TryAdd(handle, group);
                return GetContextGroup(handle);
            }
            else
            {
                if (_contextGroupRootContexts.TryGetValue(shared, out int group))
                    return group;

                return GetContextGroup(shared);
            }
        }

        public class DummySwapGroup : ISwapGroup
        {
            public OpenGLContextHandle ContextHandle { get; }

            public int SwapInterval
            {
                get
                {
                    TK.OpenGL.SetCurrentContext(ContextHandle);
                    return TK.OpenGL.GetSwapInterval();
                }
                set
                {
                    TK.OpenGL.SetCurrentContext(ContextHandle);
                    TK.OpenGL.SetSwapInterval(value);
                }
            }

            public DummySwapGroup(OpenGLContextHandle handle)
            {
                ContextHandle = handle;
            }

            public void Swap()
            {
                TK.OpenGL.SwapBuffers(ContextHandle);
            }
        }
    }
}