using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;

namespace OpenTK_tutorial
{
    public class Game : GameWindow
    {
        float azimuth, zenith;
        float zMove;

        Matrix4 projection;
        Matrix4 view;

        LightShader simpleShader;
        FlatShader flatShader;
        FlatTexShader flatTexShader;
        FrameBufferShader frameBufferShader;

        GameObject obj;
        GameObject armadillo;
        GameObject plane;

        FrameBuffer firstPass;
        FrameBuffer secondPass;

        public Game(int width, int height, string title) : base(GameWindowSettings.Default, new NativeWindowSettings() { ClientSize = (width, height), Title = title })
        {
        }

        protected override void OnLoad()
        {
            base.OnLoad();

            GL.ClearColor(0.22f, 0.22f, 0.22f, 1.0f);
            GL.Enable(EnableCap.DepthTest);

            //Code goes here
            obj = new Cube(new Vector3(1f, 1f, 0), new Vector3(0,0,0), 1);
            obj.Initialize();

            plane = new Plane();
            plane.Initialize();

            armadillo = new MeshObject(1f, "Models/model.x", "Models/modelcolormap.png");
            // armadillo = new MeshObject(0.05f, "Models/utah.x", "Models/utahColor.png");
            //armadillo = new MeshObject(0.3f, "Models/spaceship.x", "Models/spaceship.png");
            armadillo.Initialize();

            simpleShader = new LightShader("simple_shader.vert", "simple_shader.frag");
            flatShader = new FlatShader("flat_shader.vert", "flat_shader.frag");
            flatTexShader = new FlatTexShader("flatTex_shader.vert", "flatTex_shader.frag");
            frameBufferShader = new FrameBufferShader("frameBuffer_shader.vert", "frameBuffer_shader.frag");

            projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(90.0f), (float)ClientSize.X / (float)ClientSize.Y, 0.1f, 100.0f);
            zMove = -2;

            firstPass = new FrameBuffer(ClientSize.X, ClientSize.Y);
            secondPass = new FrameBuffer(ClientSize.X, ClientSize.Y);
        }

        protected override void OnFramebufferResize(FramebufferResizeEventArgs e)
        {
            base.OnFramebufferResize(e);
            GL.Viewport(0, 0, e.Width, e.Height);

            float ratio = (float)ClientSize.X / (float)ClientSize.Y;
            projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(90.0f), ratio, 0.1f, 100.0f);

            firstPass = new FrameBuffer(ClientSize.X, ClientSize.Y);
            secondPass = new FrameBuffer(ClientSize.X, ClientSize.Y);
        }

        protected override void OnUpdateFrame(FrameEventArgs args)
        {
            base.OnUpdateFrame(args);

            if (KeyboardState.IsKeyDown(Keys.Escape))
            {
                simpleShader.Dispose();
                flatShader.Dispose();
                Close();
            }

            if (MouseState.IsButtonDown(MouseButton.Left))
            {
                azimuth += MouseState.X - MouseState.PreviousX;
                zenith += MouseState.Y - MouseState.PreviousY;

                armadillo.SetRotation(new Vector3(zenith, -azimuth,0));
            }

            Vector2 scroll = MouseState.Scroll - MouseState.PreviousScroll;
            zMove += 0.1f * scroll.Y; 
        }

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            view = Matrix4.CreateTranslation(0.0f, 0.0f, zMove);

            //Code goes here.

            RenderFlatScene();
            RenderScene();
            RenderDisplayPlane();

            SwapBuffers();
        }

        private void RenderFlatScene()
        {
            // set first pass framebuffer
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, firstPass.ID);
            GL.FramebufferTexture(FramebufferTarget.Framebuffer,
                                  FramebufferAttachment.DepthAttachment,
                                  firstPass.depthBuffer, 0);

            // clear buffers
            GL.ClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            // render objects to outline
            flatTexShader.Use();
            flatTexShader.SetUniforms(view, projection);
            armadillo.Draw(flatTexShader);

            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
        }

        private void RenderScene()
        {
            // TODO set second pass framebuffer
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, secondPass.ID);
            GL.FramebufferTexture(FramebufferTarget.Framebuffer,
                                  FramebufferAttachment.ColorAttachment0,
                                  secondPass.colorBuffer, 0);

            GL.ClearColor(0.22f, 0.22f, 0.22f, 1.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            // render objects
            simpleShader.Use();
            simpleShader.SetUniforms(view, projection, new Vector3(1, 1, 0), obj.translation, new Vector3(0, 0, zMove));
            armadillo.Draw(simpleShader);

            flatShader.Use();
            flatShader.SetUniforms(view, projection, new Vector3(1, 1, 0));
            obj.Draw(flatShader);

            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
        }

        private void RenderDisplayPlane()
        {
            // TODO

            // clear buffers
            GL.ClearColor(0.22f, 0.22f, 0.22f, 1.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            // render data from framebuffers to plane

            // set textures
            GL.Enable(EnableCap.Texture2D);
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, firstPass.depthBuffer);
            GL.ActiveTexture(TextureUnit.Texture1);
            GL.BindTexture(TextureTarget.Texture2D, secondPass.colorBuffer);

            // draw the plane
            frameBufferShader.Use();
            plane.Draw(frameBufferShader);

            GL.Disable(EnableCap.Texture2D);
        }
    }
}