#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <sys/mman.h>

/* unit test */
#include "minunit.h"
/* kernel/mm/xxx */
#include "buddy.h"
#include "slab.h"

#define ROUND 1000
#define NPAGES (128 * 1000)

#define START_VADDR phys_to_virt(24*1024*1024)

void printk(const char *fmt, ...)
{
	va_list va;

	va_start(va, fmt);
	vprintf(fmt, va);
	va_end(va);
}

struct phys_mem_pool global_mem;

/* test buddy allocator */

static unsigned long buddy_num_free_page(struct phys_mem_pool *zone)
{
	unsigned long i, ret;

	ret = 0;
	for (i = 0; i < BUDDY_MAX_ORDER; ++i) {
		ret += zone->free_lists[i].nr_free;
	}
	return ret;
}

static void valid_page_idx(struct phys_mem_pool *zone, long idx)
{
	mu_assert((idx < zone->pool_phys_page_num)
		  && (idx >= 0), "invalid page idx");
}

static unsigned long get_page_idx(struct phys_mem_pool *zone, struct page *page)
{
	long idx;

	idx = page - zone->page_metadata;
	valid_page_idx(zone, idx);

	return idx;
}

static void test_alloc(struct phys_mem_pool *zone, long n, long order)
{
	long i;
	struct page *page;

	for (i = 0; i < n; ++i) {
		page = buddy_get_pages(zone, order);
		mu_check(page != NULL);
		get_page_idx(zone, page);
	}
	return;
}

void test_buddy(void)
{
	void *start;
	unsigned long npages;
	unsigned long size;
	unsigned long start_addr;

	long nget;
	long ncheck;

	struct page *page;
	long i;

	/* free_mem_size: npages * 0x1000 */
	npages = 128 * 0x1000;
	/* PAGE_SIZE + page metadata size */
	size = npages * (sizeof(struct page));
	start = mmap((void *)0x50000000000, size, PROT_READ | PROT_WRITE,
		     MAP_SHARED | MAP_ANONYMOUS, -1, 0);
	size = npages * (0x1000);
	start_addr =
	    (unsigned long)mmap((void *)0x60000000000, size,
				PROT_READ | PROT_WRITE,
				MAP_SHARED | MAP_ANONYMOUS, -1, 0);

	/* skip the metadata area */
	init_buddy(&global_mem, start, start_addr, npages);

	/* check the init state */
	nget = buddy_num_free_page(&global_mem);
	ncheck = npages / powl(2, BUDDY_MAX_ORDER - 1);
	//printf("ncheck is %ld\n", ncheck);
	mu_check(nget == ncheck);

	/* alloc single page for $npages times */
	test_alloc(&global_mem, npages, 0);

	/* should have 0 free pages */
	nget = buddy_num_free_page(&global_mem);
	ncheck = 0;
	mu_check(nget == ncheck);

	/* free all pages */
	for (i = 0; i < npages; ++i) {
		page = global_mem.page_metadata + i;
		buddy_free_pages(&global_mem, page);
	}
	nget = buddy_num_free_page(&global_mem);
	ncheck = npages / powl(2, BUDDY_MAX_ORDER - 1);
	mu_check(nget == ncheck);

	/* alloc 2-pages for $npages/2 times */
	test_alloc(&global_mem, npages / 2, 1);

	/* should have 0 free pages */
	nget = buddy_num_free_page(&global_mem);
	ncheck = 0;
	mu_check(nget == ncheck);

	/* free all pages */
	for (i = 0; i < npages; i += 2) {
		page = global_mem.page_metadata + i;
		buddy_free_pages(&global_mem, page);
	}
	nget = buddy_num_free_page(&global_mem);
	ncheck = npages / powl(2, BUDDY_MAX_ORDER - 1);
	mu_check(nget == ncheck);

	/* alloc MAX_ORDER-pages for  */
	test_alloc(&global_mem, npages / powl(2, BUDDY_MAX_ORDER - 1),
		   BUDDY_MAX_ORDER - 1);

	/* should have 0 free pages */
	nget = buddy_num_free_page(&global_mem);
	ncheck = 0;
	mu_check(nget == ncheck);

	/* free all pages */
	for (i = 0; i < npages; i += powl(2, BUDDY_MAX_ORDER - 1)) {
		page = global_mem.page_metadata + i;
		buddy_free_pages(&global_mem, page);
	}
	nget = buddy_num_free_page(&global_mem);
	ncheck = npages / powl(2, BUDDY_MAX_ORDER - 1);
	mu_check(nget == ncheck);

	/* alloc single page for $npages times */
	test_alloc(&global_mem, npages, 0);

	/* should have 0 free pages */
	nget = buddy_num_free_page(&global_mem);
	ncheck = 0;
	mu_check(nget == ncheck);

	/* free a half pages */
	for (i = 0; i < npages; i += 2) {
		page = global_mem.page_metadata + i;
		buddy_free_pages(&global_mem, page);
	}
	nget = buddy_num_free_page(&global_mem);
	ncheck = npages / 2;
	mu_check(nget == ncheck);

	/* free another half pages */
	for (i = 1; i < npages; i += 2) {
		page = global_mem.page_metadata + i;
		buddy_free_pages(&global_mem, page);
	}
	nget = buddy_num_free_page(&global_mem);
	ncheck = npages / powl(2, BUDDY_MAX_ORDER - 1);
	mu_check(nget == ncheck);
}

MU_TEST_SUITE(test_suite)
{
	MU_RUN_TEST(test_buddy);
}

int main(int argc, char *argv[])
{
	MU_RUN_SUITE(test_suite);
	MU_REPORT();
	return minunit_status;
}