// ImGuiController with docking and viewport support for MoonWorks/Refresh.
// Based on the example im ImGui.NET and MoonWorksDearImGuiScaffold.

using ImGuiNET;
using Nerfed.Runtime.Graphics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Nerfed.Runtime.Gui;

public class GuiController : IDisposable
{
    public event Action OnGui;

    private readonly string shaderContentPath = Path.Combine(System.AppContext.BaseDirectory, "Content", "Shaders");

    private readonly GraphicsDevice graphicsDevice;
    private readonly Window mainWindow;
    private readonly Color clearColor;

    private readonly Platform_CreateWindow createWindow;
    private readonly Platform_DestroyWindow destroyWindow;
    private readonly Platform_GetWindowPos getWindowPos;
    private readonly Platform_ShowWindow showWindow;
    private readonly Platform_SetWindowPos setWindowPos;
    private readonly Platform_SetWindowSize setWindowSize;
    private readonly Platform_GetWindowSize getWindowSize;
    private readonly Platform_SetWindowFocus setWindowFocus;
    private readonly Platform_GetWindowFocus getWindowFocus;
    private readonly Platform_GetWindowMinimized getWindowMinimized;
    private readonly Platform_SetWindowTitle setWindowTitle;

    private readonly ResourceUploader resourceUploader;
    private readonly GraphicsPipeline imGuiPipeline;
    private readonly Shader imGuiVertexShader;
    private readonly Shader imGuiFragmentShader;
    private readonly Sampler imGuiSampler;
    private readonly GuiTextureStorage textureStorage = new GuiTextureStorage();
    private readonly GuiViewportWindow mainViewportWindow;

    private Texture fontTexture = null;
    private uint vertexCount = 0;
    private uint indexCount = 0;
    private Graphics.Buffer imGuiVertexBuffer = null;
    private Graphics.Buffer imGuiIndexBuffer = null;
    private bool frameBegun = false;

    public GuiController(GraphicsDevice graphicsDevice, Window mainWindow, Color clearColor, ImGuiConfigFlags configFlags = ImGuiConfigFlags.NavEnableKeyboard | ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.ViewportsEnable)
    {
        this.graphicsDevice = graphicsDevice;
        this.mainWindow = mainWindow;
        this.clearColor = clearColor;

        resourceUploader = new ResourceUploader(graphicsDevice);

        ImGui.CreateContext();

        ImGuiIOPtr io = ImGui.GetIO();
        io.DisplaySize = new Vector2(mainWindow.Width, mainWindow.Height);
        io.DisplayFramebufferScale = Vector2.One;

        imGuiVertexShader = ResourceManager.Load<Shader>("Shaders/ImGui.vert");
        imGuiFragmentShader = ResourceManager.Load<Shader>("Shaders/ImGui.frag");

        imGuiSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp);

        imGuiPipeline = new GraphicsPipeline(
            graphicsDevice,
            new GraphicsPipelineCreateInfo
            {
                AttachmentInfo = new GraphicsPipelineAttachmentInfo(
                    new ColorAttachmentDescription(
                        mainWindow.SwapchainFormat,
                        ColorAttachmentBlendState.NonPremultiplied
                    )
                ),
                DepthStencilState = DepthStencilState.Disable,
                MultisampleState = MultisampleState.None,
                PrimitiveType = PrimitiveType.TriangleList,
                RasterizerState = RasterizerState.CW_CullNone,
                VertexInputState = VertexInputState.CreateSingleBinding<Position2DTextureColorVertex>(),
                VertexShader = imGuiVertexShader,
                FragmentShader = imGuiFragmentShader,
            }
        );

        BuildFontAtlas();

        io.ConfigFlags = configFlags;

        if (!OperatingSystem.IsWindows())
        {
            io.SetClipboardTextFn = GuiClipboard.SetFnPtr;
            io.GetClipboardTextFn = GuiClipboard.GetFnPtr;
        }

        ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
        ImGuiViewportPtr mainViewport = platformIO.Viewports[0];
        mainViewport.PlatformHandle = mainWindow.Handle;
        mainViewportWindow = new GuiViewportWindow(graphicsDevice, mainViewport, mainWindow);

        unsafe
        {
            createWindow = CreateWindow;
            destroyWindow = DestroyWindow;
            getWindowPos = GetWindowPos;
            showWindow = ShowWindow;
            setWindowPos = SetWindowPos;
            setWindowSize = SetWindowSize;
            getWindowSize = GetWindowSize;
            setWindowFocus = SetWindowFocus;
            getWindowFocus = GetWindowFocus;
            getWindowMinimized = GetWindowMinimized;
            setWindowTitle = SetWindowTitle;

            platformIO.Platform_CreateWindow = Marshal.GetFunctionPointerForDelegate(createWindow);
            platformIO.Platform_DestroyWindow = Marshal.GetFunctionPointerForDelegate(destroyWindow);
            platformIO.Platform_ShowWindow = Marshal.GetFunctionPointerForDelegate(showWindow);
            platformIO.Platform_SetWindowPos = Marshal.GetFunctionPointerForDelegate(setWindowPos);
            platformIO.Platform_SetWindowSize = Marshal.GetFunctionPointerForDelegate(setWindowSize);
            platformIO.Platform_SetWindowFocus = Marshal.GetFunctionPointerForDelegate(setWindowFocus);
            platformIO.Platform_GetWindowFocus = Marshal.GetFunctionPointerForDelegate(getWindowFocus);
            platformIO.Platform_GetWindowMinimized = Marshal.GetFunctionPointerForDelegate(getWindowMinimized);
            platformIO.Platform_SetWindowTitle = Marshal.GetFunctionPointerForDelegate(setWindowTitle);

            ImGuiNative.ImGuiPlatformIO_Set_Platform_GetWindowPos(platformIO.NativePtr, Marshal.GetFunctionPointerForDelegate(getWindowPos));
            ImGuiNative.ImGuiPlatformIO_Set_Platform_GetWindowSize(platformIO.NativePtr, Marshal.GetFunctionPointerForDelegate(getWindowSize));
        }

        io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
        io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
        io.BackendFlags |= ImGuiBackendFlags.PlatformHasViewports;
        io.BackendFlags |= ImGuiBackendFlags.RendererHasViewports;

        UpdatePerFrameImGuiData(1.0 / 60.0);
        ImGui.NewFrame();
        frameBegun = true;
    }

    public void Update(double deltaTime)
    {
        if (frameBegun)
        {
            ImGui.Render();
            ImGui.UpdatePlatformWindows();
        }

        UpdatePerFrameImGuiData(deltaTime);
        UpdateInput();
        UpdateCursor();
        UpdateMonitors();

        frameBegun = true;
        ImGui.NewFrame();

        OnGui?.Invoke();

        ImGui.EndFrame();
    }

    private void UpdatePerFrameImGuiData(double deltaSeconds)
    {
        ImGuiIOPtr io = ImGui.GetIO();
        io.DisplaySize = new Vector2(mainWindow.Width, mainWindow.Height);
        io.DisplayFramebufferScale = new Vector2(1, 1);
        io.DeltaTime = (float)deltaSeconds; // DeltaTime is in seconds.
    }

    private void UpdateInput()
    {
        ImGuiIOPtr io = ImGui.GetIO();

        if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
        {
            // For viewports we use the global mouse position.
            _ = SDL2.SDL.SDL_GetGlobalMouseState(out int x, out int y);
            io.MousePos = new Vector2(x, y);
        }
        else
        {
            // Without viewports we need to use the relative position.
            //_ = SDL2.SDL.SDL_GetMouseState(out int x, out int y);
            io.MousePos = Mouse.Position;
        }

        io.MouseDown[0] = Mouse.IsButtonDown(MouseButton.Left);
        io.MouseDown[1] = Mouse.IsButtonDown(MouseButton.Right);
        io.MouseDown[2] = Mouse.IsButtonDown(MouseButton.Middle);
        
        io.MouseWheel = Mouse.GetWheel();

        io.AddKeyEvent(ImGuiKey.A, Keyboard.IsKeyDown(Key.A));
        io.AddKeyEvent(ImGuiKey.Z, Keyboard.IsKeyDown(Key.Z));
        io.AddKeyEvent(ImGuiKey.Y, Keyboard.IsKeyDown(Key.Y));
        io.AddKeyEvent(ImGuiKey.X, Keyboard.IsKeyDown(Key.X));
        io.AddKeyEvent(ImGuiKey.C, Keyboard.IsKeyDown(Key.C));
        io.AddKeyEvent(ImGuiKey.V, Keyboard.IsKeyDown(Key.V));

        io.AddKeyEvent(ImGuiKey.Tab, Keyboard.IsKeyDown(Key.Tab));
        io.AddKeyEvent(ImGuiKey.LeftArrow, Keyboard.IsKeyDown(Key.Left));
        io.AddKeyEvent(ImGuiKey.RightArrow, Keyboard.IsKeyDown(Key.Right));
        io.AddKeyEvent(ImGuiKey.UpArrow, Keyboard.IsKeyDown(Key.Up));
        io.AddKeyEvent(ImGuiKey.DownArrow, Keyboard.IsKeyDown(Key.Down));
        io.AddKeyEvent(ImGuiKey.Enter, Keyboard.IsKeyDown(Key.Enter));
        io.AddKeyEvent(ImGuiKey.Escape, Keyboard.IsKeyDown(Key.Escape));
        io.AddKeyEvent(ImGuiKey.Delete, Keyboard.IsKeyDown(Key.Delete));
        io.AddKeyEvent(ImGuiKey.Backspace, Keyboard.IsKeyDown(Key.Backspace));
        io.AddKeyEvent(ImGuiKey.Home, Keyboard.IsKeyDown(Key.Home));
        io.AddKeyEvent(ImGuiKey.End, Keyboard.IsKeyDown(Key.End));
        io.AddKeyEvent(ImGuiKey.PageDown, Keyboard.IsKeyDown(Key.PageDown));
        io.AddKeyEvent(ImGuiKey.PageUp, Keyboard.IsKeyDown(Key.PageUp));
        io.AddKeyEvent(ImGuiKey.Insert, Keyboard.IsKeyDown(Key.Insert));

        io.AddKeyEvent(ImGuiKey.ModCtrl, Keyboard.IsKeyDown(Key.LeftControl) || Keyboard.IsKeyDown(Key.RightControl));
        io.AddKeyEvent(ImGuiKey.ModShift, Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift));
        io.AddKeyEvent(ImGuiKey.ModAlt, Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt));
        io.AddKeyEvent(ImGuiKey.ModSuper, Keyboard.IsKeyDown(Key.LeftSuper) || Keyboard.IsKeyDown(Key.RightSuper));

        ReadOnlySpan<char> input = Keyboard.GetTextInput();
        if (!input.IsEmpty)
        {
            foreach (char c in input)
            {
                if (c == '\t')
                {
                    break;
                }

                io.AddInputCharacter(c);
            }
        }
    }

    private void UpdateCursor()
    {
        ImGuiIOPtr io = ImGui.GetIO();
        
        if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) != 0)
        {
            return;
        }

        ImGuiMouseCursor imGuiCursor = ImGui.GetMouseCursor();

        if (imGuiCursor == ImGuiMouseCursor.None || io.MouseDrawCursor)
        {
            _ = SDL2.SDL.SDL_ShowCursor(0);
        }
        else
        {
            nint sdlCursor = imGuiCursor switch
            {
                ImGuiMouseCursor.Arrow => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_ARROW),
                ImGuiMouseCursor.TextInput => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_IBEAM),
                ImGuiMouseCursor.ResizeAll => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZEALL),
                ImGuiMouseCursor.ResizeNS => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENS),
                ImGuiMouseCursor.ResizeEW => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZEWE),
                ImGuiMouseCursor.ResizeNESW => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENESW),
                ImGuiMouseCursor.ResizeNWSE => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENWSE),
                ImGuiMouseCursor.Hand => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_HAND),
                ImGuiMouseCursor.NotAllowed => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NO),
                _ => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_ARROW),
            };
            SDL2.SDL.SDL_SetCursor(sdlCursor);
            _ = SDL2.SDL.SDL_ShowCursor(1);
        }
    }

    private unsafe void UpdateMonitors()
    {
        ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
        Marshal.FreeHGlobal(platformIO.NativePtr->Monitors.Data);
        int videoDisplayCount = SDL2.SDL.SDL_GetNumVideoDisplays();
        IntPtr data = Marshal.AllocHGlobal(Unsafe.SizeOf<ImGuiPlatformMonitor>() * videoDisplayCount);
        platformIO.NativePtr->Monitors = new ImVector(videoDisplayCount, videoDisplayCount, data);

        for (int i = 0; i < videoDisplayCount; i++)
        {
            _ = SDL2.SDL.SDL_GetDisplayUsableBounds(i, out SDL2.SDL.SDL_Rect usableBounds);
            _ = SDL2.SDL.SDL_GetDisplayBounds(i, out SDL2.SDL.SDL_Rect bounds);
            _ = SDL2.SDL.SDL_GetDisplayDPI(i, out float ddpi, out float hdpi, out float vdpi);
            ImGuiPlatformMonitorPtr monitor = platformIO.Monitors[i];
            float standardDpi = 96f; // Standard DPI typically used
            monitor.DpiScale = hdpi / standardDpi;
            monitor.MainPos = new Vector2(bounds.x, bounds.y);
            monitor.MainSize = new Vector2(bounds.w, bounds.h);
            monitor.WorkPos = new Vector2(usableBounds.x, usableBounds.y);
            monitor.WorkSize = new Vector2(usableBounds.w, usableBounds.h);
        }
    }

    public void Render()
    {
        if (!frameBegun)
        {
            return;
        }

        frameBegun = false;

        if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
        {
            ImGui.Render();
            ImGui.UpdatePlatformWindows();
            ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();

            for (int i = 0; i < platformIO.Viewports.Size; i++)
            {
                ImGuiViewportPtr vp = platformIO.Viewports[i];
                GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;

                if (!window.Window.Claimed)
                {
                    continue;
                }

                UpdateImGuiBuffers(vp.DrawData);

                CommandBuffer commandBuffer = graphicsDevice.AcquireCommandBuffer();
                Texture swapchainTexture = commandBuffer.AcquireSwapchainTexture(window.Window);

                if (swapchainTexture != null)
                {
                    RenderCommandLists(commandBuffer, swapchainTexture, vp.DrawData);
                    graphicsDevice.Submit(commandBuffer);
                    //graphicsDevice.Wait();
                }
            }
        }
        else
        {
            ImGui.Render();

            if (!mainWindow.Claimed)
            {
                return;
            }

            ImDrawDataPtr drawDataPtr = ImGui.GetDrawData();
            UpdateImGuiBuffers(drawDataPtr);

            CommandBuffer commandBuffer = graphicsDevice.AcquireCommandBuffer();
            Texture swapchainTexture = commandBuffer.AcquireSwapchainTexture(mainWindow);

            if (swapchainTexture != null)
            {
                RenderCommandLists(commandBuffer, swapchainTexture, drawDataPtr);
                graphicsDevice.Submit(commandBuffer);
                //graphicsDevice.Wait();
            }
        }
    }

    private unsafe void UpdateImGuiBuffers(ImDrawDataPtr drawDataPtr)
    {
        if (drawDataPtr.TotalVtxCount == 0 || drawDataPtr.CmdListsCount == 0)
        {
            return;
        }

        if (drawDataPtr.TotalVtxCount > vertexCount)
        {
            imGuiVertexBuffer?.Dispose();

            vertexCount = (uint)(drawDataPtr.TotalVtxCount * 1.5f);
            imGuiVertexBuffer = Graphics.Buffer.Create<Position2DTextureColorVertex>(
                graphicsDevice,
                BufferUsageFlags.Vertex,
                vertexCount
            );
        }

        if (drawDataPtr.TotalIdxCount > indexCount)
        {
            imGuiIndexBuffer?.Dispose();

            indexCount = (uint)(drawDataPtr.TotalIdxCount * 1.5f);
            imGuiIndexBuffer = Graphics.Buffer.Create<ushort>(
                graphicsDevice,
                BufferUsageFlags.Index,
                indexCount
            );
        }

        uint vertexOffset = 0;
        uint indexOffset = 0;

        for (int n = 0; n < drawDataPtr.CmdListsCount; n++)
        {
            ImDrawListPtr cmdList = drawDataPtr.CmdLists[n];

            resourceUploader.SetBufferData(
                imGuiVertexBuffer, 
                vertexOffset, 
                new Span<Position2DTextureColorVertex>(cmdList.VtxBuffer.Data.ToPointer(), cmdList.VtxBuffer.Size),
                n == 0
            );

            resourceUploader.SetBufferData(
                imGuiIndexBuffer,
                indexOffset,
                new Span<ushort>(cmdList.IdxBuffer.Data.ToPointer(), cmdList.IdxBuffer.Size),
                n == 0
            );

            vertexOffset += (uint)cmdList.VtxBuffer.Size;
            indexOffset += (uint)cmdList.IdxBuffer.Size;
        }

        resourceUploader.Upload();
    }

    private void RenderCommandLists(CommandBuffer commandBuffer, Texture renderTexture, ImDrawDataPtr drawDataPtr)
    {
        Vector2 pos = drawDataPtr.DisplayPos;

        RenderPass renderPass = commandBuffer.BeginRenderPass(
            new ColorAttachmentInfo(renderTexture, false, clearColor)
        );

        renderPass.BindGraphicsPipeline(imGuiPipeline);

        // It is possible that the buffers are null (for example nothing is in our main windows viewport, then we exixt early but still clear it).
        if (imGuiVertexBuffer == null || imGuiIndexBuffer == null)
        {
            commandBuffer.EndRenderPass(renderPass);
            return;
        }

        Matrix4x4 projectionMatrix = Matrix4x4.CreateOrthographicOffCenter(
            pos.X,
            pos.X + drawDataPtr.DisplaySize.X,
            pos.Y + drawDataPtr.DisplaySize.Y,
            pos.Y,
            -1.0f,
            1.0f
        );
        TransformVertexUniform vertexUniform = new TransformVertexUniform(projectionMatrix);

        renderPass.BindVertexBuffer(imGuiVertexBuffer);
        renderPass.BindIndexBuffer(imGuiIndexBuffer, IndexElementSize.Sixteen);

        commandBuffer.PushVertexUniformData(in vertexUniform);

        uint vertexOffset = 0;
        uint indexOffset = 0;

        for (int n = 0; n < drawDataPtr.CmdListsCount; n++)
        {
            ImDrawListPtr cmdList = drawDataPtr.CmdLists[n];

            for (int cmdIndex = 0; cmdIndex < cmdList.CmdBuffer.Size; cmdIndex++)
            {
                ImDrawCmdPtr drawCmd = cmdList.CmdBuffer[cmdIndex];

                Texture texture = textureStorage.GetTexture(drawCmd.TextureId);

                if (texture == null)
                {
                    Log.Error("Texture or drawCmd.TextureId became null. Fit it!");
                    continue;
                }

                renderPass.BindFragmentSampler(new TextureSamplerBinding(texture, imGuiSampler));

                float width = drawCmd.ClipRect.Z - (int)drawCmd.ClipRect.X;
                float height = drawCmd.ClipRect.W - (int)drawCmd.ClipRect.Y;

                if (width <= 0 || height <= 0)
                {
                    continue;
                }

                renderPass.SetScissor(
                    new Rect(
                        (int)drawCmd.ClipRect.X - (int)pos.X,
                        (int)drawCmd.ClipRect.Y - (int)pos.Y,
                        (int)drawCmd.ClipRect.Z - (int)drawCmd.ClipRect.X,
                        (int)drawCmd.ClipRect.W - (int)drawCmd.ClipRect.Y
                    )
                );

                renderPass.DrawIndexedPrimitives(vertexOffset, indexOffset, drawCmd.ElemCount / 3);

                indexOffset += drawCmd.ElemCount;
            }

            vertexOffset += (uint)cmdList.VtxBuffer.Size;
        }

        commandBuffer.EndRenderPass(renderPass);
    }

    #region Resources
    private unsafe void BuildFontAtlas()
    {
        ResourceUploader resourceUploader = new ResourceUploader(graphicsDevice);

        ImGuiIOPtr io = ImGui.GetIO();

        io.Fonts.GetTexDataAsRGBA32(
            out nint pixelData,
            out int width,
            out int height,
            out int bytesPerPixel
        );

        Texture fontTexture = resourceUploader.CreateTexture2D(
            new Span<byte>((void*)pixelData, width * height * bytesPerPixel),
            (uint)width,
            (uint)height
        );

        resourceUploader.Upload();
        resourceUploader.Dispose();

        io.Fonts.SetTexID(fontTexture.Handle);
        io.Fonts.ClearTexData();

        textureStorage.Add(fontTexture);    // <-- The fontTexture seems to get lost after some time (CG?).
        this.fontTexture = fontTexture;     // <-- So we also keep a reference to make sure it doesn't happen.
    }
    #endregion

    #region Window
    private void CreateWindow(ImGuiViewportPtr vp)
    {
        GuiViewportWindow window = new GuiViewportWindow(graphicsDevice, vp);
    }

    private void DestroyWindow(ImGuiViewportPtr vp)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        window.Dispose();

        vp.PlatformUserData = IntPtr.Zero;
    }

    private void ShowWindow(ImGuiViewportPtr vp)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        SDL2.SDL.SDL_ShowWindow(window.Window.Handle);
    }

    private unsafe void GetWindowPos(ImGuiViewportPtr vp, Vector2* outPos)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        SDL2.SDL.SDL_GetWindowPosition(window.Window.Handle, out int x, out int y);
        *outPos = new Vector2(x, y);
    }

    private void SetWindowPos(ImGuiViewportPtr vp, Vector2 pos)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        SDL2.SDL.SDL_SetWindowPosition(window.Window.Handle, (int)pos.X, (int)pos.Y);
    }

    private void SetWindowSize(ImGuiViewportPtr vp, Vector2 size)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        SDL2.SDL.SDL_SetWindowSize(window.Window.Handle, (int)size.X, (int)size.Y);
    }

    private unsafe void GetWindowSize(ImGuiViewportPtr vp, Vector2* outSize)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        SDL2.SDL.SDL_GetWindowSize(window.Window.Handle, out int w, out int h);
        *outSize = new Vector2(w, h);
    }

    private void SetWindowFocus(ImGuiViewportPtr vp)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        //SDL2.SDL.SDL_SetWindowInputFocus(window.Handle);
        SDL2.SDL.SDL_RaiseWindow(window.Window.Handle);
    }

    private byte GetWindowFocus(ImGuiViewportPtr vp)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return (byte)0;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        SDL2.SDL.SDL_WindowFlags flags = (SDL2.SDL.SDL_WindowFlags)SDL2.SDL.SDL_GetWindowFlags(window.Window.Handle);

        return (flags & SDL2.SDL.SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS) != 0 ? (byte)1 : (byte)0;
    }

    private byte GetWindowMinimized(ImGuiViewportPtr vp)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return 0;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        SDL2.SDL.SDL_WindowFlags flags = (SDL2.SDL.SDL_WindowFlags)SDL2.SDL.SDL_GetWindowFlags(window.Window.Handle);

        return (flags & SDL2.SDL.SDL_WindowFlags.SDL_WINDOW_MINIMIZED) != 0 ? (byte)1 : (byte)0;
    }

    private unsafe void SetWindowTitle(ImGuiViewportPtr vp, IntPtr title)
    {
        if (vp.PlatformUserData == IntPtr.Zero) return;

        GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
        byte* titlePtr = (byte*)title;
        int count = 0;
        while (titlePtr[count] != 0)
        {
            count += 1;
        }
        SDL2.SDL.SDL_SetWindowTitle(window.Window.Handle, System.Text.Encoding.ASCII.GetString(titlePtr, count));
    }
    #endregion

    public void Dispose()
    {
        fontTexture?.Dispose();
        imGuiVertexBuffer?.Dispose();
        imGuiIndexBuffer?.Dispose();
        ResourceManager.Unload(imGuiVertexShader);
        ResourceManager.Unload(imGuiFragmentShader);
        imGuiPipeline?.Dispose();
        imGuiSampler?.Dispose();
        resourceUploader?.Dispose();
    }
}