using GLFW;
using Silk.NET.OpenGL;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;

namespace _2DGAMELIB
{
    public enum MouseButtons { 
    	None = 0,
    	Left = 1,
    	Right = 2,
    	Middle = 4,
    	Button4 = 8,
    	Button5 = 16
    }

    public class GlImage
    {

        Silk.NET.OpenGL.GL gl;

    	//yeah this is a little bit sketchy
        public static unsafe GLFW.Window PtrToWindow(IntPtr source)
        {
            var sourceRef = __makeref(source);
            var dest = default(GLFW.Window);
            var destRef = __makeref(dest);
            *(IntPtr*)&destRef = *(IntPtr*)&sourceRef;
            return __refvalue(destRef, GLFW.Window);
        }

        public GLFW.Window window;
    	private uint shader_program;
    	private uint texture;
    	private uint vertex_buf;
    	private uint vao;
        public int texW = 0;
        public int texH = 0;

        public GlImage() { }

        public void SetViewport(uint vpW, uint vpH, int vpX, int vpY)
        {
            gl.Viewport(vpX, vpY, vpW, vpH);
        }

    	public Vector2D GetCursorPoint() {
    		double x, y;
    		Glfw.GetCursorPosition(window, out x, out y);
    		return new Vector2D(x, y);
    	}

        public void SetCursorPoint(Vector2D pos) {
            Glfw.SetCursorPosition(window, pos.X, pos.Y);
        }

    	public MouseButtons GetMouseButtons() {
    		MouseButtons btns = 0;

    		if (Glfw.GetMouseButton(window, MouseButton.Left) == InputState.Press)
    			btns |= MouseButtons.Left;

            if (Glfw.GetMouseButton(window, MouseButton.Right) == InputState.Press)
                btns |= MouseButtons.Right;

            if (Glfw.GetMouseButton(window, MouseButton.Middle) == InputState.Press)
                btns |= MouseButtons.Middle;

            if (Glfw.GetMouseButton(window, MouseButton.Button4) == InputState.Press)
                btns |= MouseButtons.Button4;

            if (Glfw.GetMouseButton(window, MouseButton.Button5) == InputState.Press)
                btns |= MouseButtons.Button5;

    		return btns;
        }

    	public delegate void ShouldCloseCallback();

    	public ShouldCloseCallback Closing = delegate () { };
    	public MouseButtonCallback Click = delegate (IntPtr window, MouseButton button, InputState state, ModifierKeys modifiers) { };
    	public MouseCallback Move = delegate (IntPtr window, double x, double y) { };
    	public SizeCallback Resize = delegate (IntPtr window, int width, int height) { };
    	public MouseCallback Scroll = delegate (IntPtr window, double x, double y) { };
    	public MouseEnterCallback Leave = delegate (IntPtr window, bool entered) { };

    	public void SetTitle(string title) {
    		Glfw.SetWindowTitle(window, title);
    	}

        public void PollEvents() {
    		Glfw.PollEvents();
    		if (Glfw.WindowShouldClose(window))
    			Closing();
    	} 
        public unsafe void SetBitmap(Bitmap bmp)
        {
            gl.UseProgram(shader_program);
            gl.ActiveTexture(Silk.NET.OpenGL.GLEnum.Texture0);
            gl.BindTexture(Silk.NET.OpenGL.GLEnum.Texture2D, texture);

            if (bmp.Width != texW || bmp.Height != texH)
            {
                gl.TexImage2D(
                Silk.NET.OpenGL.GLEnum.Texture2D,
                0,
                InternalFormat.Rgba8,
                (uint)bmp.Width,
                (uint)bmp.Height,
                0,
                Silk.NET.OpenGL.GLEnum.Bgra,
                Silk.NET.OpenGL.GLEnum.UnsignedByte,
                null);

                texW = bmp.Width;
                texH = bmp.Height;
            }

            BitmapData data = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            gl.PixelStore(GLEnum.UnpackAlignment, 1);

            gl.TexSubImage2D(
                Silk.NET.OpenGL.GLEnum.Texture2D,
                0,
                0,
                0,
                (uint)bmp.Width,
                (uint)bmp.Height,
                Silk.NET.OpenGL.GLEnum.Bgra,
                Silk.NET.OpenGL.GLEnum.UnsignedByte,
                (void*)data.Scan0);

            bmp.UnlockBits(data);

            gl.BindVertexArray(vao);
            gl.DrawArrays(GLEnum.TriangleStrip, 0, 4);
            gl.BindVertexArray(0);

            Glfw.SwapBuffers(window);
        }

    	public unsafe void BitmapSetting(Bitmap bmp)
    	{
    		Glfw.WindowHint(Hint.ClientApi, ClientApi.OpenGL);
    		Glfw.WindowHint(Hint.ContextVersionMajor, 3);
    		Glfw.WindowHint(Hint.ContextVersionMinor, 3);
    		Glfw.WindowHint(Hint.OpenglProfile, Profile.Core);
    		Glfw.WindowHint(Hint.Doublebuffer, true);
    		Glfw.WindowHint(Hint.Decorated, true);
    		Glfw.WindowHint(Hint.OpenglForwardCompatible, true);

    		window = Glfw.CreateWindow(bmp.Width, bmp.Height, "yayy", Monitor.None, GLFW.Window.None);

            Glfw.SetWindowSizeCallback(window, Resize);
            Glfw.SetMouseButtonCallback(window, Click);
            Glfw.SetCursorPositionCallback(window, Move);
    		Glfw.SetScrollCallback(window, Scroll);
    		Glfw.SetCursorEnterCallback(window, Leave);

    		GCHandle handle = GCHandle.Alloc(this);
    		Glfw.SetWindowUserPointer(window, GCHandle.ToIntPtr(handle));

            Glfw.MakeContextCurrent(window);
            Glfw.SwapInterval(0);
            gl = Silk.NET.OpenGL.GL.GetApi(Glfw.GetProcAddress);


    		string vertexShaderSource =
    @"
#version 330 core

layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aUV;

out vec2 vUV;

void main()
{
    gl_Position = vec4(aPos, 0.0f, 1.0f);
    vUV = aUV;
}
";

            string fragmentShaderSource =
    @"
#version 330 core

in vec2 vUV;

out vec4 FragColor;

uniform sampler2D sTexture;

void main()
{
    FragColor = texture(sTexture, vUV);
}
";

            uint vertexShader = gl.CreateShader(Silk.NET.OpenGL.GLEnum.VertexShader);
            gl.ShaderSource(vertexShader, vertexShaderSource);
            gl.CompileShader(vertexShader);

            uint fragmentShader = gl.CreateShader(Silk.NET.OpenGL.GLEnum.FragmentShader);
            gl.ShaderSource(fragmentShader, fragmentShaderSource);
            gl.CompileShader(fragmentShader);

            shader_program = gl.CreateProgram();
            gl.AttachShader(shader_program, vertexShader);
            gl.AttachShader(shader_program, fragmentShader);
            gl.LinkProgram(shader_program);

            
            System.Diagnostics.Debug.WriteLine(gl.GetShaderInfoLog(fragmentShader));
            System.Diagnostics.Debug.WriteLine(gl.GetShaderInfoLog(vertexShader));
            System.Diagnostics.Debug.WriteLine(gl.GetProgramInfoLog(shader_program));

            gl.UseProgram(shader_program);
            gl.Uniform1(gl.GetUniformLocation(shader_program, "sTexture"), 0);

            texture = gl.GenTexture();

            gl.ActiveTexture(Silk.NET.OpenGL.GLEnum.Texture0);
            gl.BindTexture(Silk.NET.OpenGL.GLEnum.Texture2D, texture);

            gl.TexParameterI(Silk.NET.OpenGL.GLEnum.Texture2D, Silk.NET.OpenGL.GLEnum.TextureWrapS, new int[] {(int)TextureWrapMode.ClampToEdge});
            gl.TexParameterI(Silk.NET.OpenGL.GLEnum.Texture2D, Silk.NET.OpenGL.GLEnum.TextureWrapT, new int[] {(int)TextureWrapMode.ClampToEdge});
    		gl.TexParameterI(Silk.NET.OpenGL.GLEnum.Texture2D, Silk.NET.OpenGL.GLEnum.TextureMagFilter, new int[] {(int)TextureMagFilter.Nearest});
    		gl.TexParameterI(Silk.NET.OpenGL.GLEnum.Texture2D, Silk.NET.OpenGL.GLEnum.TextureMinFilter, new int[] {(int)TextureMinFilter.Nearest});

            gl.TexImage2D(
                Silk.NET.OpenGL.GLEnum.Texture2D,
                0,
                InternalFormat.Rgba8,
                (uint)bmp.Width,
                (uint)bmp.Height,
                0,
                Silk.NET.OpenGL.GLEnum.Bgra,
                Silk.NET.OpenGL.GLEnum.UnsignedByte,
                null
            );

            texW = bmp.Width;
            texH = bmp.Height;

            float[] buf = {
                -1.0f, -1.0f, 0.0f, 1.0f,
                1.0f, -1.0f, 1.0f, 1.0f,
                -1.0f, 1.0f, 0.0f, 0.0f,
                1.0f, 1.0f, 1.0f, 0.0f
            };

            vertex_buf = gl.GenBuffer();
            gl.BindBuffer(Silk.NET.OpenGL.GLEnum.ArrayBuffer, vertex_buf);
            fixed (float* buf_ = buf) gl.BufferData(Silk.NET.OpenGL.GLEnum.ArrayBuffer, (uint)(sizeof(float) * buf.Length), buf_, Silk.NET.OpenGL.GLEnum.StaticDraw);

    		vao = gl.GenVertexArray();

            gl.BindVertexArray(vao);
            gl.BindBuffer(GLEnum.ArrayBuffer, vertex_buf);


            gl.VertexAttribPointer(
                0,
                2,
                GLEnum.Float,
                false,
                4 * sizeof(float),
                IntPtr.Zero
            );

            gl.EnableVertexAttribArray(0);

            gl.VertexAttribPointer(
                1,
                2,
                GLEnum.Float,
                false,
                4 * sizeof(float),
                (void*)(2 * sizeof(float))
            );

            gl.EnableVertexAttribArray(1);

            gl.BindVertexArray(0);
        }
    }
}