using System;
using System.Collections.Generic;
using System.Diagnostics;
using Quik;
using Quik.VertexGenerator;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using Quik.FreeType;
using Quik.OpenTK;
using Quik.Typography;
using Quik.Controls;

namespace QuikTestApplication
{
    public class Program
    {
        public const string vertex =
            @"#version 140
uniform mat4 matrix;
in vec2 position;
in vec2 texcoord;
in vec4 color;
out vec2 ftexcoord;
out vec4 fcolor;

void main()
{
    fcolor = color;
    ftexcoord = texcoord;
    gl_Position = matrix * vec4(position.xy, 0, 1);
}
";

        public const string fragment =
            @"#version 140
in vec2 ftexcoord;
in vec4 fcolor;
out vec4 outcolor;

uniform sampler2D texture0;
uniform vec2 texture0offset;

void main()
{
    outcolor = fcolor * texture(texture0, ftexcoord + texture0offset);
}
";

        public static void Main(string[] args)
        {
            NativeWindowSettings windowSettings = NativeWindowSettings.Default;
            windowSettings.NumberOfSamples = 4;
            NativeWindow window = new NativeWindow(windowSettings);

            window.Context.MakeCurrent();
            GL.LoadBindings(new GLFWBindingsContext());

            FreeTypeFontManager fontManager = new FreeTypeFontManager();
            QuikContext context = new QuikContext(new OpenGLTextureManager(), fontManager);
            QuikVertexGenerator gen = new QuikVertexGenerator(context);
            RootControl root = new RootControl(context);
            Button button = new Button()
            {
                Bounds = new QuikRectangle(120, 60, 20, 20),
                Text = "button",
                Padding = 8,
                NormalStroke = new QuikStrokeStyle(new QuikColor(0xccccccff), 4f),
                HoverStroke = new QuikStrokeStyle(new QuikColor(0x1010ccff), 4f),
                ActiveStroke = new QuikStrokeStyle(new QuikColor(0x999999ff), 4f),
            };
            button.Clicked += (sender, args) => {
                if (!args.Buttons.HasFlag(Quik.MouseButton.Primary))
                    return;

                Button xbutton = (Button)sender;

                if (xbutton.Text == "button")
                {
                    xbutton.Text = "le button";
                }
                else
                {
                    xbutton.Text = "button";
                }
            };
            root.MouseEnter += (_,_) => Console.WriteLine("enter");
            root.MouseLeave += (_,_) => Console.WriteLine("leave");
            root.MouseMove  += (_,_) => Console.WriteLine("move");
            root.MouseDown  += (_,_) => Console.WriteLine("down");
            root.MouseUp    += (_,_) => Console.WriteLine("up");
            root.Add(button);

            GL.Enable(EnableCap.Multisample);

            int sp;
            {
                int vs, fs;

                sp = GL.CreateProgram();
                
                vs = GL.CreateShader(ShaderType.VertexShader);
                fs = GL.CreateShader(ShaderType.FragmentShader);

                GL.ShaderSource(vs, vertex);
                GL.CompileShader(vs);
                GL.ShaderSource(fs, fragment);
                GL.CompileShader(fs);
                
                GL.AttachShader(sp, vs);
                GL.AttachShader(sp, fs);
                GL.LinkProgram(sp);
                
                GL.UseProgram(sp);
            }

            new GL30Driver();
            
            int vbo, ebo, vao;
            vbo = GL.GenBuffer();
            ebo = GL.GenBuffer();

            vao = GL.GenVertexArray();
            GL.BindVertexArray(vao);
            
            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);

            int loc;
            GL.VertexAttribPointer(
                loc = GL.GetAttribLocation(sp, "position"),
                2,
                VertexAttribPointerType.Float,
                false,
                QuikVertex.Stride,
                QuikVertex.PositionOffset);
            GL.EnableVertexAttribArray(loc);
            GL.VertexAttribPointer(
                loc = GL.GetAttribLocation(sp, "texcoord"),
                2,
                VertexAttribPointerType.Float,
                false,
                QuikVertex.Stride,
                QuikVertex.TextureCoordinatesOffset);
            GL.EnableVertexAttribArray(loc);
            GL.VertexAttribPointer(
                loc = GL.GetAttribLocation(sp, "color"),
                4,
                VertexAttribPointerType.UnsignedByte,
                true,
                QuikVertex.Stride,
                QuikVertex.ColorOffset);
            GL.EnableVertexAttribArray(loc);

            loc = GL.GetUniformLocation(sp, "matrix");

            int offsetLoc = GL.GetUniformLocation(sp, "texture0offset");

            QuikStrokeStyle strokeBorder = new QuikStrokeStyle()
            {
                Color = new QuikColor(0xaaaaaaff),
                Width = 8
            };

            QuikStrokeStyle strokeNoBorder = new QuikStrokeStyle()
            {
                Color = new QuikColor(0xaaaaaaff),
                Width = 0
            };

            QuikFillStyle fill = new QuikFillStyle()
            {
                Color = new QuikColor(0xeeeeeeff)
            };

            QuikFillStyle magenta = new QuikFillStyle
            {
                Color = new QuikColor(0xff00ffff)
            };

            int whiteTexture = GL.GenTexture();
            uint[] whitePixel = {0xFFFFFFFF};
            GL.BindTexture(TextureTarget.Texture2D, whiteTexture);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, 1, 1, 0, PixelFormat.Rgba, PixelType.UnsignedByte, whitePixel);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest);

            context.DefaultFont = context.FontManager.GetFont(new QuikFontStyle("Arial", 16, QuikFontType.Normal));

            const string testString =
                "The quick brown fox jumps over the lazy dog. " +
                "Sphinx of black quartz judge my vow. " +
                "Have some japanese for fun: これが読めるかな?";

            var para = new HorizontalParagraph();
            para.ConsumeText(context.DefaultFont, testString);
            var typeset = new TypesetGroup();
            para.Typeset(typeset, 200);

            window.Context.SwapInterval = 0;
            Stopwatch stopwatch = Stopwatch.StartNew();
            float lastMs = stopwatch.ElapsedMilliseconds;
            int frames = 0;
            for (;!window.IsExiting;)
            {
                NativeWindow.ProcessWindowEvents(false);
                window.ProcessEvents(0.0f);
                window.TryGetCurrentMonitorDpi(out float dpi, out _);

                if (window.IsFocused)
                {
                    var mouseState = window.MouseState;
                    QuikVec2 postion = new QuikVec2(mouseState.Position.X, window.ClientSize.Y - mouseState.Position.Y) * (dpi/72f);
                    Quik.MouseButton buttons =
                        (mouseState.IsButtonDown(OpenTK.Windowing.GraphicsLibraryFramework.MouseButton.Button1) ? Quik.MouseButton.Primary : 0) |
                        (mouseState.IsButtonDown(OpenTK.Windowing.GraphicsLibraryFramework.MouseButton.Button2) ? Quik.MouseButton.Secondary : 0) |
                        (mouseState.IsButtonDown(OpenTK.Windowing.GraphicsLibraryFramework.MouseButton.Button3) ? Quik.MouseButton.Tertiary : 0);

                    root.NotifyMouse(new Quik.MouseState(postion, buttons));
                }

                root.Bounds = new QuikRectangle(
                    window.ClientSize.X,
                    window.ClientSize.Y,
                    0,
                    0);
                root.NotifyUpdate();

                GL.Viewport(0, 0, window.Size.X, window.Size.Y);

                GL.ClearColor(1,1,1,1);
                GL.Clear(ClearBufferMask.ColorBufferBit);
                GL.Enable(EnableCap.Blend);
                GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
                
                Matrix4 matrix = Matrix4.CreateOrthographicOffCenter(
                    0,
                    (float)window.Size.X*dpi/72f,
                    0,
                    (float)window.Size.Y*dpi/72f,
                    1, 
                    -1);
                GL.UniformMatrix4(loc, false, ref matrix);

                context.Draw.Commands.Enqueue(
                    new QuikCommandRectangle(new QuikRectangle(120, 60, 20, 20))
                    {
                        CornerRadius = 0,
                        FillStyle = fill,
                        StrokeStyle = strokeNoBorder
                    });
                
                context.Draw.Commands.Enqueue(
                    new QuikCommandRectangle(new QuikRectangle(240, 60, 140, 20))
                    {
                        CornerRadius = 4,
                        FillStyle = fill,
                        StrokeStyle = strokeNoBorder
                    });
                
                context.Draw.Commands.Enqueue(
                    new QuikCommandRectangle(new QuikRectangle(360, 60, 260, 20))
                    {
                        CornerRadius = 15,
                        FillStyle = fill,
                        StrokeStyle = strokeNoBorder
                    });
                
                context.Draw.Commands.Enqueue(
                    new QuikCommandRectangle(new QuikRectangle(120, 120, 20, 80))
                    {
                        CornerRadius = 0,
                        FillStyle = fill,
                        StrokeStyle = strokeBorder
                    });
                
                context.Draw.Commands.Enqueue(
                    new QuikCommandRectangle(new QuikRectangle(240, 120, 140, 80))
                    {
                        CornerRadius = 4,
                        FillStyle = fill,
                        StrokeStyle = strokeBorder
                    });
                
                context.Draw.Commands.Enqueue(
                    new QuikCommandRectangle(new QuikRectangle(360, 120, 260, 80))
                    {
                        CornerRadius = 15,
                        FillStyle = fill,
                        StrokeStyle = strokeBorder
                    });

                // context.Draw.PutText("これが読めるかな?", new QuikVec2(25,30));
                root.NotifyPaint(context.Draw);

                context.Draw.Commands.Enqueue(new QuikCommandEmitText(typeset, new QuikVec2(200, 200)));

                QuikCommand command;
                while (context.Draw.Commands.TryDequeue(out command))
                {
                    gen.ConsumeCommand(command);
                }
                
                GL.BufferData(BufferTarget.ArrayBuffer, gen.VertexCount * QuikVertex.Stride, ref gen.VertexBuffer[0], BufferUsageHint.StreamDraw);
                GL.BufferData(BufferTarget.ElementArrayBuffer, gen.ElementCount * 2, ref gen.ElementBuffer[0], BufferUsageHint.StreamDraw);
                
                GL.Enable(EnableCap.Blend);
                GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
                foreach (QuikDrawCall call in gen.DrawCalls)
                {
                    GL.BindTexture(
                        TextureTarget.Texture2D,
                        call.Texture == null ? whiteTexture : (call.Texture as OpenGLTexture).TextureId);
                    if (call.Texture != null)
                        GL.Uniform2(offsetLoc, 0.5f / call.Texture.Width, 0.5f / call.Texture.Height);
                    GL.DrawElements(BeginMode.Triangles, call.Count, DrawElementsType.UnsignedShort, call.Offset);
                }
                GL.Disable(EnableCap.Blend);

                int callCount = gen.DrawCalls.Count;
                gen.Clear();
                System.Threading.Thread.Sleep(1);
                window.Context.SwapBuffers();

                frames++;
                float ms = stopwatch.ElapsedMilliseconds; 
                if (ms - lastMs > 1000)
                {
                    Console.WriteLine("Frames: {0}", frames*(ms - lastMs)/1000);
                    frames = 0;
                    lastMs = ms;
                    Console.WriteLine("Vertex Usage: {0} ; Element Usage: {1} Calls: {2}", gen.VertexUsage, gen.ElementUsage, callCount);
                }
            }
        }

        public class TextFontManager : IQuikFontManager
        {
            public QuikContext Context { get; set; }

            private TestFont _font;

            public void Clear()
            {
            }

            public QuikFont GetFont(QuikFontStyle fontStyle)
            {
                if (_font is null)
                {
                    _font = new TestFont(Context);
                }

                return _font;
            }
        }
    }
}