#include "camera_2d.h"

#include <cmath>

namespace fn::cam {

using fn::math2d::Vec2;
using fn::math2d::Rect;

Vec2 world_to_screen(const Camera2D& c, Vec2 world) {
    float dx = world.x - c.pos.x;
    float dy = world.y - c.pos.y;

    if (c.rotation != 0.0f) {
        float cs = std::cos(-c.rotation);
        float sn = std::sin(-c.rotation);
        float rx = dx * cs - dy * sn;
        float ry = dx * sn + dy * cs;
        dx = rx;
        dy = ry;
    }

    float sx = dx * c.zoom + (float)c.viewport_w * 0.5f;
    float sy = dy * c.zoom + (float)c.viewport_h * 0.5f;
    return {sx, sy};
}

Vec2 screen_to_world(const Camera2D& c, Vec2 screen) {
    float dx = (screen.x - (float)c.viewport_w * 0.5f) / c.zoom;
    float dy = (screen.y - (float)c.viewport_h * 0.5f) / c.zoom;

    if (c.rotation != 0.0f) {
        float cs = std::cos(c.rotation);
        float sn = std::sin(c.rotation);
        float rx = dx * cs - dy * sn;
        float ry = dx * sn + dy * cs;
        dx = rx;
        dy = ry;
    }

    return {c.pos.x + dx, c.pos.y + dy};
}

Rect visible_world_rect(const Camera2D& c) {
    // For rotated cameras, return the AABB that contains the rotated viewport.
    float hw = (float)c.viewport_w * 0.5f / c.zoom;
    float hh = (float)c.viewport_h * 0.5f / c.zoom;

    if (c.rotation == 0.0f) {
        return {c.pos.x - hw, c.pos.y - hh, hw * 2.0f, hh * 2.0f};
    }

    float cs = std::fabs(std::cos(c.rotation));
    float sn = std::fabs(std::sin(c.rotation));
    float ehw = hw * cs + hh * sn;
    float ehh = hw * sn + hh * cs;
    return {c.pos.x - ehw, c.pos.y - ehh, ehw * 2.0f, ehh * 2.0f};
}

void view_proj_matrix(const Camera2D& c, float out[16]) {
    // Orthographic projection mapping a viewport-sized box around camera pos
    // to clip space [-1, 1].
    float hw = (float)c.viewport_w * 0.5f / c.zoom;
    float hh = (float)c.viewport_h * 0.5f / c.zoom;

    float l = c.pos.x - hw;
    float r = c.pos.x + hw;
    // Screen Y goes down, world Y can go either; we pick world-Y-up convention:
    // top of screen = pos.y + hh, bottom = pos.y - hh.
    float b = c.pos.y - hh;
    float t = c.pos.y + hh;

    float cs = std::cos(-c.rotation);
    float sn = std::sin(-c.rotation);

    // Build column-major:
    // M = Ortho(l,r,b,t) * Rotate(-rotation around camera center)
    // Compose by hand to avoid temporaries.

    float ortho[16] = {
        2.0f / (r - l), 0.0f,           0.0f, 0.0f,
        0.0f,           2.0f / (t - b), 0.0f, 0.0f,
        0.0f,           0.0f,          -1.0f, 0.0f,
        -(r + l) / (r - l), -(t + b) / (t - b), 0.0f, 1.0f,
    };

    if (c.rotation == 0.0f) {
        for (int i = 0; i < 16; ++i) out[i] = ortho[i];
        return;
    }

    // Rotation around camera pos in world space:
    // T(pos) * R * T(-pos)
    // We bake it as a column-major 4x4 then multiply: out = ortho * rot.
    float px = c.pos.x;
    float py = c.pos.y;

    float rot[16] = {
         cs,  sn, 0.0f, 0.0f,
        -sn,  cs, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 0.0f,
        px - cs * px + sn * py,
        py - sn * px - cs * py,
        0.0f, 1.0f,
    };

    // out = ortho * rot (column-major).
    for (int col = 0; col < 4; ++col) {
        for (int row = 0; row < 4; ++row) {
            float sum = 0.0f;
            for (int k = 0; k < 4; ++k) {
                sum += ortho[k * 4 + row] * rot[col * 4 + k];
            }
            out[col * 4 + row] = sum;
        }
    }
}

}  // namespace fn::cam
