#include "camera.hpp"

#include "../util/constants.hpp"

using namespace constants;

Camera::Camera()
  : mPosition(2.0, 2.0, 0.5),
    mFront(glm::normalize(glm::dvec3(0.0, 1.0, 0.0))),
    mUp(0.0, 0.0, 1.0),
    mRight(glm::normalize(glm::cross(mFront, glm::dvec3(0.0, 0.0, 1.0)))),
    mYaw(180.0),
    mPitch(0.0) {
  updateCameraVectors();
}

fn Camera::getPosition() const -> glm::dvec3 { return mPosition; }

fn Camera::getFront() const -> glm::dvec3 { return mFront; }

fn Camera::getYaw() const -> f64 { return mYaw; }

fn Camera::getPitch() const -> f64 { return mPitch; }

fn Camera::getViewMatrix() const -> glm::mat4 { return glm::lookAt(mPosition, mPosition + mFront, mUp); }

fn Camera::moveForward(f64 deltaTime) -> void {
  // Project front vector onto horizontal plane by zeroing Z component
  glm::dvec3 horizontalFront = mFront;
  horizontalFront.z          = 0.0;
  horizontalFront            = glm::normalize(horizontalFront);
  mPosition += horizontalFront * CAMERA_SPEED * deltaTime;
}

fn Camera::moveBackward(f64 deltaTime) -> void {
  // Project front vector onto horizontal plane by zeroing Z component
  glm::dvec3 horizontalFront = mFront;
  horizontalFront.z          = 0.0;
  horizontalFront            = glm::normalize(horizontalFront);
  mPosition -= horizontalFront * CAMERA_SPEED * deltaTime;
}

fn Camera::moveLeft(f64 deltaTime) -> void {
  // Project right vector onto horizontal plane by zeroing Z component
  glm::dvec3 horizontalRight = mRight;
  horizontalRight.z          = 0.0;
  horizontalRight            = glm::normalize(horizontalRight);
  mPosition -= horizontalRight * CAMERA_SPEED * deltaTime;
}

fn Camera::moveRight(f64 deltaTime) -> void {
  // Project right vector onto horizontal plane by zeroing Z component
  glm::dvec3 horizontalRight = mRight;
  horizontalRight.z          = 0.0;
  horizontalRight            = glm::normalize(horizontalRight);
  mPosition += horizontalRight * CAMERA_SPEED * deltaTime;
}

fn Camera::moveUp(f64 deltaTime) -> void {
  mPosition += glm::dvec3(0.0, 0.0, 1.0) * CAMERA_SPEED * deltaTime;
}

fn Camera::moveDown(f64 deltaTime) -> void {
  mPosition -= glm::dvec3(0.0, 0.0, 1.0) * CAMERA_SPEED * deltaTime;
}

fn Camera::rotate(f64 xoffset, double yoffset) -> void {
  const f64 sensitivity = 0.1;
  mYaw += xoffset * sensitivity;
  mPitch += yoffset * sensitivity;

  // Clamp yaw to [-180, 180] range
  if (mYaw > 180.0)
    mYaw -= 360.0;
  if (mYaw < -180.0)
    mYaw += 360.0;

  // Constrain pitch to avoid camera flipping
  if (mPitch > 89.0)
    mPitch = 89.0;
  if (mPitch < -89.0)
    mPitch = -89.0;

  updateCameraVectors();
}

fn Camera::updateCameraVectors() -> void {
  // Calculate new front vector
  glm::dvec3 newFront;
  newFront.x = cos(glm::radians(mYaw)) * cos(glm::radians(mPitch));
  newFront.y = sin(glm::radians(mYaw)) * cos(glm::radians(mPitch));
  newFront.z = sin(glm::radians(mPitch));

  mFront = glm::normalize(newFront);
  // Recalculate right and up vectors
  mRight = glm::normalize(glm::cross(mFront, glm::dvec3(0.0, 0.0, 1.0)));
  mUp    = glm::normalize(glm::cross(mRight, mFront));
}
