#include "tests/MatrixTests.h"
#include "math/Matrix.h"
#include "tests/Test.h"
#include "utils/StringBuffer.h"

const float eps = 0.0001f;

template<int N, typename T>
static void compareVectors(Test& test, const Vector<N, T>& wanted,
                           const Vector<N, T>& actual, const char* text) {
    for(int i = 0; i < N; i++) {
        test.checkFloat(
            wanted[i], actual[i], eps,
            StringBuffer<50>(text).append(" (").append(i).append(")"));
    }
}

static void testInit(Test& test) {
    Matrix m;
    const float* data = m.getValues();
    for(int i = 0; i < 16; i++) {
        int x = i % 4;
        int y = i / 4;
        test.checkEqual(static_cast<float>(x == y), data[i],
                        StringBuffer<50>("init ").append(i));
    }
}

static void testTranspose(Test& test) {
    Matrix m;
    m.set(0, Vector4(1.0f, 2.0f, 3.0f, 4.0f));
    m.set(1, Vector4(5.0f, 6.0f, 7.0f, 8.0f));
    m.set(2, Vector4(9.0f, 10.0f, 11.0f, 12.0f));
    m.set(3, Vector4(13.0f, 14.0f, 15.0f, 16.0f));
    Matrix t = m.transpose();
    Matrix m2 = t.transpose();

    const float* mp = m.getValues();
    const float* tp = t.getValues();
    for(int x = 0; x < 4; x++) {
        for(int y = 0; y < 4; y++) {
            StringBuffer<50> s("transpose ");
            s.append(x).append(" | ").append(y);
            test.checkEqual(mp[y * 4 + x], tp[x * 4 + y], s);
        }
    }
    const float* mp2 = m2.getValues();
    for(int i = 0; i < 16; i++) {
        test.checkEqual(mp[i], mp2[i],
                        StringBuffer<50>("transpose ").append(i));
    }
}

static void testScale(Test& test) {
    Matrix m;
    m.scale(Vector3(2.0f, 3.0f, 4.0f));
    compareVectors(test, Vector3(-8.0f, 18.0f, 28.0f),
                   m * Vector3(-4.0f, 6.0f, 7.0f), "scale");
}

static void testUniformScale(Test& test) {
    Matrix m;
    m.scale(2.0f);
    compareVectors(test, Vector3(-8.0f, 12.0f, 14.0f),
                   m * Vector3(-4.0f, 6.0f, 7.0f), "uniform scale");
}

static void testTranslateX(Test& test) {
    Matrix m;
    m.translateX(5.0f);
    compareVectors(test, Vector3(1.0f, 6.0f, 7.0f),
                   m * Vector3(-4.0f, 6.0f, 7.0f), "translate x");
}

static void testTranslateY(Test& test) {
    Matrix m;
    m.translateY(6.0f);
    compareVectors(test, Vector3(-4.0f, 12.0f, 7.0f),
                   m * Vector3(-4.0f, 6.0f, 7.0f), "translate y");
}

static void testTranslateZ(Test& test) {
    Matrix m;
    m.translateZ(7.0f);
    compareVectors(test, Vector3(-4.0f, 6.0f, 14.0f),
                   m * Vector3(-4.0f, 6.0f, 7.0f), "translate z");
}

static void testTranslate(Test& test) {
    Matrix m;
    m.translate(Vector3(1.0f, 2.0f, 3.0f));
    compareVectors(test, Vector3(-3.0f, 8.0f, 10.0f),
                   m * Vector3(-4.0f, 6.0f, 7.0f), "translate");
}

static void testCombination(Test& test) {
    Matrix m;
    m.scale(2.0f);
    m.translateX(1.0f);
    m.translateY(2.0f);
    m.translateZ(3.0f);
    m.translate(Vector3(-4.0f, 2.0f, 3.0f));
    m.scale(Vector3(2.0f, 3.0f, 4.0f));
    m.scale(0.5f);
    compareVectors(test, Vector3(-1.0f, 9.0f, 16.0f),
                   m * Vector3(1.0f, 1.0f, 1.0f), "combination");
}

static void testMatrixCombination(Test& test) {
    Matrix a;
    a.scale(2.0f);
    a.translate(Vector3(1.0f, 2.0f, 3.0f));

    Matrix b;
    b.scale(3.0f);
    b.translate(Vector3(1.0f, 1.0f, 1.0f));

    Matrix c;
    c.translate(Vector3(-1.0f, -2.0f, -3.0f));
    c *= b * a;

    compareVectors(test, Vector3(9.0f, 11.0f, 13.0f),
                   c * Vector3(1.0f, 1.0f, 1.0f), "combination");
}

static void testRotateX(Test& test) {
    Matrix m;
    m.rotateX(90);
    compareVectors(test, Vector3(1.0f, 0.0f, 0.0f),
                   m * Vector3(1.0f, 0.0f, 0.0f), "rotate x 1");
    compareVectors(test, Vector3(0.0f, 0.0f, 1.0f),
                   m * Vector3(0.0f, 1.0f, 0.0f), "rotate x 2");
    compareVectors(test, Vector3(0.0f, -1.0f, 0.0f),
                   m * Vector3(0.0f, 0.0f, 1.0f), "rotate x 3");
}

static void testRotateY(Test& test) {
    Matrix m;
    m.rotateY(90);
    compareVectors(test, Vector3(0.0f, 0.0f, -1.0f),
                   m * Vector3(1.0f, 0.0f, 0.0f), "rotate y 1");
    compareVectors(test, Vector3(0.0f, 1.0f, 0.0f),
                   m * Vector3(0.0f, 1.0f, 0.0f), "rotate y 2");
    compareVectors(test, Vector3(1.0f, 0.0f, 0.0f),
                   m * Vector3(0.0f, 0.0f, 1.0f), "rotate y 3");
}

static void testRotateZ(Test& test) {
    Matrix m;
    m.rotateZ(90);
    compareVectors(test, Vector3(0.0f, 1.0f, 0.0f),
                   m * Vector3(1.0f, 0.0f, 0.0f), "rotate z 1");
    compareVectors(test, Vector3(-1.0f, 0.0f, 0.0f),
                   m * Vector3(0.0f, 1.0f, 0.0f), "rotate z 2");
    compareVectors(test, Vector3(0.0f, 0.0f, 1.0f),
                   m * Vector3(0.0f, 0.0f, 1.0f), "rotate z 3");
}

static void testToString(Test& test) {
    StringBuffer<200> s;
    Matrix m;
    m.set(0, Vector4(1.0f, 2.0f, 3.0f, 4.0f));
    m.set(1, Vector4(5.0f, 6.0f, 7.0f, 8.0f));
    m.set(2, Vector4(9.0f, 10.0f, 11.0f, 12.0f));
    m.set(3, Vector4(13.0f, 14.0f, 15.0f, 16.0f));
    s.append(m);
    test.checkEqual(
        StringBuffer<200>(
            "[[1.00, 2.00, 3.00, 4.00], [5.00, 6.00, 7.00, 8.00], "
            "[9.00, 10.00, 11.00, 12.00], [13.00, 14.00, 15.00, 16.00]]"),
        s, "to string");
}

static void testQuaternionMatrix(Test& test) {
    Quaternion q1(Vector3(1.0f, 0.0f, 0.0f), 48.0f);
    Quaternion q2(Vector3(0.0f, 1.0f, 0.0f), 52.0f);
    Quaternion q3(Vector3(0.0f, 0.0f, 1.0f), 60.0f);

    Matrix m;
    m.translate(Vector3(1.0f, 2.0f, 3.0f));
    m.rotate(q1).rotate(q2).rotate(q3);
    m.translate(Vector3(1.0f, 2.0f, 3.0f));

    Matrix check;
    check.translate(Vector3(1.0f, 2.0f, 3.0f));
    check.rotateX(48.0f).rotateY(52.0f).rotateZ(60.0f);
    check.translate(Vector3(1.0f, 2.0f, 3.0f));

    for(int i = 0; i < 16; i++) {
        test.checkFloat(check.getValues()[i], m.getValues()[i], eps,
                        "mul matrix");
    }
}

void MatrixTests::test() {
    Test test("Matrix");
    testInit(test);
    testScale(test);
    testUniformScale(test);
    testTranspose(test);
    testTranslateX(test);
    testTranslateY(test);
    testTranslateZ(test);
    testTranslate(test);
    testCombination(test);
    testMatrixCombination(test);
    testRotateX(test);
    testRotateY(test);
    testRotateZ(test);
    testToString(test);
    testQuaternionMatrix(test);
    test.finalize();
}