/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/

#include "stdafx.h"
#include "dumb3d.h"
#include <cassert>

namespace Math3D
{

void vector3::RotateX(double angle)
{
    double ty = y;
    y = (cos(angle) * y - z * sin(angle));
    z = (z * cos(angle) + sin(angle) * ty);
};
void vector3::RotateY(double angle)
{
    double tx = x;
    x = (cos(angle) * x + z * sin(angle));
    z = (z * cos(angle) - sin(angle) * tx);
};
void vector3::RotateZ(double angle)
{
    double ty = y;
    y = (cos(angle) * y + x * sin(angle));
    x = (x * cos(angle) - sin(angle) * ty);
};

void inline vector3::SafeNormalize()
{
    double l = Length();
    if (l == 0)
    {
        x = y = z = 0;
    }
    else
    {
        x /= l;
        y /= l;
        z /= l;
    }
}

// From code in Graphics Gems; p. 766
inline scalar_t det2x2(scalar_t a, scalar_t b, scalar_t c, scalar_t d)
{
    return a * d - b * c;
}

inline scalar_t det3x3(scalar_t a1, scalar_t a2, scalar_t a3, scalar_t b1, scalar_t b2, scalar_t b3,
                       scalar_t c1, scalar_t c2, scalar_t c3)
{
    return a1 * det2x2(b2, b3, c2, c3) - b1 * det2x2(a2, a3, c2, c3) + c1 * det2x2(a2, a3, b2, b3);
}

scalar_t Determinant(const matrix4x4 &m)
{
    scalar_t a1 = m[0][0];
    scalar_t a2 = m[1][0];
    scalar_t a3 = m[2][0];
    scalar_t a4 = m[3][0];
    scalar_t b1 = m[0][1];
    scalar_t b2 = m[1][1];
    scalar_t b3 = m[2][1];
    scalar_t b4 = m[3][1];
    scalar_t c1 = m[0][2];
    scalar_t c2 = m[1][2];
    scalar_t c3 = m[2][2];
    scalar_t c4 = m[3][2];
    scalar_t d1 = m[0][3];
    scalar_t d2 = m[1][3];
    scalar_t d3 = m[2][3];
    scalar_t d4 = m[3][3];

    return a1 * det3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4) -
           b1 * det3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4) +
           c1 * det3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4) -
           d1 * det3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4);
}

matrix4x4 Adjoint(const matrix4x4 &m)
{
    scalar_t a1 = m[0][0];
    scalar_t a2 = m[0][1];
    scalar_t a3 = m[0][2];
    scalar_t a4 = m[0][3];
    scalar_t b1 = m[1][0];
    scalar_t b2 = m[1][1];
    scalar_t b3 = m[1][2];
    scalar_t b4 = m[1][3];
    scalar_t c1 = m[2][0];
    scalar_t c2 = m[2][1];
    scalar_t c3 = m[2][2];
    scalar_t c4 = m[2][3];
    scalar_t d1 = m[3][0];
    scalar_t d2 = m[3][1];
    scalar_t d3 = m[3][2];
    scalar_t d4 = m[3][3];

    // Adjoint(x,y) = -1^(x+y) * a(y,x)
    // Where a(i,j) is the 3x3 determinant of m with row i and col j removed
    matrix4x4 retVal;
    retVal(0)[0] = det3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4);
    retVal(0)[1] = -det3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4);
    retVal(0)[2] = det3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4);
    retVal(0)[3] = -det3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4);

    retVal(1)[0] = -det3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4);
    retVal(1)[1] = det3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4);
    retVal(1)[2] = -det3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4);
    retVal(1)[3] = det3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4);

    retVal(2)[0] = det3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4);
    retVal(2)[1] = -det3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4);
    retVal(2)[2] = det3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4);
    retVal(2)[3] = -det3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4);

    retVal(3)[0] = -det3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3);
    retVal(3)[1] = det3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3);
    retVal(3)[2] = -det3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3);
    retVal(3)[3] = det3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3);

    return retVal;
}

matrix4x4 Inverse(const matrix4x4 &m)
{
    matrix4x4 retVal = Adjoint(m);
    scalar_t det = Determinant(m);
    assert(det);

    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            retVal(i)[j] /= det;
        }
    }

    return retVal;
}
}

//**************************************
// Testing from here on.
//**************************************
#ifdef TEST_MATH3D
#include <iostream>

using namespace Math3D;
using namespace std;

static int failures = 0;

void ReportFailure(const char *className, const char *testName, bool passed)
{
    cout << className;
    if (passed)
        cout << " passed test ";
    else
    {
        cout << " FAILED test ";
        ++failures;
    }
    cout << testName << "." << endl;
}

const char *vector3Name = "vector3";
const char *matrix4x4Name = "matrix4x4";

void Testvector3Constructors(void)
{
    // Default ctor... just make sure it compiles
    vector3 defaultCtorTest;

    // Initializer ctor test (3 param)
    vector3 initCtorTest(1, 2, 3);
    ReportFailure(vector3Name, "initialized ctor (3 parameter version)",
                  (initCtorTest[0] == 1 && initCtorTest[1] == 2 && initCtorTest[2] == 3 &&
                   initCtorTest[3] == 1));

    // Initializer ctor test (4 param)
    vector3 initCtorTest2(1, 2, 3, 4);
    ReportFailure(vector3Name, "initialized ctor (4 parameter version)",
                  (initCtorTest2[0] == 1 && initCtorTest2[1] == 2 && initCtorTest2[2] == 3 &&
                   initCtorTest2[3] == 4));

    scalar_t initArray[] = {1, 2, 3, 4};
    vector3 initCtorArrayTest3(initArray);
    ReportFailure(vector3Name, "array initialized ctor (3 parameter version)",
                  (initCtorArrayTest3[0] == 1 && initCtorArrayTest3[1] == 2 &&
                   initCtorArrayTest3[2] == 3 && initCtorArrayTest3[3] == 1));

    vector3 initCtorArrayTest4(initArray, 4);
    ReportFailure(vector3Name, "array initialized ctor (4 parameter version)",
                  (initCtorArrayTest4[0] == 1 && initCtorArrayTest4[1] == 2 &&
                   initCtorArrayTest4[2] == 3 && initCtorArrayTest4[3] == 4));

    // Copy ctor test
    vector3 copyCtorTest(initCtorTest2);
    ReportFailure(vector3Name, "copy ctor", (copyCtorTest[0] == 1 && copyCtorTest[1] == 2 &&
                                             copyCtorTest[2] == 3 && copyCtorTest[3] == 4));
}

void Testvector3Comparison(void)
{
    vector3 alpha(1, 1, 1);
    vector3 beta(alpha);
    vector3 gamma(2, 3, 4);

    ReportFailure(vector3Name, "equivalence operator test 1", (alpha == beta));
    ReportFailure(vector3Name, "equivalence operator test 2", (!(alpha == gamma)));
    ReportFailure(vector3Name, "comparison operator test 1", !(alpha < beta));
    ReportFailure(vector3Name, "comparison operator test 2", (alpha < gamma));
    ReportFailure(vector3Name, "comparison operator test 3", !(gamma < beta));
}

void Testvector3Assignment(void)
{
    vector3 alpha(1, 1, 1, 1);
    vector3 beta(10, 10, 10, 10);
    alpha = beta;
    ReportFailure(vector3Name, "assignment operator", (alpha == beta));
}

void Testvector3UnaryOps(void)
{
    vector3 alpha(10, 10, 10, 10);
    vector3 beta(-10, -10, -10, -10);
    alpha = -alpha;
    ReportFailure(vector3Name, "negation operator", (alpha == beta));

    ReportFailure(vector3Name, "length squared 3 element version", LengthSquared3(alpha) == 300);
    ReportFailure(vector3Name, "length 3 element version", Length3(alpha) == SQRT_FUNCTION(300));
    ReportFailure(vector3Name, "length squared 4 element version", LengthSquared4(alpha) == 400);
    ReportFailure(vector3Name, "length 4 element version", Length4(alpha) == SQRT_FUNCTION(400));

    // Manually normalize beta
    // Done without /= on vector3, as we want to be independant of failure of /=
    // Earlier failures should be resolved before later ones (just like C++)
    beta = alpha;
    for (int i = 0; i < 3; ++i)
        beta(i) /= SQRT_FUNCTION(300);
    beta(3) = 1;
    ReportFailure(vector3Name, "normalize 3 element version", Normalize3(alpha) == beta);

    beta = alpha;
    for (int i = 0; i < 4; ++i)
        beta(i) /= SQRT_FUNCTION(400);
    ReportFailure(vector3Name, "normalize 4 element version", Normalize4(alpha) == beta);
}

void Testvector3BinaryOps(void)
{
    // Vector * Matrix is tested in Testmatrix4x4BinaryOps
    vector3 testVec(1, 1, 1, 1);
    vector3 deltaVec(1, 2, 3, 4);
    vector3 crossVec(1, -2, 1, 1); // testVec x deltaVec

    vector3 factorVec(10, 10, 10, 10);
    vector3 sumVec(2, 3, 4, 5);
    vector3 difVec(0, -1, -2, -3);
    vector3 testVec2;

    ReportFailure(vector3Name, "scalar multiply 1", (testVec * 10) == factorVec);
    ReportFailure(vector3Name, "scalar multiply 2", (10 * testVec) == factorVec);
    testVec2 = testVec;
    ReportFailure(vector3Name, "scalar multiply and store", (testVec2 *= 10) == factorVec);

    ReportFailure(vector3Name, "scalar divide", (factorVec / 10) == testVec);
    testVec2 = factorVec;
    ReportFailure(vector3Name, "scalar divide and store", (testVec2 /= 10) == testVec);

    ReportFailure(vector3Name, "vector addition", (testVec + deltaVec) == sumVec);
    testVec2 = testVec;
    ReportFailure(vector3Name, "vector addition and store", (testVec2 += deltaVec) == sumVec);

    ReportFailure(vector3Name, "vector subtraction", (testVec - deltaVec) == difVec);
    testVec2 = testVec;
    ReportFailure(vector3Name, "vector subtraction and store", (testVec2 -= deltaVec) == difVec);

    ReportFailure(vector3Name, "3 element dot product", 6 == DotProduct3(testVec, deltaVec));
    ReportFailure(vector3Name, "4 element dot product", 10 == DotProduct4(testVec, deltaVec));

    ReportFailure(vector3Name, "cross product", crossVec == CrossProduct(testVec, deltaVec));
}

void Testvector3(void)
{
    // Accessors cannot be tested effectively...
    // They are really trivial, and so don't really need testing,
    // but more importantly, how do you test the ctors without assuming
    // the accessors work?  Conversely, how do you test the acccessors
    // without assuming the ctors work?  Chicken and egg problem, and I
    // decided on testing the ctors, not the accessors.
    Testvector3Constructors();
    Testvector3Comparison();
    Testvector3Assignment();
    Testvector3UnaryOps();
    Testvector3BinaryOps();
}

void Testmatrix4x4Constructors(void)
{
    // Check if default ctor compiles
    matrix4x4 defaultTest;

    scalar_t initArray[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
    matrix4x4 arrayTest;
    arrayTest.C_Matrix(initArray);
    bool passedTest = true;
    for (int x = 0; x < 4; ++x)
        for (int y = 0; y < 4; ++y)
            if (arrayTest[x][y] != initArray[(y << 2) + x])
                passedTest = false;
    ReportFailure(matrix4x4Name, "array constructor", passedTest);

    matrix4x4 copyTest(arrayTest);
    passedTest = true;
    for (int x = 0; x < 4; ++x)
        for (int y = 0; y < 4; ++y)
            if (arrayTest[x][y] != copyTest[x][y])
                passedTest = false;
    ReportFailure(matrix4x4Name, "copy constructor", passedTest);
}

void Testmatrix4x4Comparison(void)
{
    scalar_t initArray[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
    scalar_t initArray2[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
    matrix4x4 alpha, beta, gamma;
    alpha.C_Matrix(initArray);
    beta.C_Matrix(initArray);
    gamma.C_Matrix(initArray2);

    ReportFailure(matrix4x4Name, "equality test 1", alpha == beta);
    ReportFailure(matrix4x4Name, "equality test 2", !(alpha == gamma));
    ReportFailure(matrix4x4Name, "comparison test 1", alpha < gamma);
    ReportFailure(matrix4x4Name, "comparison test 2", !(gamma < alpha));
    ReportFailure(matrix4x4Name, "comparison test 3", !(alpha < beta));
}

void Testmatrix4x4BinaryOps(void)
{
    scalar_t initVector[] = {0, 1, 2, 3};
    scalar_t initMatrix[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
    scalar_t resultVector[] = {0 * 0 + 1 * 1 + 2 * 2 + 3 * 3, 0 * 4 + 1 * 5 + 2 * 6 + 3 * 7,
                               0 * 8 + 1 * 9 + 2 * 10 + 3 * 11, 0 * 12 + 1 * 13 + 2 * 14 + 3 * 15};

    vector3 vector1(initVector, 4);
    matrix4x4 matrix1;
    matrix1.C_Matrix(initMatrix);
    vector3 vectorTest(resultVector, 4);
    ReportFailure(matrix4x4Name, "matrix * vector", vectorTest == matrix1 * vector1);

    scalar_t initMatrix2[] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};

    matrix4x4 matrix2;
    matrix2.C_Matrix(initMatrix2);
    matrix4x4 resultMatrix;
    for (int x = 0; x < 4; ++x)
        for (int y = 0; y < 4; ++y)
        {
            resultMatrix(x)[y] = 0;
            for (int i = 0; i < 4; ++i)
                resultMatrix(x)[y] += matrix1[i][y] * matrix2[x][i];
        }
    ReportFailure(matrix4x4Name, "matrix * matrix", resultMatrix == matrix1 * matrix2);
}

void Testmatrix4x4(void)
{
    Testmatrix4x4Constructors();
    Testmatrix4x4Comparison();
    Testmatrix4x4BinaryOps();
}

int main(int, char *[])
{
    int vectorFailures = 0;
    int matrixFailures = 0;
    Testvector3();
    vectorFailures = failures;
    failures = 0;

    Testmatrix4x4();
    matrixFailures = failures;

    cout << endl
         << "****************************************" << endl;
    cout << "*                                      *" << endl;
    if (vectorFailures + matrixFailures == 0)
        cout << "*    No failures detected in Math3D    *" << endl;
    else
    {
        cout << "*     FAILURES DETECTED IN MATH3D!     *" << endl;
        cout << "*     Total vector3 failures: " << vectorFailures << "        *" << endl;
        cout << "*     Total matrix4x4 failures: " << matrixFailures << "        *" << endl;
        cout << "*   Total Failures in Math3D: " << vectorFailures + matrixFailures << "        *"
             << endl;
    }
    cout << "*                                      *" << endl;
    cout << "****************************************" << endl;

    return 0;
}
#endif
