#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 MatrixScale(float x, float y, float z)
    {
        Matrix result = MatrixIdentity();
        result.m[0][0] = x;
        result.m[1][1] = y;
        result.m[2][2] = 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;
    }

    inline void MatrixTranslate(Matrix& m, const Vector3& v)
    {
        m.m[0][3] += v.x;
        m.m[1][3] += v.y;
        m.m[2][3] += v.z;
    }

    [[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;
    }

    [[nodiscard]] inline Matrix MatrixInverse(const Matrix& m)
    {
        Matrix out = {};

        float m00 = m.m[0][0], m01 = m.m[0][1], m02 = m.m[0][2], m03 = m.m[0][3];
        float m10 = m.m[1][0], m11 = m.m[1][1], m12 = m.m[1][2], m13 = m.m[1][3];
        float m20 = m.m[2][0], m21 = m.m[2][1], m22 = m.m[2][2], m23 = m.m[2][3];
        float m30 = m.m[3][0], m31 = m.m[3][1], m32 = m.m[3][2], m33 = m.m[3][3];

        out.m[0][0] = m11 * m22 * m33 - m11 * m23 * m32 - m21 * m12 * m33 + m21 * m13 * m32 + m31 * m12 * m23 - m31 * m13 * m22;
        out.m[1][0] = -m10 * m22 * m33 + m10 * m23 * m32 + m20 * m12 * m33 - m20 * m13 * m32 - m30 * m12 * m23 + m30 * m13 * m22;
        out.m[2][0] = m10 * m21 * m33 - m10 * m23 * m31 - m20 * m11 * m33 + m20 * m13 * m31 + m30 * m11 * m23 - m30 * m13 * m21;
        out.m[3][0] = -m10 * m21 * m32 + m10 * m22 * m31 + m20 * m11 * m32 - m20 * m12 * m31 - m30 * m11 * m22 + m30 * m12 * m21;

        out.m[0][1] = -m01 * m22 * m33 + m01 * m23 * m32 + m21 * m02 * m33 - m21 * m03 * m32 - m31 * m02 * m23 + m31 * m03 * m22;
        out.m[1][1] = m00 * m22 * m33 - m00 * m23 * m32 - m20 * m02 * m33 + m20 * m03 * m32 + m30 * m02 * m23 - m30 * m03 * m22;
        out.m[2][1] = -m00 * m21 * m33 + m00 * m23 * m31 + m20 * m01 * m33 - m20 * m03 * m31 - m30 * m01 * m23 + m30 * m03 * m21;
        out.m[3][1] = m00 * m21 * m32 - m00 * m22 * m31 - m20 * m01 * m32 + m20 * m02 * m31 + m30 * m01 * m22 - m30 * m02 * m21;

        out.m[0][2] = m01 * m12 * m33 - m01 * m13 * m32 - m11 * m02 * m33 + m11 * m03 * m32 + m31 * m02 * m13 - m31 * m03 * m12;
        out.m[1][2] = -m00 * m12 * m33 + m00 * m13 * m32 + m10 * m02 * m33 - m10 * m03 * m32 - m30 * m02 * m13 + m30 * m03 * m12;
        out.m[2][2] = m00 * m11 * m33 - m00 * m13 * m31 - m10 * m01 * m33 + m10 * m03 * m31 + m30 * m01 * m13 - m30 * m03 * m11;
        out.m[3][2] = -m00 * m11 * m32 + m00 * m12 * m31 + m10 * m01 * m32 - m10 * m02 * m31 - m30 * m01 * m12 + m30 * m02 * m11;

        out.m[0][3] = -m01 * m12 * m23 + m01 * m13 * m22 + m11 * m02 * m23 - m11 * m03 * m22 - m21 * m02 * m13 + m21 * m03 * m12;
        out.m[1][3] = m00 * m12 * m23 - m00 * m13 * m22 - m10 * m02 * m23 + m10 * m03 * m22 + m20 * m02 * m13 - m20 * m03 * m12;
        out.m[2][3] = -m00 * m11 * m23 + m00 * m13 * m21 + m10 * m01 * m23 - m10 * m03 * m21 - m20 * m01 * m13 + m20 * m03 * m11;
        out.m[3][3] = m00 * m11 * m22 - m00 * m12 * m21 - m10 * m01 * m22 + m10 * m02 * m21 + m20 * m01 * m12 - m20 * m02 * m11;

        float det = m00 * out.m[0][0] + m01 * out.m[1][0] + m02 * out.m[2][0] + m03 * out.m[3][0];

        if (det != 0.0f)
        {
            float invDet = 1.0f / det;
            for (int r = 0; r < 4; ++r)
                for (int c = 0; c < 4; ++c)
                    out.m[r][c] *= invDet;
        }

        return out;
    }
} // namespace Juliet
