#include <frostbite2D/graphics/camera.h>
#include <algorithm>
#include <cmath>
#include <glm/gtc/matrix_transform.hpp>

namespace frostbite2D {

namespace {

constexpr float kMinZoom = 0.01f;

float clampZoom(float zoom) {
  return std::max(zoom, kMinZoom);
}

Vec2 snapPositionToPixelGrid(const Vec2& position, float zoom) {
  float safeZoom = clampZoom(zoom);
  return Vec2(std::round(position.x * safeZoom) / safeZoom,
              std::round(position.y * safeZoom) / safeZoom);
}

} // namespace

Camera::Camera() : position_(0, 0), zoom_(1.0f) {}

void Camera::setPosition(const Vec2& pos) { position_ = pos; }

void Camera::setZoom(float zoom) { zoom_ = clampZoom(zoom); }

void Camera::setViewport(int width, int height) {
  viewportWidth_ = width;
  viewportHeight_ = height;
}

Vec2 Camera::getRenderPosition() const {
  return snapWorldPosition(position_);
}

Vec2 Camera::snapWorldPosition(const Vec2& position) const {
  if (!pixelSnapEnabled_) {
    return position;
  }
  return snapPositionToPixelGrid(position, zoom_);
}

float Camera::getVisibleWidth() const {
  float safeZoom = clampZoom(zoom_);
  return static_cast<float>(viewportWidth_) / safeZoom;
}

float Camera::getVisibleHeight() const {
  float safeZoom = clampZoom(zoom_);
  return static_cast<float>(viewportHeight_) / safeZoom;
}

void Camera::lookAt(const Vec2& target) { position_ = target; }

void Camera::move(const Vec2& delta) { position_ = position_ + delta; }

void Camera::zoomAt(float factor, const Vec2& screenPos) {
  float previousZoom = clampZoom(zoom_);
  Vec2 worldBefore(position_.x + screenPos.x / previousZoom,
                   position_.y + screenPos.y / previousZoom);
  zoom_ = clampZoom(zoom_ * factor);
  Vec2 worldAfter(position_.x + screenPos.x / zoom_,
                  position_.y + screenPos.y / zoom_);
  position_ = position_ + (worldBefore - worldAfter);
}

glm::mat4 Camera::getViewMatrix() const {
  float safeZoom = clampZoom(zoom_);
  Vec2 renderPosition = getRenderPosition();

  glm::mat4 view = glm::mat4(1.0f);
  view = glm::scale(view, glm::vec3(safeZoom, safeZoom, 1.0f));
  view = glm::translate(view,
                        glm::vec3(-renderPosition.x, -renderPosition.y, 0.0f));
  return view;
}

glm::mat4 Camera::getProjectionMatrix() const {
  float left = 0.0f;
  float right = static_cast<float>(viewportWidth_);
  float bottom, top;

  if (flipY_) {
    bottom = static_cast<float>(viewportHeight_);
    top = 0.0f;
  } else {
    bottom = 0.0f;
    top = static_cast<float>(viewportHeight_);
  }

  return glm::ortho(left, right, bottom, top, -1.0f, 1.0f);
}

} // namespace frostbite2D
