#include <stdlib.h>
#include <stdio.h>

#include <nds.h>

#include "ds.h"
#include "ram.h"

//#define ARM7_USES_EXRAM

#define FEATURE_MEDIUM_CANREAD		0x00000001
#define FEATURE_MEDIUM_CANWRITE		0x00000002
#define FEATURE_SLOT_GBA			0x00000010
#define FEATURE_SLOT_NDS			0x00000020

bool dldi_writable = false;
bool dldi_readable = false;
int dldi_slot = 0;

int ds_find_dldi_slot(void)
{
	if (dldi_slot == 0)
	{
		unsigned int dldi_start = 0;
		
		for (unsigned int address = 0x2000000; address < 0x2000000 + 4 * 1024 * 1024; address += 4)
			if (*(unsigned int *)address == 0xBF8DA5ED)
			{
				if ((*(char *)(address + 4) == ' ') && (*(char *)(address + 5) == 'C'))
				{
					dldi_start = address;
					break;
				}		
			}
		
		if (dldi_start == 0)
		{
			printf("could not find dldi section - this is not good!\n");
			*(int *)0 = 0;
			while(1);
		}
		
		dldi_start += 0x64;
		unsigned int dldi_caps = *(unsigned int *)dldi_start;
		
		dldi_readable = (dldi_caps & FEATURE_MEDIUM_CANREAD) == FEATURE_MEDIUM_CANREAD;
		dldi_writable = (dldi_caps & FEATURE_MEDIUM_CANWRITE) == FEATURE_MEDIUM_CANWRITE;
		
		if ((dldi_caps & FEATURE_SLOT_NDS) == FEATURE_SLOT_NDS)
			dldi_slot = 1;
		
		if ((dldi_caps & FEATURE_SLOT_GBA) == FEATURE_SLOT_GBA)
			dldi_slot = 2;
		
		if (dldi_slot == 0)
		{
			printf("error: dldi section incorrectly set up!\ndriver seems to use neither slot-1 nor slot-2!\n");
			*(int *)0 = 0;
			while(1);
		}
	}
	
	return dldi_slot;
}

void ds_set_exram_timings(unsigned int first, unsigned int second)
{
	REG_EXEMEMCNT = REG_EXEMEMCNT | (first << 2) | (second << 4);
}

void ds_exram_cache_enable(void)
{
	DC_FlushAll();
	
	__asm__ __volatile__ (
		"ldr	r0,=0b01001010\n"
		"mcr	p15, 0, r0, c2, c0, 0\n"
		"mcr	p15, 0, r0, c2, c0, 1\n");
}

void ds_exram_cache_disable(void)
{
	DC_FlushAll();
	
	__asm__ __volatile__ (
		"ldr	r0,=0b01000010\n"
		"mcr	p15, 0, r0, c2, c0, 0\n"
		"mcr	p15, 0, r0, c2, c0, 1\n");
}

void ds_exram_unlock(void)			//for ram access
{
#ifdef USE_EXTRA_RAM
	ram_unlock();
	ds_exram_cache_enable();
#endif
}

void ds_exram_lock(void)			//for disk access
{
#ifdef USE_EXTRA_RAM
#ifdef ARM7_USES_EXRAM
	sysSetCartOwner(BUS_OWNER_ARM9);
#endif
	ds_exram_cache_disable();
	ram_lock();
#endif
}

void ds_try_exram_unlock(void)		//for ram access
{
#ifdef USE_EXTRA_RAM
//	printf("unlocking\n");

	unsigned short *text_map = (unsigned short *)SCREEN_BASE_BLOCK_SUB(9);
	text_map[32 * 23 + 29] = 0xf055;
		
	ds_exram_unlock();
#ifdef ARM7_USES_EXRAM
	sysSetCartOwner(BUS_OWNER_ARM7);
#endif
#endif
}

void ds_try_exram_lock(void)		//for disk access
{
#ifdef USE_EXTRA_RAM
//	printf("locking\n");

	unsigned short *text_map = (unsigned short *)SCREEN_BASE_BLOCK_SUB(9);
	text_map[32 * 23 + 29] = 0xf04c;
	
	ds_exram_lock();
#endif
}

void *exram_base = 0x0;
void *ds_exram_base(void)
{
#ifdef USE_EXTRA_RAM
	return exram_base;
#else
	return NULL;
#endif
}

void ds_set_exram_base(void *base)
{
	exram_base = base;
}

unsigned int exram_size = 0;
unsigned int ds_exram_size(void)
{
#ifdef USE_EXTRA_RAM
	return exram_size;
#else
	return 0;
#endif
}

void ds_set_exram_size(unsigned int size)
{
	exram_size = size;
}

unsigned int found_ram = 0;
void ram_test(void)
{
#ifdef USE_EXTRA_RAM

	volatile unsigned short *ptr = (volatile unsigned short *)ds_exram_base();
	printf("Testing RAM from %08x\n", ptr);
	unsigned short special = 0x1234;
	unsigned short read_back = 0;
	
	int count = 0;
	while(1)
	{
		*ptr = special;
		read_back = *ptr;
		if (read_back != special)
			break;
		else
			count += 16;
		ptr += 16;
	}
	printf("%d bytes found\n", count * 2);
	if (count < 4 * 1024 * 1024)
	{
		printf("that\'s not enough!\n");
		while(1);
	}
	found_ram = count * 2;
#endif
}

void clear_ram(unsigned int *base, unsigned int size)
{
	printf("initialising %dMB of ram\n", size >> 20);
	int megs = size >> 20;				//in megabytes
	
	for (int outer = 0; outer < megs; outer++)
	{
		printf("*");
		
		for (int count = 0; count < 1048576 >> 2; count++)
		{
			*base = 0;
			base++;
		}
	}
	printf("\n");
}

unsigned int ram_test_fast(void)
{
#ifdef USE_EXTRA_RAM

	volatile unsigned short *ptr = (volatile unsigned short *)ds_exram_base();
	unsigned short special = 0x1234;
	unsigned short read_back = 0;
	
	int count = 0;
	while(1)
	{
		*ptr = special;
		read_back = *ptr;
		if (read_back != special)
			break;
		else
			count += 512;
		ptr += 512;
	}
	return count * 2;
#endif
}

int count_largest_block(void)
{
	int largest = 0;
	void *ptr = NULL;
	
	do
	{
		if (ptr != NULL)
		{
			free(ptr);
			ptr = NULL;
			largest++;
		}
			
		ptr = malloc(largest + 1);
		
	} while(ptr != NULL);
	
	return largest;
}

int count_largest_block_kb(void)
{
	int largest = 0;
	void *ptr = NULL;
	
	do
	{
		if (ptr != NULL)
		{
			free(ptr);
			ptr = NULL;
			largest += 1024;
		}
			
		ptr = malloc(largest + 1024);
		
	} while(ptr != NULL);
	
	return largest;
}

void print_top_four_blocks_kb(void)
{
	void *l1, *l2, *l3, *l4;
	
	printf("big free blocks, kb:\n");
	int size = count_largest_block_kb();
	l1 = malloc(size);
	printf("1: %.2f\n", (float)size / 1024);
	
	size = count_largest_block_kb();
	l2 = malloc(size);
	printf("2: %.2f\n", (float)size / 1024);
	
	size = count_largest_block_kb();
	l3 = malloc(size);
	printf("3: %.2f\n", (float)size / 1024);
	
	size = count_largest_block_kb();
	l4 = malloc(size);
	printf("4: %.2f\n", (float)size / 1024);
	
	free(l1);
	free(l2);
	free(l3);
	free(l4);
	
	printf("done\n");
}

void print_top_four_blocks(void)
{
	void *l1, *l2, *l3, *l4;
	
	printf("big free blocks, kb:\n");
	int size = count_largest_block();
	l1 = malloc(size);
	printf("1: %.2f\n", (float)size / 1024);
	
	size = count_largest_block();
	l2 = malloc(size);
	printf("2: %.2f\n", (float)size / 1024);
	
	size = count_largest_block();
	l3 = malloc(size);
	printf("3: %.2f\n", (float)size / 1024);
	
	size = count_largest_block();
	l4 = malloc(size);
	printf("4: %.2f\n", (float)size / 1024);
	
	if (l1) free(l1);
	if (l2) free(l2);
	if (l3) free(l3);
	if (l4) free(l4);
	
	printf("done\n");
}

void ds_dmacopywords_async(unsigned char channel, const void *src, void *dest, unsigned int size)
{
	dmaCopyWordsAsynch(channel, src, dest, size);
}

void ds_waitfordma(unsigned char channel)
{
	while (DMA_CR(channel) & DMA_BUSY);
}

void ds_fifo_dma_async(unsigned char channel, const unsigned int *src)
{
	unsigned int entries = *src++;
	
//	while (entries--)
//		*(volatile unsigned int *)0x04000400 = *src++;

	DC_FlushRange((void *)src, entries * 4);
	
	while (DMA_CR(channel) & DMA_BUSY);
	
	DMA_SRC(0) = (unsigned int)src;
	DMA_DEST(0) = 0x4000400;
	DMA_CR(0) = DMA_FIFO | entries;
}
