using System.Numerics;

using Silk.NET.Windowing;
using Silk.NET.WebGPU;
using Silk.NET.Maths;
using Voile.Utils;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace Voile.Rendering
{
    /// <summary>
    /// A standard, WebGPU-based renderer.
    /// </summary>
    public class StandardRenderSystem : RenderSystem
    {
        /// <inheritdoc />
        public override Vector2 WindowSize { get => _windowSize; set { _windowSize = value; } }
        /// <inheritdoc />
        public override bool ShouldRun => !_window?.IsClosing ?? false;

        public override Vector2 MonitorSize => throw new NotImplementedException();

        public override int TargetFps { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
        public override bool VSync { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
        public override string WindowTitle { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
        public override bool Fullscreen { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

        /// <inheritdoc />
        public override void Initialize(RendererSettings settings)
        {
            _logger.Info("Initializing WebGPU...");

            CreateApi();
            CreateInstance();
            CreateSurface();
            CreateAdapter();
            CreateDevice();

            ConfigureDebugCallback();

            ConfigureSurface();
        }

        /// <inheritdoc />
        public override void CreateAndInitializeWithWindow(RendererSettings settings)
        {
            CreateWindow(settings.WindowSettings);
            Initialize(settings);
        }

        public override void CreateWindow(WindowSettings windowSettings)
        {
            // throw new NotImplementedException();
            _logger.Info("Creating window...");

            WindowOptions windowOptions = WindowOptions.Default;
            windowOptions.Size = new Vector2D<int>((int)windowSettings.Size.X, (int)windowSettings.Size.Y);
            windowOptions.Title = windowSettings.Title;
            windowOptions.API = GraphicsAPI.None;

            _window = Window.Create(windowOptions);
            _window.Initialize();
        }

        public override void BeginFrame()
        {
            _window!.DoEvents();
            _window.DoUpdate();
            _window.DoRender();

            WgpuBeginFrame();
        }

        /// <inheritdoc />
        public override void ClearBackground(Color color)
        {
            _clearColor = color;
        }

        public override void DrawText(ResourceRef<Font> fontResource, string text, Color color)
        {
            throw new NotImplementedException();
        }

        public override void DrawSdfText(string text, int fontSize, Color color)
        {
            throw new NotImplementedException();
        }

        public override void BeginCamera2d(Vector2 offset, Vector2 target, float rotation, float zoom)
        {
            throw new NotImplementedException();
        }

        public override void EndCamera2d()
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public override void DrawCircle(float radius, Color color)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public override void DrawDebugText(string text, int fontSize, Color color)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public override void DrawRectangle(Vector2 size, Color color)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public override void DrawTexture(ResourceRef<Texture2d> textureResource, Color tint)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public override void EndFrame()
        {
            WgpuEndFrame();
        }


        /// <inheritdoc />
        protected override void Shutdown()
        {
            _window!.DoEvents();
            _window.Reset();
            _window.Dispose();

            ShutdownUnsafe();
        }

        /// <inheritdoc />
        protected override double GetFrameTime()
        {
            return 0.0;
        }

        /// <inheritdoc />
        protected override void SetTargetFps(int fps)
        {
            return;
        }

        /// <inheritdoc />
        public override void SetTransform(Matrix4x4 transform)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        protected override void SetWindowTitle(string title)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        protected override void SetWindowVSync(bool value)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        protected override bool WindowShouldClose() => throw new NotImplementedException();

        protected override int GetMonitorWidth(int monitorId)
        {
            throw new NotImplementedException();
        }

        protected override int GetMonitorHeight(int monitorId)
        {
            throw new NotImplementedException();
        }

        protected override int GetCurrentMonitor()
        {
            throw new NotImplementedException();
        }

        private unsafe void CreateApi()
        {
            _wgpu = WebGPU.GetApi() ?? throw new Exception("Failed to initialize WebGPU API.");
        }

        private unsafe void CreateInstance()
        {
            InstanceDescriptor descriptor = new();
            _instance = _wgpu!.CreateInstance(descriptor);
        }

        private unsafe void CreateSurface()
        {
            _surface = _window.CreateWebGPUSurface(_wgpu, _instance);
        }

        private unsafe void CreateAdapter()
        {
            if (_wgpu is null)
            {
                throw new Exception("WebGPU API is not initialized!");
            }

            RequestAdapterOptions adapterOptions = new()
            {
                CompatibleSurface = _surface,
                // TODO: do not force Vulkan, let user choose their own API.
                BackendType = BackendType.Vulkan,
                PowerPreference = PowerPreference.HighPerformance
            };

            PfnRequestAdapterCallback callback = PfnRequestAdapterCallback.From(
                (status, adapter, msgPtr, userDataPtr) =>
                    {
                        if (status == RequestAdapterStatus.Success)
                        {
                            _adapter = adapter;

                            AdapterProperties adapterProperties = new();

                            _wgpu.AdapterGetProperties(_adapter, &adapterProperties);

                            string deviceName = Marshal.PtrToStringAnsi((IntPtr)adapterProperties.Name) ?? string.Empty;

                            _logger.Info($"Retrieved WebGPU Adapter ({deviceName})");
                        }
                        else
                        {
                            string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
                            _logger.Error($"Error while retrieving WebGPU Adapter: {msg}");
                        }
                    });

            _wgpu.InstanceRequestAdapter(_instance, adapterOptions, callback, null);
        }

        private unsafe void CreateDevice()
        {
            if (_wgpu is null)
            {
                throw new Exception("WebGPU API is not initialized!");
            }

            DeviceDescriptor deviceDescriptor = new();

            PfnRequestDeviceCallback callback = PfnRequestDeviceCallback.From(
                (status, device, msgPtr, userDataPtr) =>
                    {
                        if (status == RequestDeviceStatus.Success)
                        {
                            _device = device;
                            _logger.Info($"Retrieved WebGPU Device.");
                        }
                        else
                        {
                            string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
                            _logger.Error($"Error while retrieving WebGPU Device: {msg}");
                        }
                    });

            _wgpu.AdapterRequestDevice(_adapter, deviceDescriptor, callback, null);
        }

        private unsafe void ConfigureSurface()
        {
            if (_wgpu is null)
            {
                throw new Exception("WebGPU API is not initialized!");
            }

            SurfaceConfiguration configuration = new SurfaceConfiguration
            {
                Device = _device,
                Width = (uint)_window!.Size.X,
                Height = (uint)_window.Size.Y,
                Format = Silk.NET.WebGPU.TextureFormat.Bgra8Unorm,
                PresentMode = PresentMode.Immediate,
                Usage = TextureUsage.RenderAttachment
            };

            _wgpu.SurfaceConfigure(_surface, configuration);
        }

        private unsafe void ConfigureDebugCallback()
        {
            if (_wgpu is null)
            {
                throw new Exception("WebGPU API is not initialized!");
            }

            PfnErrorCallback callback = PfnErrorCallback.From((type, msgPtr, userDataPtr) =>
            {
                string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
                _logger.Error($"WebGPU Error: {msg}");
            });

            _wgpu.DeviceSetUncapturedErrorCallback(_device, callback, null);
            _logger.Info("WGPU Debug Callback Configured.");
        }

        private unsafe void WgpuBeginFrame()
        {
            if (_wgpu is null)
            {
                throw new Exception("WebGPU API is not initialized!");
            }

            _queue = _wgpu.DeviceGetQueue(_device);

            _commandEncoder = _wgpu.DeviceCreateCommandEncoder(_device, null);

            _wgpu.SurfaceGetCurrentTexture(_surface, ref _surfaceTexture);
            _surfaceTextureView = _wgpu.TextureCreateView(_surfaceTexture.Texture, null);

            RenderPassColorAttachment colorAttachments = CreateClearColorAttachment(_surfaceTextureView, _clearColor);

            RenderPassDescriptor renderPassDescriptor = new()
            {
                ColorAttachments = &colorAttachments,
                ColorAttachmentCount = 1
            };

            _renderPassEncoder = _wgpu.CommandEncoderBeginRenderPass(_commandEncoder, renderPassDescriptor);
        }

        private unsafe void WgpuEndFrame()
        {
            if (_wgpu is null)
            {
                throw new Exception("WebGPU API is not initialized!");
            }

            _wgpu.RenderPassEncoderEnd(_renderPassEncoder);
            var commandBuffer = _wgpu.CommandEncoderFinish(_commandEncoder, null);
            _wgpu.CommandEncoderRelease(_commandEncoder);

            _wgpu.QueueSubmit(_queue, 1, &commandBuffer);
            _wgpu.CommandBufferRelease(commandBuffer);

            _wgpu.SurfacePresent(_surface);
            _wgpu.TextureViewRelease(_surfaceTextureView);

            _wgpu.TextureRelease(_surfaceTexture.Texture);
            _wgpu.RenderPassEncoderRelease(_renderPassEncoder);
        }

        private unsafe void ShutdownUnsafe()
        {
            _wgpu!.DeviceDestroy(_device);
            _wgpu.InstanceRelease(_instance);
            _wgpu.SurfaceRelease(_surface);
            _wgpu.AdapterRelease(_adapter);
        }

        private Silk.NET.WebGPU.Color VoileColorToWebGPUColor(Color color)
        {
            return new Silk.NET.WebGPU.Color(color.R, color.G, color.B, color.A);
        }

        private unsafe RenderPassColorAttachment CreateClearColorAttachment(TextureView* view, Color clearColor)
        {
            var colorAttachment = new RenderPassColorAttachment
            {
                View = view,
                LoadOp = LoadOp.Clear,
                ClearValue = VoileColorToWebGPUColor(clearColor),
                StoreOp = StoreOp.Store
            };

            return colorAttachment;
        }

        private unsafe RenderPipeline* CreatePipeline(Material fromMaterial)
        {
            return null;
        }

        public override void BeginBlended(BlendMode blendMode)
        {
            throw new NotImplementedException();
        }

        public override void EndBlended()
        {
            throw new NotImplementedException();
        }

        protected override void ReloadResources()
        {
            throw new NotImplementedException();
        }

        public override void DrawRectangleOutline(Vector2 size, Color color, float outlineWidth = 1)
        {
            throw new NotImplementedException();
        }

        private Vector2 _windowSize = Vector2.Zero;
        private IWindow? _window;

        private Color _clearColor = Color.Black;

        private WebGPU? _wgpu;
        private unsafe Instance* _instance;
        private unsafe Surface* _surface;
        private unsafe Adapter* _adapter;
        private unsafe Device* _device;
        private unsafe Queue* _queue;
        private SurfaceTexture _surfaceTexture;
        private unsafe TextureView* _surfaceTextureView;
        private unsafe CommandEncoder* _commandEncoder;
        private unsafe RenderPassEncoder* _renderPassEncoder;

        private Logger _logger = new(nameof(StandardRenderSystem));
    }
}