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

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

        Matrix4 projection;
        Matrix4 view;

        GameObject plane;
        GameObject volumeBox;

        ShaderProgram colorShader;
        VolumeShader volumeShader;
        Texture3D headVolumeData;

        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
            plane = new Plane();
            plane.Initialize();
            volumeBox = new VolumeBox(new Vector3(0, 0, 0), new Vector3(), 1);
            volumeBox.Initialize();

            volumeShader = new VolumeShader("shaders/volume_shader.vert", "shaders/volume_shader.frag");
            colorShader = new ShaderProgram("shaders/color_shader.vert", "shaders/color_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);

            headVolumeData = new Texture3D("Models/head256x256x109", 256, 256, 109);
        }

        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))
            {
                volumeShader.Dispose();
                colorShader.Dispose();
                Close();
            }

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

                volumeBox.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.
            RenderFrontFaces();
            RenderBackFaces();
            RenderDisplayPlane();

            SwapBuffers();
        }

        // Render from faces of the volume box
        private void RenderFrontFaces()
        {
            // Render front faces to firstPass.colorBuffer
            // render volumeBox, using colorShader
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, firstPass.ID);
            GL.FramebufferTexture(FramebufferTarget.Framebuffer,
                                  FramebufferAttachment.ColorAttachment0,
                                  firstPass.colorBuffer, 0);

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
            GL.Viewport(0, 0, ClientSize.X, ClientSize.Y);

            colorShader.Use();
            colorShader.SetUniforms(view, projection);
            volumeBox.Draw(colorShader);

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

        // Render back faces of the volume box
        private void RenderBackFaces()
        {
            // Render back faces to secondPass.colorBuffer
            // render volumeBox, using colorShader
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, secondPass.ID);
            GL.FramebufferTexture(FramebufferTarget.Framebuffer,
                                  FramebufferAttachment.ColorAttachment0,
                                  secondPass.colorBuffer, 0);

            GL.Clear(ClearBufferMask.ColorBufferBit |
                     ClearBufferMask.DepthBufferBit |
                     ClearBufferMask.StencilBufferBit);
            GL.Viewport(0, 0, ClientSize.X, ClientSize.Y);

            GL.Enable(EnableCap.CullFace);
            GL.CullFace(CullFaceMode.Front);
            colorShader.Use();
            colorShader.SetUniforms(view, projection);
            volumeBox.Draw(colorShader);
            GL.Disable(EnableCap.CullFace);

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

        /// <summary>
        /// Render the volume
        /// </summary>
        private void RenderDisplayPlane()
        {
            // clear buffers
            GL.ClearColor(0.22f, 0.22f, 0.22f, 1.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            // render data from framebuffers
            // set textures: 0 - front, 1 - back, 2 - volume
            // render plane using volumeShader

            GL.Enable(EnableCap.Texture2D);
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, firstPass.colorBuffer);
            GL.ActiveTexture(TextureUnit.Texture1);
            GL.BindTexture(TextureTarget.Texture2D, secondPass.colorBuffer);
            GL.ActiveTexture(TextureUnit.Texture2);
            GL.BindTexture(TextureTarget.Texture3D, headVolumeData.ID);

            volumeShader.Use();
            volumeShader.SetUniforms(view, projection);
            plane.Draw(volumeShader);

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