#include "../HandmadeTest.h"

TEST(QuaternionOps, Inverse)
{
    hmm_quaternion q1 = HMM_Quaternion(1.0f, 2.0f, 3.0f, 4.0f);
    hmm_quaternion inverse = HMM_InverseQuaternion(q1);

    hmm_quaternion result = HMM_MultiplyQuaternion(q1, inverse);

    EXPECT_FLOAT_EQ(result.X, 0.0f);
    EXPECT_FLOAT_EQ(result.Y, 0.0f);
    EXPECT_FLOAT_EQ(result.Z, 0.0f);
    EXPECT_FLOAT_EQ(result.W, 1.0f);
}

TEST(QuaternionOps, Dot)
{
    hmm_quaternion q1 = HMM_Quaternion(1.0f, 2.0f, 3.0f, 4.0f);
    hmm_quaternion q2 = HMM_Quaternion(5.0f, 6.0f, 7.0f, 8.0f);

    {
        float result = HMM_DotQuaternion(q1, q2);
        EXPECT_FLOAT_EQ(result, 70.0f);
    }
#ifdef __cplusplus
    {
        float result = HMM_Dot(q1, q2);
        EXPECT_FLOAT_EQ(result, 70.0f);
    }
#endif
}

TEST(QuaternionOps, Normalize)
{
    hmm_quaternion q = HMM_Quaternion(1.0f, 2.0f, 3.0f, 4.0f);

    {
        hmm_quaternion result = HMM_NormalizeQuaternion(q);
        EXPECT_FLOAT_EQ(result.X, 0.1825741858f);
        EXPECT_FLOAT_EQ(result.Y, 0.3651483717f);
        EXPECT_FLOAT_EQ(result.Z, 0.5477225575f);
        EXPECT_FLOAT_EQ(result.W, 0.7302967433f);
    }
#ifdef __cplusplus
    {
        hmm_quaternion result = HMM_Normalize(q);
        EXPECT_FLOAT_EQ(result.X, 0.1825741858f);
        EXPECT_FLOAT_EQ(result.Y, 0.3651483717f);
        EXPECT_FLOAT_EQ(result.Z, 0.5477225575f);
        EXPECT_FLOAT_EQ(result.W, 0.7302967433f);
    }
#endif
}

TEST(QuaternionOps, NLerp)
{
    hmm_quaternion from = HMM_Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
    hmm_quaternion to = HMM_Quaternion(0.5f, 0.5f, -0.5f, 0.5f);

    hmm_quaternion result = HMM_NLerp(from, 0.5f, to);
    EXPECT_FLOAT_EQ(result.X, 0.28867513f);
    EXPECT_FLOAT_EQ(result.Y, 0.28867513f);
    EXPECT_FLOAT_EQ(result.Z, -0.28867513f);
    EXPECT_FLOAT_EQ(result.W, 0.86602540f);
}

TEST(QuaternionOps, Slerp)
{
    hmm_quaternion from = HMM_Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
    hmm_quaternion to = HMM_Quaternion(0.5f, 0.5f, -0.5f, 0.5f);

    hmm_quaternion result = HMM_Slerp(from, 0.5f, to);
    EXPECT_FLOAT_EQ(result.X, 0.28867513f);
    EXPECT_FLOAT_EQ(result.Y, 0.28867513f);
    EXPECT_FLOAT_EQ(result.Z, -0.28867513f);
    EXPECT_FLOAT_EQ(result.W, 0.86602540f);
}

TEST(QuaternionOps, QuatToMat4)
{
    const float abs_error = 0.0001f;

    hmm_quaternion rot = HMM_Quaternion(0.707107f, 0.0f, 0.0f, 0.707107f);

    hmm_mat4 result = HMM_QuaternionToMat4(rot);

    EXPECT_NEAR(result.Elements[0][0], 1.0f, abs_error);
    EXPECT_NEAR(result.Elements[0][1], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[0][2], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[0][3], 0.0f, abs_error);

    EXPECT_NEAR(result.Elements[1][0], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[1][1], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[1][2], 1.0f, abs_error);
    EXPECT_NEAR(result.Elements[1][3], 0.0f, abs_error);

    EXPECT_NEAR(result.Elements[2][0], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[2][1], -1.0f, abs_error);
    EXPECT_NEAR(result.Elements[2][2], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[2][3], 0.0f, abs_error);

    EXPECT_NEAR(result.Elements[3][0], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[3][1], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[3][2], 0.0f, abs_error);
    EXPECT_NEAR(result.Elements[3][3], 1.0f, abs_error);
}

TEST(QuaternionOps, Mat4ToQuat)
{
    const float abs_error = 0.0001f;

    // Rotate 90 degrees on the X axis
    {
        hmm_mat4 m = HMM_Rotate(90, HMM_Vec3(1, 0, 0));
        hmm_quaternion result = HMM_Mat4ToQuaternion(m);

        float cosf = 0.707107f; // cos(90/2 degrees)
        float sinf = 0.707107f; // sin(90/2 degrees)

        EXPECT_NEAR(result.X, sinf, abs_error);
        EXPECT_NEAR(result.Y, 0.0f, abs_error);
        EXPECT_NEAR(result.Z, 0.0f, abs_error);
        EXPECT_NEAR(result.W, cosf, abs_error);
    }

    // Rotate 90 degrees on the Y axis (axis not normalized, just for fun)
    {
        hmm_mat4 m = HMM_Rotate(90, HMM_Vec3(0, 2, 0));
        hmm_quaternion result = HMM_Mat4ToQuaternion(m);

        float cosf = 0.707107f; // cos(90/2 degrees)
        float sinf = 0.707107f; // sin(90/2 degrees)

        EXPECT_NEAR(result.X, 0.0f, abs_error);
        EXPECT_NEAR(result.Y, sinf, abs_error);
        EXPECT_NEAR(result.Z, 0.0f, abs_error);
        EXPECT_NEAR(result.W, cosf, abs_error);
    }

    // Rotate 90 degrees on the Z axis
    {
        hmm_mat4 m = HMM_Rotate(90, HMM_Vec3(0, 0, 1));
        hmm_quaternion result = HMM_Mat4ToQuaternion(m);

        float cosf = 0.707107f; // cos(90/2 degrees)
        float sinf = 0.707107f; // sin(90/2 degrees)

        EXPECT_NEAR(result.X, 0.0f, abs_error);
        EXPECT_NEAR(result.Y, 0.0f, abs_error);
        EXPECT_NEAR(result.Z, sinf, abs_error);
        EXPECT_NEAR(result.W, cosf, abs_error);
    }

    // Rotate 45 degrees on the X axis (this hits case 4)
    {
        hmm_mat4 m = HMM_Rotate(45, HMM_Vec3(1, 0, 0));
        hmm_quaternion result = HMM_Mat4ToQuaternion(m);

        float cosf = 0.9238795325f; // cos(90/2 degrees)
        float sinf = 0.3826834324f; // sin(90/2 degrees)

        EXPECT_NEAR(result.X, sinf, abs_error);
        EXPECT_NEAR(result.Y, 0.0f, abs_error);
        EXPECT_NEAR(result.Z, 0.0f, abs_error);
        EXPECT_NEAR(result.W, cosf, abs_error);
    }
}

TEST(QuaternionOps, FromAxisAngle)
{
    hmm_vec3 axis = HMM_Vec3(1.0f, 0.0f, 0.0f);
    float angle = HMM_PI32 / 2.0f;

    hmm_quaternion result = HMM_QuaternionFromAxisAngle(axis, angle);
    EXPECT_NEAR(result.X, 0.707107f, FLT_EPSILON * 2);
    EXPECT_FLOAT_EQ(result.Y, 0.0f);
    EXPECT_FLOAT_EQ(result.Z, 0.0f);
    EXPECT_NEAR(result.W, 0.707107f, FLT_EPSILON * 2);
}
