#define PARANOID
#include "../zone.c"

static int
check_hunk_block (const memhunk_t *hunk, const void *mem, size_t size)
{
	const hunkblk_t  *h = (const hunkblk_t *) mem - 1;
	if (h->sentinal1 != HUNK_SENTINAL || h->sentinal2 != HUNK_SENTINAL) {
		printf ("invalid sentinals: %u %u\n", h->sentinal1, h->sentinal2);
		return 1;
	}
	if (h->size % HUNK_ALIGN) {
		printf ("block size misaligned: %zd %zd\n",
				h->size, h->size % HUNK_ALIGN);
		return 1;
	}
	if (h->size - sizeof (hunkblk_t) < size) {
		printf ("block size too small: %zd %zd\n",
				h->size - sizeof (hunkblk_t), size);
		return 1;
	}
	if (h->size - sizeof (hunkblk_t) - size >= HUNK_ALIGN) {
		printf ("block size too small: %zd %d\n",
				h->size - sizeof (hunkblk_t) - size, HUNK_ALIGN);
		return 1;
	}
	if ((byte *) h < hunk->base
		|| (byte *) h + h->size > hunk->base + hunk->size) {
		printf ("block outside of hunk: %p %p, %p %p\n",
				h, hunk->base, (byte *) h + h->size, hunk->base + hunk->size);
		return 1;
	}
	size_t      offset = (byte *) h - hunk->base;
	if (offset < hunk->size - hunk->high_used
		&& offset + h->size > hunk->low_used) {
		printf ("block in unallocated region: %zd %zd, %zd %zd\n",
				offset, offset + h->size,
				hunk->low_used, hunk->size - hunk->high_used);
		return 1;
	}
	return 0;
}

static int
check_cache_block (const memhunk_t *hunk, const void *c, size_t size)
{
	if (!c) {
		printf ("cache block is null\n");
		return 1;
	}

	const cache_system_t *cs = (cache_system_t *) c - 1;

	size_t      hunk_high = hunk->size - hunk->high_used;
	size_t      offset = (byte *) cs - hunk->base;
	if (offset < hunk->low_used || offset + cs->size > hunk_high) {
		printf ("cache block in hunk stack: %zd %zd, %zd, %zd\n",
				offset, offset + cs->size, hunk->low_used, hunk_high);
		return 1;
	}
	return 0;
}

static int
check_cache_order (memhunk_t *hunk)
{
	uint32_t    ci;
	cache_system_t *c;
	int         ret = 0;

	cache_system_t *first = cs_ptr (hunk, hunk->cache_head[0].next);
	cache_system_t *last = cs_ptr (hunk, hunk->cache_head[0].prev);

	if (first->prev) {
		printf ("first cache block does not point back to head: %u\n",
				first->prev);
	}
	if (last->next) {
		printf ("last cache block does not point forward to head: %u\n",
				last->next);
	}
	if (first->prev || last->next) {
		exit (1);	// unsafe to continue
	}
	uint32_t    prev = 0;
	for (ci = hunk->cache_head[0].next; ci; prev = ci, ci = c->next) {
		c = cs_ptr (hunk, ci);
		if ((byte *) c + c->size > hunk->base + hunk->size) {
			printf ("cache block outside of hunk (from %u)\n", prev);
			exit (1);
		}
		if (c->readlock) {
			printf ("look in cache detected at %u\n", ci);
			exit (1);
		}
		c->readlock = 1;
		if (c->next && c->next < ci) {
			printf ("cache block sequence incorrect %u -> %u\n", prev, ci);
			ret |= 1;
		}
	}
	return ret;
}

int
main (int argc, const char **argv)
{
	int         ret = 0;

	if (sizeof (memhunk_t) != 128) {
		ret = 1;
		printf ("memhunk_t not 128 bytes: %zd\n", sizeof (memhunk_t));
	}
	if (sizeof (hunkblk_t) != 64) {
		ret = 1;
		printf ("hunkblk_t not 64 bytes: %zd\n", sizeof (memhunk_t));
	}
	size_t      memsize = 64 * 1024;
	void       *hunk_mem = Sys_Alloc (memsize);
	if ((intptr_t) hunk_mem & 63) {
		// (not really part of test, but relied upon) should be 4k
		// aligned, but doesn't matter for the tests
		printf ("Sys_Alloc returned unaligned memory");
		ret = 1;
	}
	memhunk_t  *hunk = Hunk_Init (hunk_mem, memsize);
	if ((void *) hunk != hunk_mem) {
		ret = 1;
		printf ("hunk moved\n");
	}
	if ((void *) hunk->base != (void *)&hunk[1]) {
		ret = 1;
		printf ("hunk->base does not point to beginning of hunk space\n");
	}
	if (hunk->size != memsize - sizeof (memhunk_t)) {
		ret = 1;
		printf ("hunk size not memsize - sizeof (memhunk_t) (%zd - %zd): %zd\n",
				memsize, sizeof (memhunk_t), hunk->size);
	}
	if (hunk->low_used || hunk->high_used) {
		ret = 1;
		printf ("hunk low and high used not 0: %zd %zd\n",
				hunk->low_used, hunk->high_used);
	}
	if (hunk->tempmark || hunk->tempactive) {
		ret = 1;
		printf ("hunk tempmark tempactive not 0: %zd %d\n",
				hunk->tempmark, hunk->tempactive);
	}
	if (ret) {
		// no point continuing
		return 1;
	}

	size_t      low_mark = Hunk_LowMark (hunk);
	if (low_mark != 0) {
		ret = 1;
		printf ("initial hunk low mark not 0: %zd\n", low_mark);
	}
	size_t      size = 1024;
	void       *low = Hunk_AllocName (hunk, size, "low test");
	ret |= check_hunk_block (hunk, low, size);
	if (low > (void *) (hunk->base + sizeof (hunkblk_t))) {
		ret = 1;
		printf ("low memory not at beginning of hunk: %p %p\n",
				low, hunk->base + sizeof (hunkblk_t));
	}

	size_t      high_mark = Hunk_HighMark (hunk);
	if (high_mark != 0) {
		ret = 1;
		printf ("initial hunk high mark not 0: %zd\n", high_mark);
	}
	void       *high = Hunk_HighAlloc (hunk, size);//FIXME, "high test");
	ret |= check_hunk_block (hunk, high, size);
	if (high + size < (void *) (hunk->base + hunk->size)) {
		ret = 1;
		printf ("high memory not at end of hunk: %p %p\n",
				high + size, hunk->base + hunk->size);
	}

	global_hunk = hunk;//FIXME put hunk in cache_user_t ?
	cache_user_t cu = {};
	void       *cm = Cache_Alloc (&cu, 512, "cache test");
	ret |= check_cache_block (hunk, cm, 512);

	size_t      mark = Hunk_LowMark (hunk);
	void       *low2 = Hunk_AllocName (hunk, 61 * 1024, "low test 2");
	ret |= check_hunk_block (hunk, low2, 61 * 1024);
	if (cm == Cache_Check (&cu)) {
		ret = 1;
		printf ("cache block was not moved\n");
	}
	cm = Cache_Check (&cu);
	ret |= check_cache_block (hunk, cm, 512);
	Hunk_FreeToLowMark (hunk, mark);
	if (cm != Cache_Check (&cu)) {
		ret = 1;
		printf ("cache block was moved\n");
	}

	void       *high2 = Hunk_HighAlloc (hunk, 2 * 1024);
	ret |= check_hunk_block (hunk, high2, 2 * 1024);
	if (cm == Cache_Check (&cu)) {
		ret = 1;
		printf ("cache block was not moved\n");
	}
	cm = Cache_Check (&cu);
	ret |= check_cache_block (hunk, cm, 512);
	printf ("%zd %zd %zd\n", hunk->low_used, hunk->size - hunk->high_used,
			(byte *) cm - hunk->base);
	check_cache_order (hunk);
	Hunk_Print(hunk, 1);
	Cache_Print ();

	return ret;
}
