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

namespace Map3DRendering {
    public class Window : GameWindow {

        private readonly Vector3 _lightPos = new Vector3(0x10, 0, 0x10);

        private MapRender mapRender;
        private AxesGizmo axesGizmo;

        private Shader _shader;

        private Texture _texture;

        private Camera _camera;

        private bool _firstMove = true;

        private Vector2 _lastPos;

        private double _time;

        private float tileSize = 64;
        private float cameraSpeed = 50f;
        private int maxTextures;

        public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
            : base(gameWindowSettings, nativeWindowSettings) {

            mapRender = new MapRender();

            GL.GetInteger(GetPName.MaxTextureImageUnits, out maxTextures);
        }

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

            GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);

            GL.Enable(EnableCap.DepthTest);

            _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
            _shader.Use();

            mapRender.OnLoad(_shader);
            //_texture = Texture.LoadFromFile("atlas.jpg");
            //_texture.Use(TextureUnit.Texture0);

            axesGizmo = new AxesGizmo();

            _camera = new Camera(Vector3.UnitY * 300, Size.X / (float)Size.Y);
            _camera.Fov = 60;
            //CursorState = CursorState.Grabbed;
        }

        protected override void OnRenderFrame(FrameEventArgs e) {
            base.OnRenderFrame(e);

            _time += 4.0 * e.Time;

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            _shader.Use();
            //_texture.Use(TextureUnit.Texture0);

            _shader.SetMatrix4("view", _camera.GetViewMatrix());
            _shader.SetMatrix4("projection", _camera.GetProjectionMatrix());

            /*_shader.SetVector3("objectColor", new Vector3(0.5f, 0.5f, 0.5f));
            _shader.SetVector3("lightColor", new Vector3(1.0f, 1.0f, 1.0f));
            _shader.SetVector3("lightPos", _camera.Position);
            _shader.SetVector3("viewPos", _camera.Position);*/

            GL.LineWidth(5.0f);

            _shader.SetVector3("viewPos", _camera.Position);
            mapRender.UpdateBlocks(_camera.Position, _shader);
            mapRender.Render(_shader);

            axesGizmo.Render(Size.X, Size.Y, _camera);

            SwapBuffers();
        }

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

            if (!IsFocused) // Check to see if the window is focused
            {
                return;
            }

            var input = KeyboardState;

            if (input.IsKeyDown(Keys.Escape)) {
                Close();
            }

            const float sensitivity = 0.2f;

            if (input.IsKeyDown(Keys.W)) {
                _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
            }

            if (input.IsKeyDown(Keys.S)) {
                _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
            }
            if (input.IsKeyDown(Keys.A)) {
                _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
            }
            if (input.IsKeyDown(Keys.D)) {
                _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
            }
            if (input.IsKeyDown(Keys.Space)) {
                _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
            }
            if (input.IsKeyDown(Keys.LeftShift)) {
                _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
            }

            // Get the mouse state
            var mouse = MouseState;

            // Check if the right mouse button is pressed
            if (mouse.IsButtonDown(MouseButton.Right)) {
                CursorState = CursorState.Grabbed;
                if (_firstMove) // This bool variable is initially set to true.
                {
                    _lastPos = new Vector2(mouse.X, mouse.Y);
                    _firstMove = false;
                } else {
                    // Calculate the offset of the mouse position
                    var deltaX = mouse.X - _lastPos.X;
                    var deltaY = mouse.Y - _lastPos.Y;
                    _lastPos = new Vector2(mouse.X, mouse.Y);

                    // Apply the camera pitch and yaw (we clamp the pitch in the camera class)
                    _camera.Yaw += deltaX * sensitivity;
                    _camera.Pitch -= deltaY * sensitivity; // Reversed since y-coordinates range from bottom to top
                }
            } else {
                _firstMove = true;
                CursorState = CursorState.Normal;
            }
        }

        // In the mouse wheel function, we manage all the zooming of the camera.
        // This is simply done by changing the FOV of the camera.
        protected override void OnMouseWheel(MouseWheelEventArgs e) {
            base.OnMouseWheel(e);

            cameraSpeed -= e.OffsetY * 2f;
        }

        protected override void OnResize(ResizeEventArgs e) {
            base.OnResize(e);

            GL.Viewport(0, 0, Size.X, Size.Y);
            // We need to update the aspect ratio once the window has been resized.
            _camera.AspectRatio = Size.X / (float)Size.Y;
        }
    }
}
