#include <assert.h>
#include <minix/u64.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>

#define ERR err(__LINE__)
#define MAX_ERROR 4
#define TIMED 0

static volatile int errct;
static volatile int expect_SIGFPE;
static u64_t i, j, k;
static jmp_buf jmpbuf_SIGFPE, jmpbuf_main;

static void quit(void)
{
	if (errct == 0) {
		printf("ok\n");
		exit(0);
	} else {
		printf("%d errors\n", errct);
		exit(1);
	}
	assert(0); /* not reachable */
}

static void err(int line)
{
	/* print error information */
	printf("error line %d; i=0x%.8x%.8x; j=0x%.8x%.8x; k=0x%.8x%.8x\n", 
		line, 
		ex64hi(i), ex64lo(i), 
		ex64hi(j), ex64lo(j),
		ex64hi(k), ex64lo(k));

	/* quit after too many errors */
	if (errct++ > MAX_ERROR) {
		printf("Too many errors; test aborted\n");
		quit();
	}
}

#define LENGTHOF(arr) (sizeof(arr) / sizeof(arr[0]))

static u64_t getargval(int index, int *done)
{
	u32_t values[] = { 
		/* corner cases */
		0, 
		1,
		0x7fffffff,
		0x80000000,
		0x80000001,
		0xffffffff,
		/* random values */
		0xa9, 
		0x0d88, 
		0x242811, 
		0xeb44d1bc, 
		0x5b, 
		0xfb50, 
		0x569c02, 
		0xb23c8f7d, 
		0xc3, 
		0x2366, 
		0xfabb73, 
		0xcb4e8aef, 
		0xe9, 
		0xffdc, 
		0x05842d, 
		0x3fff902d};

	assert(done);

	/* values with corner case and random 32-bit components */
	if (index < LENGTHOF(values) * LENGTHOF(values))
		return make64(values[index / LENGTHOF(values)], values[index % LENGTHOF(values)]);

	index -= LENGTHOF(values) * LENGTHOF(values);

	/* small numbers */
	if (index < 16) return make64(index + 2, 0);
	index -= 16;

	/* big numbers */
	if (index < 16) return make64(-index - 2, -1);
	index -= 16;

	/* powers of two */
	if (index < 14) return make64(1 << (index * 2 + 5), 0);
	index -= 14;
	if (index < 16) return make64(0, 1 << (index * 2 + 1));
	index -= 16;

	/* done */
	*done = 1;
	return make64(0, 0);
}

static void handler_SIGFPE(int signum)
{
	assert(signum == SIGFPE);

	/* restore the signal handler */
	if (signal(SIGFPE, handler_SIGFPE) == SIG_ERR) ERR;

	/* division by zero occurred, was this expected? */
	if (expect_SIGFPE) {
		/* expected: jump back to test */
		expect_SIGFPE = 0;
		longjmp(jmpbuf_SIGFPE, -1);
	} else {
		/* not expected: error and jump back to main */
		longjmp(jmpbuf_main, -1);
	}

	/* not reachable */
	assert(0);
	exit(-1);
}

static void testmul(void)
{
	int kdone, kidx;
	u32_t ilo = ex64lo(i), jlo = ex64lo(j);
	u64_t prod = mul64(i, j);
	int prodbits;
		
	/* compute maximum index of highest-order bit */
	prodbits = bsr64(i) + bsr64(j) + 1;
	if (cmp64u(i, 0) == 0 || cmp64u(j, 0) == 0) prodbits = -1;
	if (bsr64(prod) > prodbits) ERR;

	/* compare to 32-bit multiplication if possible */	
	if (ex64hi(i) == 0 && ex64hi(j) == 0) {
		if (cmp64(prod, mul64u(ilo, jlo)) != 0) ERR;
		
		/* if there is no overflow we can check against pure 32-bit */
		if (prodbits < 32 && cmp64u(prod, ilo * jlo) != 0) ERR;
	}

	/* in 32-bit arith low-order DWORD matches regardless of overflow */
	if (ex64lo(prod) != ilo * jlo) ERR;

	/* multiplication by zero yields zero */
	if (prodbits < 0 && cmp64u(prod, 0) != 0) ERR;

	/* if there is no overflow, check absence of zero divisors */
	if (prodbits >= 0 && prodbits < 64 && cmp64u(prod, 0) == 0) ERR;

	/* commutativity */
	if (cmp64(prod, mul64(j, i)) != 0) ERR;

	/* loop though all argument value combinations for third argument */
	for (kdone = 0, kidx = 0; k = getargval(kidx, &kdone), !kdone; kidx++) {
		/* associativity */
		if (cmp64(mul64(mul64(i, j), k), mul64(i, mul64(j, k))) != 0) ERR;

		/* left and right distributivity */
		if (cmp64(mul64(add64(i, j), k), add64(mul64(i, k), mul64(j, k))) != 0) ERR;
		if (cmp64(mul64(i, add64(j, k)), add64(mul64(i, j), mul64(i, k))) != 0) ERR;
	}
}

static void testdiv0(void)
{
	int funcidx;

	assert(cmp64u(j, 0) == 0);

	/* loop through the 5 different division functions */
	for (funcidx = 0; funcidx < 5; funcidx++) {
		expect_SIGFPE = 1;
		if (setjmp(jmpbuf_SIGFPE) == 0) {
			/* divide by zero using various functions */
			switch (funcidx) {
				case 0: div64(i, j);		ERR; break;
				case 1: div64u64(i, ex64lo(j));	ERR; break;
				case 2: div64u(i, ex64lo(j));	ERR; break;
				case 3: rem64(i, j);		ERR; break;
				case 4: rem64u(i, ex64lo(j));	ERR; break;
				default: assert(0);		ERR; break;
			}

			/* if we reach this point there was no signal and an
			 * error has been recorded
			 */
			expect_SIGFPE = 0;
		} else {
			/* a signal has been received and expect_SIGFPE has
			 * been reset; all is ok now
			 */
			assert(!expect_SIGFPE);
		}
	}
}

static void testdiv(void)
{
	u64_t q, r;
#if TIMED
	struct timeval tvstart, tvend;

	printf("i=0x%.8x%.8x; j=0x%.8x%.8x\n", 
		ex64hi(i), ex64lo(i), 
		ex64hi(j), ex64lo(j));
	fflush(stdout);
	if (gettimeofday(&tvstart, NULL) < 0) ERR;
#endif

	/* division by zero has a separate test */
	if (cmp64u(j, 0) == 0) {
		testdiv0();
		return;
	}

	/* perform division, store q in k to make ERR more informative */
	q = div64(i, j);
	r = rem64(i, j);
	k = q;

#if TIMED
	if (gettimeofday(&tvend, NULL) < 0) ERR;
	tvend.tv_sec -= tvstart.tv_sec;
	tvend.tv_usec -= tvstart.tv_usec;
	if (tvend.tv_usec < 0) {
		tvend.tv_sec -= 1;
		tvend.tv_usec += 1000000;
	}
	printf("q=0x%.8x%.8x; r=0x%.8x%.8x; time=%d.%.6d\n", 
		ex64hi(q), ex64lo(q), 
		ex64hi(r), ex64lo(r), 
		tvend.tv_sec, tvend.tv_usec);
	fflush(stdout);
#endif

	/* compare to 64/32-bit division if possible */
	if (!ex64hi(j)) {
		if (cmp64(q, div64u64(i, ex64lo(j))) != 0) ERR;
		if (!ex64hi(q)) {
			if (cmp64u(q, div64u(i, ex64lo(j))) != 0) ERR;
		}
		if (cmp64u(r, rem64u(i, ex64lo(j))) != 0) ERR;

		/* compare to 32-bit division if possible */
		if (!ex64hi(i)) {
			if (cmp64u(q, ex64lo(i) / ex64lo(j)) != 0) ERR;
			if (cmp64u(r, ex64lo(i) % ex64lo(j)) != 0) ERR;
		}
	}

	/* check results using i = q j + r and r < j */
	if (cmp64(i, add64(mul64(q, j), r)) != 0) ERR;
	if (cmp64(r, j) >= 0) ERR;
}

static void test(void)
{
	int idone, jdone, iidx, jidx;

	/* loop though all argument value combinations */
	for (idone = 0, iidx = 0; i = getargval(iidx, &idone), !idone; iidx++)
	for (jdone = 0, jidx = 0; j = getargval(jidx, &jdone), !jdone; jidx++) {
		testmul();
		testdiv();
	}
}

int main(void)
{
	printf("Test 53 ");

	/* set up signal handler to deal with div by zero */
	if (setjmp(jmpbuf_main) == 0) {
		if (signal(SIGFPE, handler_SIGFPE) == SIG_ERR) ERR;
		
		/* perform tests */
		test();
	} else {
		/* an unexpected SIGFPE has occurred */
		ERR;
	}

	/* this was all */
	quit();
	assert(0); /* not reachable */
	return -1;
}
