#ifndef CAMERA_H
#define CAMERA_H

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include "World.h"

const float CAMERA_HEIGHT = 0.0f;
const float GRAVITY = 18.6f; // Should be in m/s^2 but it doesn't really work out with 9.8 so we double it

class Camera
{
  public:
    glm::vec3 position;
    glm::vec3 target;
    glm::vec3 direction;
    glm::vec3 up;

    // Movement
    glm::vec3 horizontal_velocity;
    glm::vec3 forward_velocity;
    glm::vec3 vertical_velocity;

    float speed;

    glm::vec3 worldUp;

    Camera(float speed, glm::vec3 pos)
    {
        this->speed = speed;
        this->vertical_velocity = glm::vec3(0.0f);

        this->horizontal_velocity = glm::vec3(0.0f);
        this->forward_velocity = glm::vec3(0.0f);

        this->position = pos;
        this->target = glm::vec3(0.0f, 0.0f, -1.0f);

        this->direction = glm::normalize(this->position);
        this->worldUp = glm::vec3(0.0f, 1.0f, 0.0f);

        glm::vec3 right = glm::normalize(glm::cross(up, this->direction));
        this->up = glm::cross(this->direction, right);
    }

    Camera(glm::vec3 pos, glm::vec3 target, float speed)
    {
        this->speed = speed;
        this->vertical_velocity = glm::vec3(0.0f);

        this->horizontal_velocity = glm::vec3(0.0f);
        this->forward_velocity = glm::vec3(0.0f);

        this->position = pos;
        this->target = target;

        this->direction = glm::normalize(this->position - this->target);
        this->worldUp = glm::vec3(0.0f, 1.0f, 0.0f);

        glm::vec3 right = glm::normalize(glm::cross(up, this->direction));
        this->up = glm::cross(this->direction, right);
    }

    glm::mat4 getView()
    {
        glm::vec3 real_camera_pos = this->position;
        real_camera_pos.y += CAMERA_HEIGHT;

        glm::mat4 v = glm::lookAt(real_camera_pos,
                           real_camera_pos + this->target,
                           this->worldUp);

        return v;
    }

    void move(World* world, float time) // time passed in seconds since last move
    {
        // bool in_air = this->position.y > 1;

        // if (this->position.y > 1)
        //     this->vertical_velocity.y -= (GRAVITY * time); // no way! physics reference?

        glm::vec3 velocity = this->forward_velocity + this->horizontal_velocity + this->vertical_velocity;

        glm::vec3 position_delta = (velocity * time);

        // glm::vec3 new_position = this->position + position_delta;

        // AABB Algorithm
        glm::vec3 aabb_dims = glm::vec3(0.8f, 1.8f, 0.8f);
        glm::vec3 aabb_half_dims = aabb_dims / 2.0f;

        glm::vec3 aabb_center_offset = glm::vec3(0.0f, aabb_dims.y / 2.0f, 0.0f);

        bool y_axis_collided = false;

        for (int axis = 0; axis < 3; axis++) {
            glm::vec3 target_position = this->position;
            target_position[axis] += position_delta[axis];

            glm::vec3 aabb_center = target_position + aabb_center_offset;
            glm::vec3 min = glm::floor(aabb_center - aabb_half_dims);
            glm::vec3 max = glm::ceil(aabb_center + aabb_half_dims);

            bool collided = false;
            for (int x = min.x; x < max.x && !collided; x++) {
                for (int y = min.y; y < max.y && !collided; y++) {
                    for (int z = min.z; z < max.z && !collided; z++) {
                        glm::ivec3 calculated_position = glm::ivec3(x, y, z);
                        if (world->get_voxel(calculated_position).has_value()) {
                            collided = true;
                            // std::cout << "Collided!!" << std::endl;
                        }
                    }
                }
            }

            if (!collided) {
                this->position[axis] = target_position[axis];
            } else if (axis == 1) {
                y_axis_collided = true;
                this->vertical_velocity.y = 0.0f;
            }
        }

        if (!y_axis_collided) {
            this->vertical_velocity.y -= GRAVITY * time;
        }



        // this->position += movement;

        // Don't want to clip into the ground right
        // if (this->position.y <= 1 && in_air)
        // {
        //     this->position.y = 0.0f;
        //     this->vertical_velocity.y = 0.0f;
        // }

    }

    glm::vec3 getEyePosition() const {
        return position + glm::vec3(0.0f, CAMERA_HEIGHT, 0.0f);
    }
};

#endif



