using ARMeilleure.Translation;
using LibRyujinx.Shared;
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;

namespace LibRyujinx
{
    public static partial class LibRyujinx
    {
        private static bool _isActive;
        private static bool _isStopped;
        private static CancellationTokenSource _gpuCancellationTokenSource;
        private static SwapBuffersCallback? _swapBuffersCallback;
        private static NativeGraphicsInterop _nativeGraphicsInterop;
        private static ManualResetEvent _gpuDoneEvent;

        public delegate void SwapBuffersCallback();
        public delegate IntPtr GetProcAddress(string name);
        public delegate IntPtr CreateSurface(IntPtr instance);

        public static IRenderer? Renderer { get; set; }
        public static GraphicsConfiguration GraphicsConfiguration { get; private set; }

        [UnmanagedCallersOnly(EntryPoint = "graphics_initialize")]
        public static bool InitializeGraphicsNative(GraphicsConfiguration graphicsConfiguration)
        {
            if(Ryujinx.Common.SystemInfo.SystemInfo.IsAndroid())
            {
                Silk.NET.Core.Loader.SearchPathContainer.Platform = Silk.NET.Core.Loader.UnderlyingPlatform.Android;
            }
            return InitializeGraphics(graphicsConfiguration);
        }
        
        public static bool InitializeGraphics(GraphicsConfiguration graphicsConfiguration)
        {
            GraphicsConfig.ResScale = graphicsConfiguration.ResScale;
            GraphicsConfig.MaxAnisotropy = graphicsConfiguration.MaxAnisotropy;
            GraphicsConfig.FastGpuTime = graphicsConfiguration.FastGpuTime;
            GraphicsConfig.Fast2DCopy = graphicsConfiguration.Fast2DCopy;
            GraphicsConfig.EnableMacroJit = graphicsConfiguration.EnableMacroJit;
            GraphicsConfig.EnableMacroHLE = graphicsConfiguration.EnableMacroHLE;
            GraphicsConfig.EnableShaderCache = graphicsConfiguration.EnableShaderCache;
            GraphicsConfig.EnableTextureRecompression = graphicsConfiguration.EnableTextureRecompression;

            GraphicsConfiguration = graphicsConfiguration;

            return true;
        }

        [UnmanagedCallersOnly(EntryPoint = "graphics_initialize_renderer")]
        public unsafe static bool InitializeGraphicsRendererNative(GraphicsBackend graphicsBackend, NativeGraphicsInterop nativeGraphicsInterop)
        {
            _nativeGraphicsInterop = nativeGraphicsInterop;
            if (Renderer != null)
            {
                return false;
            }

            List<string> extensions = new List<string>();
            var size = Marshal.SizeOf<IntPtr>();
            var extPtr = (IntPtr*)nativeGraphicsInterop.VkRequiredExtensions;
            for (int i = 0; i < nativeGraphicsInterop.VkRequiredExtensionsCount; i++)
            {
                var ptr = extPtr[i];
                extensions.Add(Marshal.PtrToStringAnsi(ptr) ?? string.Empty);
            }

            CreateSurface createSurfaceFunc = nativeGraphicsInterop.VkCreateSurface == IntPtr.Zero ? default : Marshal.GetDelegateForFunctionPointer<CreateSurface>(nativeGraphicsInterop.VkCreateSurface);

            return InitializeGraphicsRenderer(graphicsBackend, createSurfaceFunc, extensions.ToArray());
        }

        public static bool InitializeGraphicsRenderer(GraphicsBackend graphicsBackend, CreateSurface createSurfaceFunc, string?[] requiredExtensions)
        {
            if (Renderer != null)
            {
                return false;
            }

            if (graphicsBackend == GraphicsBackend.OpenGl)
            {
                Renderer = new OpenGLRenderer();
            }
            else if (graphicsBackend == GraphicsBackend.Vulkan)
            {
                Renderer = new VulkanRenderer(VulkanLoader?.GetApi() ?? Vk.GetApi(), (instance, vk) => new SurfaceKHR((ulong?)createSurfaceFunc(instance.Handle)),
                    () => requiredExtensions,
                    null);
            }
            else
            {
                return false;
            }

            return true;
        }


        [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_size")]
        public static void SetRendererSizeNative(int width, int height)
        {
            Renderer?.Window?.SetSize(width, height);
        }

        public static void SetRendererSize(int width, int height)
        {
            Renderer?.Window?.SetSize(width, height);
        }

        [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_run_loop")]
        public static void RunLoopNative()
        {
            if (Renderer is OpenGLRenderer)
            {
                var proc = Marshal.GetDelegateForFunctionPointer<GetProcAddress>(_nativeGraphicsInterop.GlGetProcAddress);
                GL.LoadBindings(new OpenTKBindingsContext(x => proc!.Invoke(x)));
            }
            RunLoop();
        }

        [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_vsync")]
        public static void SetVsyncStateNative(bool enabled)
        {
            SetVsyncState(enabled);
        }

        public static void SetVsyncState(bool enabled)
        {
            var device = SwitchDevice!.EmulationContext!;
            device.EnableDeviceVsync = enabled;
            device.Gpu.Renderer.Window.ChangeVSyncMode(enabled);
        }

        public static void RunLoop()
        {
            if (Renderer == null)
            {
                return;
            }
            var device = SwitchDevice!.EmulationContext!;
            _gpuDoneEvent = new ManualResetEvent(true);

            device.Gpu.Renderer.Initialize(GraphicsDebugLevel.None);
            _gpuCancellationTokenSource = new CancellationTokenSource();

            device.Gpu.Renderer.RunLoop(() =>
            {
                _gpuDoneEvent.Reset();
                device.Gpu.SetGpuThread();
                device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
                Translator.IsReadyForTranslation.Set();

                _isActive = true;

                while (_isActive)
                {
                    if (_isStopped)
                    {
                        break;
                    }

                    debug_break(1);

                    if (Ryujinx.Common.SystemInfo.SystemInfo.IsBionic)
                    {
                        setRenderingThread();
                    }

                    if (device.WaitFifo())
                    {
                        device.Statistics.RecordFifoStart();
                        device.ProcessFrame();
                        device.Statistics.RecordFifoEnd();
                    }

                    while (device.ConsumeFrameAvailable())
                    {
                        device.PresentFrame(() => _swapBuffersCallback?.Invoke());
                    }
                }

                if (device.Gpu.Renderer is ThreadedRenderer threaded)
                {
                    threaded.FlushThreadedCommands();
                }

                _gpuDoneEvent.Set();
            });
        }

        [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_swap_buffer_callback")]
        public static void SetSwapBuffersCallbackNative(IntPtr swapBuffersCallback)
        {
            _swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
        }
        
        public static void SetSwapBuffersCallback(SwapBuffersCallback swapBuffersCallback)
        {
            _swapBuffersCallback = swapBuffersCallback;
        }
    }


    [StructLayout(LayoutKind.Sequential)]
    public struct GraphicsConfiguration
    {
        public float ResScale = 1f;
        public float MaxAnisotropy = -1;
        public bool FastGpuTime = true;
        public bool Fast2DCopy = true;
        public bool EnableMacroJit = false;
        public bool EnableMacroHLE = true;
        public bool EnableShaderCache = true;
        public bool EnableTextureRecompression = false;
        public BackendThreading BackendThreading = BackendThreading.Auto;
        public AspectRatio AspectRatio = AspectRatio.Fixed16x9;

        public GraphicsConfiguration()
        {
        }
    }

    public struct NativeGraphicsInterop
    {
        public IntPtr GlGetProcAddress;
        public IntPtr VkNativeContextLoader;
        public IntPtr VkCreateSurface;
        public IntPtr VkRequiredExtensions;
        public int VkRequiredExtensionsCount;
    }
}
