#include "lib/self_test.h"

#include "lib/lib.h"

class TestLib : public CxxTest::TestSuite 
{
public:
	void test_fnv_hash()
	{
		TS_ASSERT_EQUALS(fnv_hash(""), 0x811C9DC5u);		// verify initial value
		const u32 h1 = fnv_hash("abcdef");
		TS_ASSERT_EQUALS(h1, 0xFF478A2A);					// verify value for simple string
		TS_ASSERT_EQUALS(fnv_hash   ("abcdef", 6), h1);	// same result if hashing buffer
		TS_ASSERT_EQUALS(fnv_lc_hash("ABcDeF", 6), h1);	// same result if case differs

		TS_ASSERT_EQUALS(fnv_hash64(""), 0xCBF29CE484222325ull);	// verify initial value
		const u64 h2 = fnv_hash64("abcdef");
		TS_ASSERT_EQUALS(h2, 0xD80BDA3FBE244A0Aull);		// verify value for simple string
		TS_ASSERT_EQUALS(fnv_hash64("abcdef", 6), h2);	// same result if hashing buffer
	}

	void test_is_pow2()
	{
		TS_ASSERT_EQUALS(is_pow2(0u), false);
		TS_ASSERT_EQUALS(is_pow2(~0u), false);
		TS_ASSERT_EQUALS(is_pow2(0x80000001), false);
		TS_ASSERT_EQUALS(is_pow2(1), true);
		TS_ASSERT_EQUALS(is_pow2(1u << 31), true);
	}

	void test_ilog2()
	{
		TS_ASSERT_EQUALS(ilog2(0u), -1);
		TS_ASSERT_EQUALS(ilog2(3u), -1);
		TS_ASSERT_EQUALS(ilog2(0xffffffffu), -1);
		TS_ASSERT_EQUALS(ilog2(1u), 0);
		TS_ASSERT_EQUALS(ilog2(256u), 8);
		TS_ASSERT_EQUALS(ilog2(0x80000000u), 31);
	}

	void test_log2()
	{
		TS_ASSERT_EQUALS(log2(3u), 2u);
		TS_ASSERT_EQUALS(log2(0xffffffffu), 32u);
		TS_ASSERT_EQUALS(log2(1u), 0u);
		TS_ASSERT_EQUALS(log2(256u), 8u);
		TS_ASSERT_EQUALS(log2(0x80000000u), 31u);
	}

	void test_ilog2f()
	{
		TS_ASSERT_EQUALS(ilog2(1.f), 0);
		TS_ASSERT_EQUALS(ilog2(3.f), 1);
		TS_ASSERT_EQUALS(ilog2(256.f), 8);
	}

	void test_round_next_pow2()
	{
		TS_ASSERT_EQUALS(round_up_to_pow2(0u), 1u);
		TS_ASSERT_EQUALS(round_up_to_pow2(1u), 2u);
		TS_ASSERT_EQUALS(round_up_to_pow2(127u), 128u);
		TS_ASSERT_EQUALS(round_up_to_pow2(128u), 256u);
		TS_ASSERT_EQUALS(round_up_to_pow2(129u), 256u);
	}

	void test_round_up()
	{
		TS_ASSERT_EQUALS(round_up( 0u, 16u), 0u);
		TS_ASSERT_EQUALS(round_up( 4u, 16u), 16u);
		TS_ASSERT_EQUALS(round_up(15u, 16u), 16u);
		TS_ASSERT_EQUALS(round_up(20u, 32u), 32u);
		TS_ASSERT_EQUALS(round_up(29u, 32u), 32u);
		TS_ASSERT_EQUALS(round_up(0x1000u, 0x1000u), 0x1000u);
		TS_ASSERT_EQUALS(round_up(0x1001u, 0x1000u), 0x2000u);
		TS_ASSERT_EQUALS(round_up(0x1900u, 0x1000u), 0x2000u);
	}

	void test_round_down()
	{
		TS_ASSERT_EQUALS(round_down( 0u, 16u), 0u);
		TS_ASSERT_EQUALS(round_down( 4u, 16u), 0u);
		TS_ASSERT_EQUALS(round_down(15u, 16u), 0u);
		TS_ASSERT_EQUALS(round_down(20u, 16u), 16u);
		TS_ASSERT_EQUALS(round_down(29u, 16u), 16u);
		TS_ASSERT_EQUALS(round_down(0x1900u, 0x1000u), 0x1000u);
		TS_ASSERT_EQUALS(round_down(0x2001u, 0x2000u), 0x2000u);
	}

	// 16-bit saturating arithmetic
	void test_addusw()
	{
		TS_ASSERT_EQUALS(addusw(4u, 0x100u), 0x0104u);
		TS_ASSERT_EQUALS(addusw(0u, 0xFFFFu), 0xFFFFu);
		TS_ASSERT_EQUALS(addusw(0x8000u, 0x8000u), 0xFFFFu);
		TS_ASSERT_EQUALS(addusw(0xFFF0u, 0x0004u), 0xFFF4u);
		TS_ASSERT_EQUALS(addusw(0xFFFFu, 0xFFFFu), 0xFFFFu);
	}

	void test_subusw()
	{
		TS_ASSERT_EQUALS(subusw(4u, 0x100u), 0u);
		TS_ASSERT_EQUALS(subusw(100u, 90u), 10u);
		TS_ASSERT_EQUALS(subusw(0x8000u, 0x8000u), 0u);
		TS_ASSERT_EQUALS(subusw(0x0FFFu, 0xFFFFu), 0u);
		TS_ASSERT_EQUALS(subusw(0xFFFFu, 0x0FFFu), 0xF000u);
	}

	void test_movzx()
	{
		const u8 d1[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
		const u8 d2[] = { 0x43, 0x12, 0x23, 0xA4 };
		TS_ASSERT_EQUALS(movzx_64le(d1, 1), 0x01ull);
		TS_ASSERT_EQUALS(movzx_64le(d1, 2), 0x0201ull);
		TS_ASSERT_EQUALS(movzx_64le(d1, 8), 0x0807060504030201ull);
		TS_ASSERT_EQUALS(movzx_64le(d2, 4), 0xA4231243ull);
		TS_ASSERT_EQUALS(movzx_64le(d2+3, 1), 0xA4ull);
	}

	void test_movsx()
	{
		const u8 d1[] = { 0x09, 0xFE };
		const u8 d2[] = { 0xD9, 0x2C, 0xDD, 0x8F };
		const u8 d3[] = { 0x92, 0x26, 0x88, 0xF1, 0x35, 0xAC, 0x01, 0x83 };
		TS_ASSERT_EQUALS(movsx_64le(d1, 1), 0x09ull);
		TS_ASSERT_EQUALS(movsx_64le(d1, 2), 0xFFFFFFFFFFFFFE09ull);
		TS_ASSERT_EQUALS(movsx_64le(d2, 4), 0xFFFFFFFF8FDD2CD9ull);
		TS_ASSERT_EQUALS(movsx_64le(d3, 8), 0x8301AC35F1882692ull);
	}

	void test_hi_lo()
	{
		TS_ASSERT_EQUALS(u64_hi(0x0123456789ABCDEFull), 0x01234567u);
		TS_ASSERT_EQUALS(u64_hi(0x0000000100000002ull), 0x00000001u);

		TS_ASSERT_EQUALS(u64_lo(0x0123456789ABCDEFull), 0x89ABCDEFu);
		TS_ASSERT_EQUALS(u64_lo(0x0000000100000002ull), 0x00000002u);

		TS_ASSERT_EQUALS(u32_hi(0x01234567u), 0x0123u);
		TS_ASSERT_EQUALS(u32_hi(0x00000001u), 0x0000u);

		TS_ASSERT_EQUALS(u32_lo(0x01234567u), 0x4567u);
		TS_ASSERT_EQUALS(u32_lo(0x00000001u), 0x0001u);

		TS_ASSERT_EQUALS(u64_from_u32(0xFFFFFFFFu, 0x80000008u), 0xFFFFFFFF80000008ull);
		TS_ASSERT_EQUALS(u32_from_u16(0x8000u, 0xFFFFu), 0x8000FFFFu);
	}

	// fp_to_u?? already validate the result.

	void test_wildcard()
	{
		TS_ASSERT_EQUALS(match_wildcard("", ""), 1);
		TS_ASSERT_EQUALS(match_wildcard("a", 0), 1);	// NULL matches everything

		TS_ASSERT_EQUALS(match_wildcard("abc", "abc")     , 1);	// direct match
		TS_ASSERT_EQUALS(match_wildcard("abc", "???")     , 1);	// only ?
		TS_ASSERT_EQUALS(match_wildcard("abc", "*"  )     , 1);	// only *

		TS_ASSERT_EQUALS(match_wildcard("ab" , "a?" )     , 1);	// trailing ?
		TS_ASSERT_EQUALS(match_wildcard("abc", "a?c")     , 1);	// middle ?
		TS_ASSERT_EQUALS(match_wildcard("abc", "?bc")     , 1);	// leading ?

		TS_ASSERT_EQUALS(match_wildcard("abc", "a*" )     , 1);	// trailing *
		TS_ASSERT_EQUALS(match_wildcard("abcdef", "ab*ef"), 1);	// middle *
		TS_ASSERT_EQUALS(match_wildcard("abcdef", "*f"   ), 1);	// leading *

		TS_ASSERT_EQUALS(match_wildcard("abcdef", "a?cd*"), 1);	// ? and *
		TS_ASSERT_EQUALS(match_wildcard("abcdef", "a*d?f"), 1);	// * and ?
		TS_ASSERT_EQUALS(match_wildcard("abcdef", "a*d*" ), 1);	// multiple *

		// unicode test pasted from the above; keep in sync!

		TS_ASSERT_EQUALS(match_wildcardw(L"", L""), 1);
		TS_ASSERT_EQUALS(match_wildcardw(L"a", 0), 1);	// NULL matches everything

		TS_ASSERT_EQUALS(match_wildcardw(L"abc", L"abc")     , 1);	// direct match
		TS_ASSERT_EQUALS(match_wildcardw(L"abc", L"???")     , 1);	// only ?
		TS_ASSERT_EQUALS(match_wildcardw(L"abc", L"*"  )     , 1);	// only *

		TS_ASSERT_EQUALS(match_wildcardw(L"ab" , L"a?" )     , 1);	// trailing ?
		TS_ASSERT_EQUALS(match_wildcardw(L"abc", L"a?c")     , 1);	// middle ?
		TS_ASSERT_EQUALS(match_wildcardw(L"abc", L"?bc")     , 1);	// leading ?

		TS_ASSERT_EQUALS(match_wildcardw(L"abc", L"a*" )     , 1);	// trailing *
		TS_ASSERT_EQUALS(match_wildcardw(L"abcdef", L"ab*ef"), 1);	// middle *
		TS_ASSERT_EQUALS(match_wildcardw(L"abcdef", L"*f"   ), 1);	// leading *

		TS_ASSERT_EQUALS(match_wildcardw(L"abcdef", L"a?cd*"), 1);	// ? and *
		TS_ASSERT_EQUALS(match_wildcardw(L"abcdef", L"a*d?f"), 1);	// * and ?
		TS_ASSERT_EQUALS(match_wildcardw(L"abcdef", L"a*d*" ), 1);	// multiple *
	}

	void test_base32()
	{
		// compare against previous output (generated via this base32() call)
		const u8 in[] = { 0x12, 0x57, 0x85, 0xA2, 0xF9, 0x41, 0xCD, 0x57, 0xF3 };
		u8 out[20] = {0};
		base32(ARRAY_SIZE(in), in, out);
		const u8 correct_out[] = "CJLYLIXZI";
		TS_ASSERT_SAME_DATA(out, correct_out, ARRAY_SIZE(correct_out));
	}

	void test_rand()
	{
		// complain if huge interval or min > max
		debug_skip_next_err(ERR_INVALID_PARAM);
		TS_ASSERT_EQUALS(rand(1, 0), 0);
		debug_skip_next_err(ERR_INVALID_PARAM);
		TS_ASSERT_EQUALS(rand(2, ~0u), 0);

		// returned number must be in [min, max)
		for(int i = 0; i < 100; i++)
		{
			uint min = rand(), max = min+rand();
			uint x = rand(min, max);
			TS_ASSERT(min <= x && x < max);
		}

		// make sure both possible values are hit
		uint ones = 0, twos = 0;
		for(int i = 0; i < 100; i++)
		{
			uint x = rand(1, 3);
			// paranoia: don't use array (x might not be 1 or 2 - checked below)
			if(x == 1) ones++; if(x == 2) twos++;
		}
		TS_ASSERT_EQUALS(ones+twos, 100);
		TS_ASSERT(ones > 10 && twos > 10);
	}
};
