#pragma once

#include <Core/Math/Vector.h>
#include <math.h>

namespace Juliet
{
    struct Matrix
    {
        float m[4][4];
    };

    [[nodiscard]] inline Matrix MatrixIdentity()
    {
        Matrix result = {};
        result.m[0][0] = 1.0f;
        result.m[1][1] = 1.0f;
        result.m[2][2] = 1.0f;
        result.m[3][3] = 1.0f;
        return result;
    }

    [[nodiscard]] inline Matrix operator*(const Matrix& lhs, const Matrix& rhs)
    {
        Matrix result = {};
        for (int i = 0; i < 4; ++i)
        {
            for (int j = 0; j < 4; ++j)
            {
                for (int k = 0; k < 4; ++k)
                {
                    result.m[i][j] += lhs.m[i][k] * rhs.m[k][j];
                }
            }
        }
        return result;
    }

    [[nodiscard]] inline Matrix MatrixTranslation(float x, float y, float z)
    {
        Matrix result = MatrixIdentity();
        result.m[0][3] = x;
        result.m[1][3] = y;
        result.m[2][3] = z;
        return result;
    }

    [[nodiscard]] inline Matrix MatrixRotationX(float radians)
    {
        float c = cosf(radians);
        float s = sinf(radians);
        Matrix result = MatrixIdentity();
        result.m[1][1] =  c;
        result.m[1][2] = -s;
        result.m[2][1] =  s;
        result.m[2][2] =  c;
        return result;
    }

    [[nodiscard]] inline Matrix MatrixRotationY(float radians)
    {
        float c = cosf(radians);
        float s = sinf(radians);
        Matrix result = MatrixIdentity();
        result.m[0][0] =  c;
        result.m[0][2] =  s;
        result.m[2][0] = -s;
        result.m[2][2] =  c;
        return result;
    }

    [[nodiscard]] inline Matrix MatrixRotationZ(float radians)
    {
        float c = cosf(radians);
        float s = sinf(radians);
        Matrix result = MatrixIdentity();
        result.m[0][0] =  c;
        result.m[0][1] = -s;
        result.m[1][0] =  s;
        result.m[1][1] =  c;
        return result;
    }

    [[nodiscard]] inline Matrix MatrixRotation(float x, float y, float z)
    {
        return MatrixRotationX(x) * MatrixRotationY(y) * MatrixRotationZ(z);
    }

    inline Matrix LookAt(const Vector3& eye, const Vector3& target, const Vector3& up)
    {
        // Left-Handed convention
        Vector3 zaxis = Normalize(target - eye); // Forward is +z
        Vector3 xaxis = Normalize(Cross(up, zaxis));
        Vector3 yaxis = Cross(zaxis, xaxis);

        Matrix result = {};
        // Row 0
        result.m[0][0] = xaxis.x;
        result.m[0][1] = xaxis.y;
        result.m[0][2] = xaxis.z;
        result.m[0][3] = -Dot(xaxis, eye);

        // Row 1
        result.m[1][0] = yaxis.x;
        result.m[1][1] = yaxis.y;
        result.m[1][2] = yaxis.z;
        result.m[1][3] = -Dot(yaxis, eye);

        // Row 2
        result.m[2][0] = zaxis.x;
        result.m[2][1] = zaxis.y;
        result.m[2][2] = zaxis.z;
        result.m[2][3] = -Dot(zaxis, eye);

        // Row 3
        result.m[3][3] = 1.0f;

        return result;
    }

    inline Matrix PerspectiveFov(float fovY, float aspectRatio, float nearZ, float farZ)
    {
        // Left-Handed Perspective
        float yScale = 1.0f / tanf(fovY * 0.5f);
        float xScale = yScale / aspectRatio;

        Matrix result = {};
        result.m[0][0]  = xScale;
        result.m[1][1]  = yScale;
        result.m[2][2]  = farZ / (farZ - nearZ);
        result.m[2][3]  = (-nearZ * farZ) / (farZ - nearZ);
        result.m[3][2]  = 1.0f;
        result.m[3][3]  = 0.0f;
        return result;
    }
} // namespace Juliet
