#pragma once

#include <cmath>
#include <cstring>

struct Vec3 {
    float x, y, z;
};

struct Mat4 {
    float m[16];
};

inline Mat4 mat4Identity() {
    Mat4 r{};
    r.m[0] = 1.0f;
    r.m[5] = 1.0f;
    r.m[10] = 1.0f;
    r.m[15] = 1.0f;
    return r;
}

inline Mat4 mat4Multiply(const Mat4& a, const Mat4& b) {
    Mat4 r{};
    for (int row = 0; row < 4; ++row) {
        for (int col = 0; col < 4; ++col) {
            r.m[row * 4 + col] =
                a.m[row * 4 + 0] * b.m[0 * 4 + col] +
                a.m[row * 4 + 1] * b.m[1 * 4 + col] +
                a.m[row * 4 + 2] * b.m[2 * 4 + col] +
                a.m[row * 4 + 3] * b.m[3 * 4 + col];
        }
    }
    return r;
}

inline Mat4 mat4Translation(float x, float y, float z) {
    Mat4 r = mat4Identity();
    r.m[12] = x;
    r.m[13] = y;
    r.m[14] = z;
    return r;
}

inline Mat4 mat4RotationY(float a) {
    Mat4 r = mat4Identity();
    float c = std::cos(a);
    float s = std::sin(a);
    r.m[0] = c;
    r.m[2] = s;
    r.m[8] = -s;
    r.m[10] = c;
    return r;
}

inline Mat4 mat4Perspective(float fovRad, float aspect, float zNear, float zFar) {
    Mat4 r{};
    float tanHalf = std::tan(fovRad / 2.0f);

    r.m[0] = 1.0f / (aspect * tanHalf);
    r.m[5] = 1.0f / tanHalf;
    r.m[10] = -(zFar + zNear) / (zFar - zNear);
    r.m[11] = -1.0f;
    r.m[14] = -(2.0f * zFar * zNear) / (zFar - zNear);
    return r;
}

inline Mat4 mat4LookAt(Vec3 eye, Vec3 center, Vec3 up) {
    auto normalize = [](Vec3 v) {
        float len = std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
        return Vec3{v.x / len, v.y / len, v.z / len};
    };

    auto subtract = [](Vec3 a, Vec3 b) {
        return Vec3{a.x - b.x, a.y - b.y, a.z - b.z};
    };

    auto cross = [](Vec3 a, Vec3 b) {
        return Vec3{
            a.y * b.z - a.z * b.y,
            a.z * b.x - a.x * b.z,
            a.x * b.y - a.y * b.x
        };
    };

    auto dot = [](Vec3 a, Vec3 b) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    };

    Vec3 f = normalize(subtract(center, eye));
    Vec3 s = normalize(cross(f, up));
    Vec3 u = cross(s, f);

    Mat4 r = mat4Identity();
    r.m[0] = s.x;  r.m[1] = u.x;  r.m[2] = -f.x;
    r.m[4] = s.y;  r.m[5] = u.y;  r.m[6] = -f.y;
    r.m[8] = s.z;  r.m[9] = u.z;  r.m[10] = -f.z;
    r.m[12] = -dot(s, eye);
    r.m[13] = -dot(u, eye);
    r.m[14] = dot(f, eye);
    return r;
}