using System.Numerics;
using System.Text;
using Raylib_cs;

namespace Voile.Rendering
{
    public class RaylibRenderSystem : RenderSystem
    {
        public override bool ShouldRun => !WindowShouldClose();
        public override Vector2 WindowSize
        {
            get
            {
                return new Vector2(Raylib.GetScreenWidth(), Raylib.GetScreenHeight());
            }
            set
            {
                Raylib.SetWindowSize((int)value.X, (int)value.Y);
            }
        }
        public override Vector2 MonitorSize => new Vector2(GetMonitorWidth(GetCurrentMonitor()), GetMonitorHeight(GetCurrentMonitor()));

        public override string WindowTitle
        {
            get => _windowTitle; set
            {
                SetWindowTitle(value);
            }
        }

        public override int TargetFps { get => _targetFps; set => SetTargetFps(value); }
        public override bool VSync { get => _vsync; set => SetWindowVSync(value); }
        public override bool Fullscreen { get => _fullscreen; set => SetFullscreen(value); }

        public override void Initialize(RendererSettings settings)
        {
            _targetFps = settings.TargetFps;

            ConfigFlags flags = 0;

            // MSAA
            flags |= settings.Msaa == Msaa.Msaa4x ? ConfigFlags.Msaa4xHint : 0;

            // VSync
            flags |= settings.UseVSync ? ConfigFlags.VSyncHint : 0;

            _fullscreen = settings.Fullscreen;

            _defaultFlags = flags;

            Raylib.SetConfigFlags(flags);
            Raylib.SetTargetFPS(settings.TargetFps);
        }

        public override void CreateAndInitializeWithWindow(RendererSettings settings)
        {
            Initialize(settings);
            CreateWindow(settings.WindowSettings);
        }

        public override void CreateWindow(WindowSettings windowSettings)
        {
            Raylib.SetTraceLogLevel(TraceLogLevel.None);

            _defaultWindowSettings = windowSettings;

            _windowSize = windowSettings.Size;
            ConfigFlags windowFlags = 0;
            windowFlags |= windowSettings.Resizable ? ConfigFlags.ResizableWindow : 0;

            _windowTitle = windowSettings.Title;

            if (_fullscreen)
            {
                var monitorSize = MonitorSize;
                Raylib.InitWindow((int)monitorSize.X, (int)monitorSize.Y, windowSettings.Title);
                Fullscreen = true;
            }
            else
            {
                Raylib.InitWindow((int)_windowSize.X, (int)_windowSize.Y, windowSettings.Title);
            }

            Raylib.SetWindowState(windowFlags);
        }

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

        protected override void SetWindowTitle(string title)
        {
            Raylib.SetWindowTitle(title);
        }

        protected override void SetWindowVSync(bool value)
        {
            _vsync = value;

            // TODO: implement VSync toggle for Raylib.
        }

        protected override void SetTargetFps(int fps)
        {
            _targetFps = fps;
            Raylib.SetTargetFPS(fps);
        }

        protected override bool WindowShouldClose()
        {
            return Raylib.WindowShouldClose();
        }

        protected override void Shutdown()
        {
            Raylib.CloseWindow();
        }

        public override void BeginFrame()
        {
            Raylib.BeginDrawing();
        }

        public override void EndFrame()
        {
            Raylib.EndDrawing();
        }

        public override void BeginBlended(BlendMode blendMode)
        {
            var rayBlend = (Raylib_cs.BlendMode)blendMode;
            Raylib.BeginBlendMode(rayBlend);
        }

        public override void EndBlended()
        {
            Raylib.EndBlendMode();
        }

        public override void BeginCamera2d(Vector2 offset, Vector2 target, float rotation, float zoom)
        {
            var camera = new Camera2D(offset, target, rotation, zoom);
            Raylib.BeginMode2D(camera);
        }

        public override void EndCamera2d()
        {
            Raylib.EndMode2D();
        }

        public override void ClearBackground(Color color)
        {
            Raylib.ClearBackground(VoileColorToRaylibColor(color));
        }

        protected override double GetFrameTime()
        {
            return (double)Raylib.GetFrameTime();
        }

        public override void DrawCircle(float radius, Color color)
        {
            Raylib.DrawCircle((int)transformPosition.X, (int)transformPosition.Y, radius, VoileColorToRaylibColor(color));
        }

        public override void DrawTexture(ResourceRef<Texture2d> textureResource, Color tint)
        {
            var texture = textureResource.Value;

            if (texture.Handle == -1)
            {
                LoadTexture(texture);
            }

            Raylib.DrawTextureV(_texturePool[texture.Handle], transformPosition, VoileColorToRaylibColor(tint));
        }

        public override void DrawRectangle(Vector2 size, Color color)
        {
            Raylib.DrawRectanglePro(new Rectangle()
            {
                X = transformPosition.X,
                Y = transformPosition.Y,
                Width = size.X,
                Height = size.Y
            }, transformPivot, transformRotation, VoileColorToRaylibColor(color));
        }

        public override void DrawRectangleOutline(Vector2 size, Color color, float outlineWidth = 1)
        {
            Raylib.DrawRectangleLinesEx(new Rectangle()
            {
                X = transformPosition.X,
                Y = transformPosition.Y,
                Width = size.X,
                Height = size.Y
            }, outlineWidth, VoileColorToRaylibColor(color));
        }

        public override void DrawDebugText(string text, int fontSize, Color color)
        {
            Raylib.DrawText(text, (int)transformPosition.X, (int)transformPosition.Y, fontSize, VoileColorToRaylibColor(color));
        }

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

        private void SetFullscreen(bool fullscreen)
        {
            // var flags = _defaultFlags;
            // if (fullscreen && !Raylib.IsWindowFullscreen())
            // {
            //     WindowSize = MonitorSize;
            //     Raylib.ToggleFullscreen();
            // }
            // else if (Raylib.IsWindowFullscreen())
            // {
            //     // Raylib.ToggleFullscreen();
            //     WindowSize = _windowSize;
            // }

            _fullscreen = fullscreen;
        }

        // TODO
        public override void SetTransform(Matrix4x4 transform) { }

        private Raylib_cs.Color VoileColorToRaylibColor(Color color)
        {
            var rayColor = new Raylib_cs.Color(color.R, color.G, color.B, color.A);
            return rayColor;
        }

        public override void DrawText(ResourceRef<Font> fontResource, string text, Color color)
        {
            var font = fontResource.Value;

            if (font.Handle == -1)
            {
                LoadFont(font);
            }

            if (font.Dirty && font.Handle != -1)
            {
                UnloadFont(font);
                LoadFont(font);
            }

            var rayFont = _fontPool[font.Handle];

            float x = transformPosition.X;
            float y = transformPosition.Y;

            for (int i = 0; i < text.Length; i++)
            {
                char c = text[i];

                if (i > 0)
                    x += font.GetKerning(text[i - 1], c);

                Raylib.DrawTextCodepoint(
                    rayFont,
                    c,
                    new Vector2(x, y),
                    font.Size,
                    VoileColorToRaylibColor(color)
                );

                var glyph = font.GetGlyph(c);
                x += glyph.Advance * font.SpacingScale + font.LetterSpacing;
            }
        }

        protected override int GetMonitorWidth(int monitorId)
        {
            return Raylib.GetMonitorWidth(monitorId);
        }

        protected override int GetMonitorHeight(int monitorId)
        {
            return Raylib.GetMonitorHeight(monitorId);
        }

        protected override int GetCurrentMonitor()
        {
            return Raylib.GetCurrentMonitor();
        }

        private void LoadFont(Font font)
        {
            Raylib_cs.Font fontRay;

            string ext = ".ttf";
            int fontChars = font.Codepoints.Count;

            fontRay = Raylib.LoadFontFromMemory(ext, font.Buffer, font.Size, font.Codepoints.ToArray(), fontChars);

            Raylib.GenTextureMipmaps(ref fontRay.Texture);
            Raylib.SetTextureFilter(fontRay.Texture, TextureFilter.Bilinear);

            _fontPool.Add(fontRay);

            font.Handle = _fontPool.Count - 1;
        }

        private void UnloadFont(Font font)
        {
            var fontRay = _fontPool[font.Handle];
            Raylib.UnloadFont(fontRay);

            _fontPool.RemoveAt(font.Handle);
        }

        private void LoadTexture(Texture2d texture)
        {
            Image image = new();

            unsafe
            {
                fixed (void* dataPtr = texture.Buffer)
                {
                    image.Data = dataPtr;
                }
            }


            image.Width = texture.Width;
            image.Height = texture.Height;
            image.Mipmaps = texture.Mipmaps;
            image.Format = (PixelFormat)texture.Format;

            Texture2D rayTexture;

            rayTexture = Raylib.LoadTextureFromImage(image);

            _texturePool.Add(rayTexture);

            texture.Handle = _texturePool.Count - 1;
        }

        private List<Texture2D> _texturePool = new();
        private List<Raylib_cs.Font> _fontPool = new();
        private Vector2 _windowSize;
        private bool _vsync;
        private string _windowTitle = string.Empty;
        private int _targetFps;
        private bool _fullscreen;
        private WindowSettings _defaultWindowSettings;
        private ConfigFlags _defaultFlags;
    }
}