using OpenTK;
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Input.HLE;
using System;
using static SDL2.SDL;

namespace Ryujinx.Headless.SDL2.OpenGL
{
    class OpenGLWindow : WindowBase
    {
        private static void SetupOpenGLAttributes(bool sharedContext, GraphicsDebugLevel debugLevel)
        {
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 3);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG : 0);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, sharedContext ? 1 : 0);

            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ACCELERATED_VISUAL, 1);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_RED_SIZE, 8);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_GREEN_SIZE, 8);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_BLUE_SIZE, 8);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ALPHA_SIZE, 8);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DEPTH_SIZE, 16);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STENCIL_SIZE, 0);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1);
            SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STEREO, 0);
        }

        private class OpenToolkitBindingsContext : IBindingsContext
        {
            public IntPtr GetProcAddress(string procName)
            {
                return SDL_GL_GetProcAddress(procName);
            }
        }

        private class SDL2OpenGLContext : IOpenGLContext
        {
            private IntPtr _context;
            private IntPtr _window;
            private bool _shouldDisposeWindow;

            public SDL2OpenGLContext(IntPtr context, IntPtr window, bool shouldDisposeWindow = true)
            {
                _context = context;
                _window = window;
                _shouldDisposeWindow = shouldDisposeWindow;
            }

            public static SDL2OpenGLContext CreateBackgroundContext(SDL2OpenGLContext sharedContext)
            {
                sharedContext.MakeCurrent();

                // Ensure we share our contexts.
                SetupOpenGLAttributes(true, GraphicsDebugLevel.None);
                IntPtr windowHandle = SDL_CreateWindow("Ryujinx background context window", 0, 0, 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
                IntPtr context = SDL_GL_CreateContext(windowHandle);

                GL.LoadBindings(new OpenToolkitBindingsContext());

                SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0);

                SDL_GL_MakeCurrent(windowHandle, IntPtr.Zero);

                return new SDL2OpenGLContext(context, windowHandle);
            }

            public void MakeCurrent()
            {
                if (SDL_GL_GetCurrentContext() == _context || SDL_GL_GetCurrentWindow() == _window)
                {
                    return;
                }

                int res = SDL_GL_MakeCurrent(_window, _context);

                if (res != 0)
                {
                    string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\"";

                    Logger.Error?.Print(LogClass.Application, errorMessage);

                    throw new Exception(errorMessage);
                }
            }

            public void Dispose()
            {
                SDL_GL_DeleteContext(_context);

                if (_shouldDisposeWindow)
                {
                    SDL_DestroyWindow(_window);
                }
            }
        }

        private GraphicsDebugLevel _glLogLevel;
        private SDL2OpenGLContext _openGLContext;

        public OpenGLWindow(
            InputManager inputManager,
            GraphicsDebugLevel glLogLevel,
            AspectRatio aspectRatio,
            bool enableMouse,
            HideCursor hideCursor)
            : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
        {
            _glLogLevel = glLogLevel;
        }

        public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_OPENGL;

        protected override void InitializeWindowRenderer()
        {
            // Ensure to not share this context with other contexts before this point.
            SetupOpenGLAttributes(false, _glLogLevel);
            IntPtr context = SDL_GL_CreateContext(WindowHandle);
            SDL_GL_SetSwapInterval(1);

            if (context == IntPtr.Zero)
            {
                string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\"";

                Logger.Error?.Print(LogClass.Application, errorMessage);

                throw new Exception(errorMessage);
            }

            // NOTE: The window handle needs to be disposed by the thread that created it and is handled separately.
            _openGLContext = new SDL2OpenGLContext(context, WindowHandle, false);

            // First take exclusivity on the OpenGL context.
            ((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL2OpenGLContext.CreateBackgroundContext(_openGLContext));

            _openGLContext.MakeCurrent();

            GL.ClearColor(0, 0, 0, 1.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit);
            SwapBuffers();

            Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
            MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
        }

        protected override void InitializeRenderer() { }

        protected override void FinalizeWindowRenderer()
        {
            // Try to bind the OpenGL context before calling the gpu disposal.
            _openGLContext.MakeCurrent();

            Device.DisposeGpu();

            // Unbind context and destroy everything
            SDL_GL_MakeCurrent(WindowHandle, IntPtr.Zero);
            _openGLContext.Dispose();
        }

        protected override void SwapBuffers()
        {
            SDL_GL_SwapWindow(WindowHandle);
        }
    }
}