#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "QF/mathlib.h"

static struct {
	quat_t      q1;
	quat_t      q2;
	quat_t      expect;
} quat_mult_tests[] = {
	{{1, 2, 3, 4}, {5, 6, 7, 8}, {24, 48, 48, -6}},
};
#define num_quat_mult_tests (sizeof (quat_mult_tests) / sizeof (quat_mult_tests[0]))

//PITCH YAW ROLL
static vec3_t test_angles[] = {
	{0, 0, 0},
	{45, 0, 0},
	{0, 45, 0},
	{0, 0, 45},
	{45, 45, 0},
	{0, 45, 45},
	{45, 0, 45},
	{45, 45, 45},
	{0, 180, 180},
	{180, 0, 180},
	{180, 180, 0},
};
#define num_angle_tests (sizeof (test_angles) / sizeof (test_angles[0]))

static struct {
	vec3_t      a;
	vec3_t      b;
	quat_t      expect;
} quat_vector_rotation_tests[] = {
	{{1, 0, 0}, {1, 0, 0}, {0, 0, 0, 1}},
	{{0, 1, 0}, {0, 1, 0}, {0, 0, 0, 1}},
	{{0, 0, 1}, {0, 0, 1}, {0, 0, 0, 1}},
	{{1, 0, 0}, {8, 0, 0}, {0, 0, 0, 1}},
	{{0, 8, 0}, {0, 1, 0}, {0, 0, 0, 1}},
	{{0, 0, 8}, {0, 0, 8}, {0, 0, 0, 1}},
	{{1, 0, 0}, {-1, 0, 0}, {0, 0, 0, 0}}, // x, y, z = NaN
	{{0, -1, 0}, {0, 1, 0}, {0, 0, 0, 0}}, // x, y, z = NaN
	{{0, 0, 1}, {0, 0, -1}, {0, 0, 0, 0}}, // x, y, z = NaN
	{{-1, 0, 0}, {8, 0, 0}, {0, 0, 0, 0}}, // x, y, z = NaN
	{{0, 8, 0}, {0, -1, 0}, {0, 0, 0, 0}}, // x, y, z = NaN
	{{0, 0, -8}, {0, 0, 8}, {0, 0, 0, 0}}, // x, y, z = NaN
	// excessive for float, but if vec_t becomes double...
	// 1/50 second orbiting JNSQ Kerbin at 120km altitude. While this has
	// nothing to do with quakeforge (yet?), it came up when testing camera
	// rotation in KSP and Unity failed miserably. I don't remember the exact
	// details, but I could see significant snapping in the rotation (I thought
	// it was a few times per second, but these numbers indicate it must have
	// been every few seconds).
	// The quaternion is unit to 9 sigfigs (a little larger)
	{{1720000, 0, 0}, {1719999.9983028059, 76.409082595828366, 0}, {0, 0, 2.22119434e-5, 1}},
	// 1/20 second, same situation
	{{1720000, 0, 0}, {1719999.9893925365, 191.02270615971355, 0}, {0, 0, 5.55298575e-5, 1}},
	// 1/5 second, same situation
	{{1720000, 0, 0}, {1719999.8302805868, 764.09080107761736, 0}, {0, 0, 2.2211943e-4, 1}},
	// 1/4 second, same situation
	{{1720000, 0, 0}, {1719999.7348134194, 955.11348367609469, 0}, {0, 0, 2.77649262e-4, 1}},
	// 1/3 second, same situation. This is (float) 1 ulp in w: about 0.0424
	// degrees.
	// The quaternion is unit to 9 sigfigs (a little larger)
	{{1720000, 0, 0}, {1719999.5285571995, 1273.4845939975546, 0}, {0, 0, 3.70199094e-4, 0.99999994}},
};
#define num_quat_vector_rotation_tests (sizeof (quat_vector_rotation_tests) / sizeof (quat_vector_rotation_tests[0]))

// return true if a and b are close enough (yay, floats)
static int
compare (vec_t a, vec_t b)
{
	vec_t       diff = a - b;
	return diff * diff < 0.001;
}

static int
test_quat_mult(const quat_t q1, const quat_t q2, const quat_t expect)
{
	int         i;
	quat_t      r;

	QuatMult (q1, q2, r);

	for (i = 0; i < 4; i++)
		if (!compare (r[i], expect[i]))
			goto fail;
	return 1;
fail:
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (q1));
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (q2));
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (r));
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (expect));
	return 0;
}


static void
rotate_vec (const quat_t r, const vec3_t v, vec3_t out)
{
	quat_t      qv = {0, 0, 0, 0};
	quat_t      t;

	VectorCopy (v, qv);

	QuatConj (r, t);
	QuatMult (qv, t, t);
	QuatMult (r, t, t);
	VectorCopy (t, out);
}

static int
test_rotation (const vec3_t angles)
{
	int         i;
	vec3_t      forward, right, up;

	quat_t      quat, conj, f, r, u, t;
	quat_t      qf = {1,  0, 0, 0};
	quat_t      qr = {0, -1, 0, 0};
	quat_t      qu = {0,  0, 1, 0};

	AngleVectors (angles, forward, right, up);

	AngleQuat (angles, quat);
	QuatConj (quat, conj);
	// rotate forward vector
	QuatMult (qf, conj, t);
	QuatMult (quat, t, f);
	// rotate right vector
	QuatMult (qr, conj, t);
	QuatMult (quat, t, r);
	// rotate up vector
	QuatMult (qu, conj, t);
	QuatMult (quat, t, u);

	if (!compare (f[3], 0))
		goto fail;
	for (i = 0; i < 3; i++)
		if (!compare (forward[i], f[i]))
			goto fail;

	if (!compare (r[3], 0))
		goto fail;
	for (i = 0; i < 3; i++)
		if (!compare (right[i], r[i]))
			goto fail;

	if (!compare (u[3], 0))
		goto fail;
	for (i = 0; i < 3; i++)
		if (!compare (up[i], u[i]))
			goto fail;
	return 1;
fail:
	printf ("\ntest_rotation\n");
	printf ("%11.9g %11.9g %11.9g\n", VectorExpand (angles));
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (quat));
	printf ("%11.9g %11.9g %11.9g %11.9g\n\n", QuatExpand (conj));
	printf ("%11.9g %11.9g %11.9g\n", VectorExpand (forward));
	printf ("%11.9g %11.9g %11.9g\n", VectorExpand (right));
	printf ("%11.9g %11.9g %11.9g\n\n", VectorExpand (up));

	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (f));
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (r));
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (u));
	return 0;
}

static int
test_rotation2 (const vec3_t angles)
{
	int         i;
	vec3_t      forward, right, up;

	quat_t      quat;
	vec3_t      f, r, u;
	vec3_t      vf = {1,  0, 0};
	vec3_t      vr = {0, -1, 0};
	vec3_t      vu = {0,  0, 1};

	AngleVectors (angles, forward, right, up);

	AngleQuat (angles, quat);
	// rotate forward vector
	QuatMultVec (quat, vf, f);
	// rotate right vector
	QuatMultVec (quat, vr, r);
	// rotate up vector
	QuatMultVec (quat, vu, u);

	for (i = 0; i < 3; i++)
		if (!compare (forward[i], f[i]))
			goto fail;

	for (i = 0; i < 3; i++)
		if (!compare (right[i], r[i]))
			goto fail;

	for (i = 0; i < 3; i++)
		if (!compare (up[i], u[i]))
			goto fail;
	return 1;
fail:
	printf ("\ntest_rotation2\n");
	printf ("\n\n%11.9g %11.9g %11.9g\n\n", angles[0], angles[1], angles[2]);
	printf ("%11.9g %11.9g %11.9g\n", forward[0], forward[1], forward[2]);
	printf ("%11.9g %11.9g %11.9g\n", right[0], right[1], right[2]);
	printf ("%11.9g %11.9g %11.9g\n\n", up[0], up[1], up[2]);

	printf ("%11.9g %11.9g %11.9g\n", f[0], f[1], f[2]);
	printf ("%11.9g %11.9g %11.9g\n", r[0], r[1], r[2]);
	printf ("%11.9g %11.9g %11.9g\n", u[0], u[1], u[2]);
	return 0;
}

static int
test_rotation3 (const vec3_t angles)
{
	int         i;
	quat_t      quat;
	vec3_t      v = {1, 1, 1};
	vec3_t      a, b;

	AngleQuat (angles, quat);
	QuatMultVec (quat, v, a);
	rotate_vec (quat, v, b);

	for (i = 0; i < 3; i++)
		if (!compare (a[i], b[i]))
			goto fail;
	return 1;
fail:
	printf ("\ntest_rotation3\n");
	printf ("%11.9g %11.9g %11.9g\n", VectorExpand(a));
	printf ("%11.9g %11.9g %11.9g\n", VectorExpand(b));
	return 0;
}

// XXX FIXME see usage in test_rotation4. need to investigate whether
// -ffast-math is any real benefit
#define ISNAN(x) (((x) & 0x7f800000) == 0x7f800000 && ((x) & 0x7fffff))

// FIXME differences in precision between archs
static int
cmp (float a, float b)
{
	typedef union {
		float       f;
		int         i;
	} fi;
	fi          ax;
	fi          bx;
	int         x;

	ax.f = a;
	bx.f = b;
	x = ax.i - bx.i;
	if (x < 0) {
		x = -x;
	}
	return (x & 0x7ffffffc) == 0;
}

static int
test_rotation4 (const vec3_t a, const vec3_t b, const quat_t expect)
{
	int         i;
	union { int x[4]; vec_t q[4]; } q;
	vec_t      *quat = q.q;
	vec3_t      t;
	vec_t       d = 0;

	VectorZero (t);

	// find the rotation vector between a and b
	QuatRotation (a, b, quat);

	if (quat[3] == 0) {
		if (expect[3] != 0) {
			goto fail;
		}
		// expect NaN for the vector components because the vectors are
		// anti-parallel and thus the rotation axis is undefined
		//XXX FIXME(?) still using -ffast-math which implies
		// -ffinite-math-only which in turn disables nan/inf checks, so have
		// to do it by hand
		// if (!(isnan(quat[0]) && isnan(quat[1]) && isnan(quat[2]))) {
		if (!(ISNAN(q.x[0]) && ISNAN(q.x[1]) && ISNAN(q.x[2]))) {
			goto fail;
		}
	} else {
		// the vectors are not anti-parallel and thus the rotation axis is
		// defined, so NaN is invalid
		// XXX FIXME see above
		//if (isnan(quat[0]) || isnan(quat[1]) || isnan(quat[2])) {
		if (ISNAN(q.x[0]) || ISNAN(q.x[1]) || ISNAN(q.x[2])) {
			goto fail;
		}
		for (i = 0; i < 4; i++) {
			// yes, float precision will make it difficult to set up expect
			// but it is at least consistent (ie, the "errors" are not at all
			// random and thus will be the same from run to run)
			if (!cmp (quat[i], expect[i])) {
				goto fail;
			}
		}
		QuatMultVec(quat, a, t);

		d = DotProduct (t, b) / (VectorLength (t) * VectorLength (b)) - 1;
		if (d * d > 1e-8) {
			goto fail;
		}
	}
	return 1;
fail:
	printf ("\ntest_rotation4\n");
	printf ("a: %11.9g %11.9g %11.9g\n", VectorExpand(a));
	printf ("b: %11.9g %11.9g %11.9g\n", VectorExpand(b));
	printf ("q: %11.9g %11.9g %11.9g %11.9g\n", QuatExpand(quat));
	printf ("e: %11.9g %11.9g %11.9g %11.9g\n", QuatExpand(expect));
	printf ("t: %11.9g %11.9g %11.9g\n", VectorExpand(t));
	printf ("d: %11.9g\n", d);
	return 0;
}

#define s05 0.70710678118654757

static struct {
	quat_t      q;
	vec_t       expect[9];
} quat_mat_tests[] = {
	{{0, 0, 0, 1},
		{1, 0, 0,
		 0, 1, 0,
		 0, 0, 1}},
	{{1, 0, 0, 0},
		{1,  0,  0,
		 0, -1,  0,
		 0,  0, -1}},
	{{0, 1, 0, 0},
		{-1, 0,  0,
		  0, 1,  0,
		  0, 0, -1}},
	{{0, 0, 1, 0},
		{-1,  0, 0,
		  0, -1, 0,
		  0,  0, 1}},
	{{0.5, 0.5, 0.5, 0.5},
		{0, 0, 1,
		 1, 0, 0,
		 0, 1, 0}},
#if defined(__i686__) && defined(__OPTIMIZE__)
	// the fp unit carries more precision than a 32-bit float, so
	// the close-to-zero errors are different
	{{s05, 0.0, 0.0, s05},
		{1,             0,             0,
		 0, 3.42285418e-08,   -0.99999994,
		 0,    0.99999994, 3.42285418e-08}},
#else
	{{s05, 0.0, 0.0, s05},
		{1,             0,             0,
		 0, 5.96046448e-8,   -0.99999994,
		 0,    0.99999994, 5.96046448e-8}},
#endif
};
#define num_quat_mat_tests (sizeof (quat_mat_tests) / sizeof (quat_mat_tests[0]))

static int
test_quat_mat(const quat_t q, const quat_t expect)
{
	int         i;
	vec_t       m[9];

	QuatToMatrix (q, m, 0, 0);

	for (i = 0; i < 9; i++)
		if (m[i] != expect[i])	// exact tests here
			goto fail;
	return 1;
fail:
	printf ("\ntest_quat_mat\n");
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (q));
	printf ("%11.9g %11.9g %11.9g   %11.9g %11.9g %11.9g\n",
			VectorExpand (m + 0), VectorExpand (expect + 0));
	printf ("%11.9g %11.9g %11.9g   %11.9g %11.9g %11.9g\n",
			VectorExpand (m + 3), VectorExpand (expect + 3));
	printf ("%11.9g %11.9g %11.9g   %11.9g %11.9g %11.9g\n",
			VectorExpand (m + 6), VectorExpand (expect + 6));
	return 0;
}

static int
test_quat_mat2(const quat_t q, const quat_t expect)
{
	int         i;
	vec_t       m[9];

	QuatToMatrix (q, m, 0, 1);
	Mat3Transpose (m, m);

	for (i = 0; i < 9; i++)
		if (m[i] != expect[i])	// exact tests here
			goto fail;
	return 1;
fail:
	printf ("\ntest_quat_mat\n");
	printf ("%11.9g %11.9g %11.9g %11.9g\n", QuatExpand (q));
	printf ("%11.9g %11.9g %11.9g   %11.9g %11.9g %11.9g\n",
			VectorExpand (m + 0), VectorExpand (expect + 0));
	printf ("%11.9g %11.9g %11.9g   %11.9g %11.9g %11.9g\n",
			VectorExpand (m + 3), VectorExpand (expect + 3));
	printf ("%11.9g %11.9g %11.9g   %11.9g %11.9g %11.9g\n",
			VectorExpand (m + 6), VectorExpand (expect + 6));
	return 0;
}

int
main (int argc, const char **argv)
{
	int         res = 0;
	size_t      i;

	for (i = 0; i < num_quat_mult_tests; i ++) {
		vec_t      *q1 = quat_mult_tests[i].q1;
		vec_t      *q2 = quat_mult_tests[i].q2;
		vec_t      *expect = quat_mult_tests[i].expect;
		if (!test_quat_mult (q1, q2, expect))
			res = 1;
	}

	for (i = 0; i < num_angle_tests; i ++) {
		if (!test_rotation (test_angles[i]))
			res = 1;
	}

	for (i = 0; i < num_angle_tests; i ++) {
		if (!test_rotation2 (test_angles[i]))
			res = 1;
	}

	for (i = 0; i < num_angle_tests; i ++) {
		if (!test_rotation3 (test_angles[i]))
			res = 1;
	}

	for (i = 0; i < num_quat_vector_rotation_tests; i++) {
		vec_t      *a = quat_vector_rotation_tests[i].a;
		vec_t      *b = quat_vector_rotation_tests[i].b;
		vec_t      *expect = quat_vector_rotation_tests[i].expect;
		if (!test_rotation4 (a, b, expect)) {
			res = 1;
		}
	}

	for (i = 0; i < num_quat_mat_tests; i ++) {
		vec_t      *q = quat_mat_tests[i].q;
		vec_t      *expect = quat_mat_tests[i].expect;
		if (!test_quat_mat (q, expect))
			res = 1;
	}

	for (i = 0; i < num_quat_mat_tests; i ++) {
		vec_t      *q = quat_mat_tests[i].q;
		vec_t      *expect = quat_mat_tests[i].expect;
		if (!test_quat_mat2 (q, expect))
			res = 1;
	}

	return res;
}
