#include "unittest/TestSuite.h"
#include "gamemath/IntegerFraction.h"
#include <limits.h>
#include <math.h>

static void testInit(void) {
	ifrac fraction;
	
	fraction = IFRAC(1, 1);
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = IFRAC(5, 3);
	TestCase_assertIntEqual(fraction.numerator, 5);
	TestCase_assertUIntEqual(fraction.denominator, 3);
}

static void testFromFloat(void) {
	ifrac fraction;
	
	fraction = ifrac_fromFloat(0.0f);
	TestCase_assertIntEqual(fraction.numerator, 0);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_fromFloat(1.0f);
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_fromFloat(0.5f);
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 2);
	
	fraction = ifrac_fromFloat(2.0f);
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_fromFloat(-0.25f);
	TestCase_assertIntEqual(fraction.numerator, -1);
	TestCase_assertUIntEqual(fraction.denominator, 4);
	
	fraction = ifrac_fromFloat(2.5f);
	TestCase_assertIntEqual(fraction.numerator, 5);
	TestCase_assertUIntEqual(fraction.denominator, 2);
	
	fraction = ifrac_fromFloat(INFINITY);
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 0);
	
	fraction = ifrac_fromFloat(NAN);
	TestCase_assertIntEqual(fraction.numerator, INT_MAX);
	TestCase_assertUIntEqual(fraction.denominator, 0);
}

#define TOLERANCE 0.000001f

static void testInspection(void) {
	ifrac fraction;
	int iresult;
	float fresult;
	
	fraction = IFRAC(0, 1);
	iresult = ifrac_wholeValue(fraction);
	TestCase_assertIntEqual(iresult, 0);
	iresult = ifrac_fractionalNumerator(fraction);
	TestCase_assertIntEqual(iresult, 0);
	fresult = ifrac_floatValue(fraction);
	TestCase_assertFloatEqual(fresult, 0.0f);
	fresult = ifrac_floatFraction(fraction);
	TestCase_assertFloatEqual(fresult, 0.0f);
	
	fraction = IFRAC(1, 1);
	iresult = ifrac_wholeValue(fraction);
	TestCase_assertIntEqual(iresult, 1);
	iresult = ifrac_fractionalNumerator(fraction);
	TestCase_assertIntEqual(iresult, 0);
	fresult = ifrac_floatValue(fraction);
	TestCase_assertFloatEqual(fresult, 1.0f);
	fresult = ifrac_floatFraction(fraction);
	TestCase_assertFloatEqual(fresult, 0.0f);
	
	fraction = IFRAC(2, 1);
	iresult = ifrac_wholeValue(fraction);
	TestCase_assertIntEqual(iresult, 2);
	iresult = ifrac_fractionalNumerator(fraction);
	TestCase_assertIntEqual(iresult, 0);
	fresult = ifrac_floatValue(fraction);
	TestCase_assertFloatEqual(fresult, 2.0f);
	fresult = ifrac_floatFraction(fraction);
	TestCase_assertFloatEqual(fresult, 0.0f);
	
	fraction = IFRAC(1, 2);
	iresult = ifrac_wholeValue(fraction);
	TestCase_assertIntEqual(iresult, 0);
	iresult = ifrac_fractionalNumerator(fraction);
	TestCase_assertIntEqual(iresult, 1);
	fresult = ifrac_floatValue(fraction);
	TestCase_assertFloatEqual(fresult, 0.5f);
	fresult = ifrac_floatFraction(fraction);
	TestCase_assertFloatEqual(fresult, 0.5f);
	
	fraction = IFRAC(2, 2);
	iresult = ifrac_wholeValue(fraction);
	TestCase_assertIntEqual(iresult, 1);
	iresult = ifrac_fractionalNumerator(fraction);
	TestCase_assertIntEqual(iresult, 0);
	fresult = ifrac_floatValue(fraction);
	TestCase_assertFloatEqual(fresult, 1.0f);
	fresult = ifrac_floatFraction(fraction);
	TestCase_assertFloatEqual(fresult, 0.0f);
	
	fraction = IFRAC(3, 2);
	iresult = ifrac_wholeValue(fraction);
	TestCase_assertIntEqual(iresult, 1);
	iresult = ifrac_fractionalNumerator(fraction);
	TestCase_assertIntEqual(iresult, 1);
	fresult = ifrac_floatValue(fraction);
	TestCase_assertFloatEqual(fresult, 1.5f);
	fresult = ifrac_floatFraction(fraction);
	TestCase_assertFloatEqual(fresult, 0.5f);
	
	fraction = IFRAC(2, 3);
	iresult = ifrac_wholeValue(fraction);
	TestCase_assertIntEqual(iresult, 0);
	iresult = ifrac_fractionalNumerator(fraction);
	TestCase_assertIntEqual(iresult, 2);
	fresult = ifrac_floatValue(fraction);
	TestCase_assertFloatApproximate(fresult, 2.0f / 3.0f, TOLERANCE);
	fresult = ifrac_floatFraction(fraction);
	TestCase_assertFloatApproximate(fresult, 2.0f / 3.0f, TOLERANCE);
	
	fraction = IFRAC(14, 5);
	iresult = ifrac_wholeValue(fraction);
	TestCase_assertIntEqual(iresult, 2);
	iresult = ifrac_fractionalNumerator(fraction);
	TestCase_assertIntEqual(iresult, 4);
	fresult = ifrac_floatValue(fraction);
	TestCase_assertFloatApproximate(fresult, 14.0f / 5.0f, TOLERANCE);
	fresult = ifrac_floatFraction(fraction);
	TestCase_assertFloatApproximate(fresult, 4.0f / 5.0f, TOLERANCE);
}

static void testReduce(void) {
	ifrac fraction;
	
	fraction = ifrac_reduce(IFRAC(1, 1));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_reduce(IFRAC(2, 2));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_reduce(IFRAC(4, 2));
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_reduce(IFRAC(1, 13));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 13);
	
	fraction = ifrac_reduce(IFRAC(43, 39));
	TestCase_assertIntEqual(fraction.numerator, 43);
	TestCase_assertUIntEqual(fraction.denominator, 39);
	
	fraction = ifrac_reduce(IFRAC(42, 39));
	TestCase_assertIntEqual(fraction.numerator, 14);
	TestCase_assertUIntEqual(fraction.denominator, 13);
}

static void testAdd(void) {
	ifrac fraction;
	
	fraction = ifrac_add(IFRAC(1, 1), IFRAC(1, 1));
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_add(IFRAC(2, 3), IFRAC(2, 3));
	TestCase_assertIntEqual(fraction.numerator, 4);
	TestCase_assertUIntEqual(fraction.denominator, 3);
	
	fraction = ifrac_add(IFRAC(2, 3), IFRAC(1, 3));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_add(IFRAC(1, 3), IFRAC(1, 5));
	TestCase_assertIntEqual(fraction.numerator, 8);
	TestCase_assertUIntEqual(fraction.denominator, 15);
	
	fraction = ifrac_add(IFRAC(1, 3), IFRAC(2, 6));
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 3);
	
	fraction = ifrac_addScalar(IFRAC(1, 3), 1);
	TestCase_assertIntEqual(fraction.numerator, 4);
	TestCase_assertUIntEqual(fraction.denominator, 3);
	
	fraction = ifrac_addScalar(IFRAC(5, 2), 3);
	TestCase_assertIntEqual(fraction.numerator, 11);
	TestCase_assertUIntEqual(fraction.denominator, 2);
}

static void testSubtract(void) {
	ifrac fraction;
	
	fraction = ifrac_sub(IFRAC(1, 1), IFRAC(1, 1));
	TestCase_assertIntEqual(fraction.numerator, 0);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_sub(IFRAC(4, 3), IFRAC(2, 3));
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 3);
	
	fraction = ifrac_sub(IFRAC(4, 3), IFRAC(1, 3));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_sub(IFRAC(1, 3), IFRAC(1, 5));
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 15);
	
	fraction = ifrac_sub(IFRAC(2, 3), IFRAC(2, 6));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 3);
	
	fraction = ifrac_subScalar(IFRAC(4, 3), 1);
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 3);
	
	fraction = ifrac_subScalar(IFRAC(11, 2), 3);
	TestCase_assertIntEqual(fraction.numerator, 5);
	TestCase_assertUIntEqual(fraction.denominator, 2);
}

static void testMultiply(void) {
	ifrac fraction;
	
	fraction = ifrac_mul(IFRAC(1, 1), IFRAC(1, 1));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_mul(IFRAC(1, 1), IFRAC(2, 1));
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_mul(IFRAC(1, 2), IFRAC(1, 2));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 4);
	
	fraction = ifrac_mul(IFRAC(2, 3), IFRAC(1, 3));
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 9);
	
	fraction = ifrac_mul(IFRAC(7, 5), IFRAC(1, 3));
	TestCase_assertIntEqual(fraction.numerator, 7);
	TestCase_assertUIntEqual(fraction.denominator, 15);
	
	fraction = ifrac_mulScalar(IFRAC(1, 3), 2);
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 3);
	
	fraction = ifrac_mulScalar(IFRAC(1, 3), 3);
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_mulScalar(IFRAC(7, 4), 2);
	TestCase_assertIntEqual(fraction.numerator, 7);
	TestCase_assertUIntEqual(fraction.denominator, 2);
}

static void testDivide(void) {
	ifrac fraction;
	
	fraction = ifrac_div(IFRAC(1, 1), IFRAC(1, 1));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_div(IFRAC(1, 1), IFRAC(2, 1));
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 2);
	
	fraction = ifrac_div(IFRAC(1, 1), IFRAC(1, 2));
	TestCase_assertIntEqual(fraction.numerator, 2);
	TestCase_assertUIntEqual(fraction.denominator, 1);
	
	fraction = ifrac_div(IFRAC(2, 3), IFRAC(3, 4));
	TestCase_assertIntEqual(fraction.numerator, 8);
	TestCase_assertUIntEqual(fraction.denominator, 9);
	
	fraction = ifrac_div(IFRAC(7, 5), IFRAC(4, 3));
	TestCase_assertIntEqual(fraction.numerator, 21);
	TestCase_assertUIntEqual(fraction.denominator, 20);
	
	fraction = ifrac_divScalar(IFRAC(1, 1), 3);
	TestCase_assertIntEqual(fraction.numerator, 1);
	TestCase_assertUIntEqual(fraction.denominator, 3);
	
	fraction = ifrac_divScalar(IFRAC(5, 3), 2);
	TestCase_assertIntEqual(fraction.numerator, 5);
	TestCase_assertUIntEqual(fraction.denominator, 6);
	
	fraction = ifrac_divScalar(IFRAC(7, 2), 2);
	TestCase_assertIntEqual(fraction.numerator, 7);
	TestCase_assertUIntEqual(fraction.denominator, 4);
}

static void testCmp(void) {
	int result;
	
	result = ifrac_cmp(IFRAC(0, 1), IFRAC(0, 1));
	TestCase_assertIntEqual(result, 0);
	
	result = ifrac_cmp(IFRAC(0, 1), IFRAC(1, 1));
	TestCase_assertIntEqual(result, -1);
	result = ifrac_cmp(IFRAC(1, 1), IFRAC(0, 1));
	TestCase_assertIntEqual(result, 1);
	result = ifrac_cmp(IFRAC(1, 1), IFRAC(1, 1));
	TestCase_assertIntEqual(result, 0);
	
	result = ifrac_cmp(IFRAC(1, 3), IFRAC(2, 6));
	TestCase_assertIntEqual(result, 0);
	result = ifrac_cmp(IFRAC(3, 9), IFRAC(1, 3));
	TestCase_assertIntEqual(result, 0);
	result = ifrac_cmp(IFRAC(2, 3), IFRAC(3, 9));
	TestCase_assertIntEqual(result, 1);
	result = ifrac_cmp(IFRAC(1, 3), IFRAC(5, 12));
	TestCase_assertIntEqual(result, -1);
	
	result = ifrac_cmp(IFRAC(1, 4), IFRAC(1, 3));
	TestCase_assertIntEqual(result, -1);
	result = ifrac_cmp(IFRAC(2, 7), IFRAC(3, 15));
	TestCase_assertIntEqual(result, 1);
	result = ifrac_cmp(IFRAC(2, 7), IFRAC(4, 13));
	TestCase_assertIntEqual(result, -1);
	
	result = ifrac_cmp(IFRAC(0x7FFFFFFC, 0x7FFFFFFE), IFRAC(0x7FFFFFFE, 0x7FFFFFFD));
	TestCase_assertIntEqual(result, -1);
	result = ifrac_cmp(IFRAC(0x7FFFFFFD, 0x7FFFFFFE), IFRAC(0x7FFFFFFC, 0x7FFFFFFD));
	TestCase_assertIntEqual(result, 1);
	result = ifrac_cmp(IFRAC(0x7FFFFFFC, 0x7FFFFFFE), IFRAC(0x7FFFFFFA, 0x7FFFFFFB));
	TestCase_assertIntEqual(result, -1);
	result = ifrac_cmp(IFRAC(0x7FFFFFFD, 0x7FFFFFFE), IFRAC(0x7FFFFFFA, 0x7FFFFFFB));
	TestCase_assertIntEqual(result, 1);
	result = ifrac_cmp(IFRAC(0x3FFFFFFF, 0x3FFFFFFE), IFRAC(0x7FFFFFFA, 0x7FFFFFFB));
	TestCase_assertIntEqual(result, 1);
}

TEST_SUITE(IntegerFractionTest,
           testInit,
           testFromFloat,
           testInspection,
           testReduce,
           testAdd,
           testSubtract,
           testMultiply,
           testDivide,
           testCmp)
