/*
** NesterDC - NES emulator for Dreamcast
** Ken Friece
** These routines were heavily influenced by 
** the vmu example that comes with libdream.
** A good source of vmu info is here:
** http://mc.pp.se/dc/sw.html
*/

#include "libc.h"
#include "stdlib.h"
#include "vmu.h"
#include "dream.h"

// This rouine came from Marcus's website
// http://mc.pp.se/dc/vms/fileheader.html
uint16 calcCRC(const unsigned char *buf, int size)
{
  int i, c, n = 0;
  for (i = 0; i < size; i++)
  {
    n ^= (buf[i]<<8);
    for (c = 0; c < 8; c++)
      if (n & 0x8000)
	n = (n << 1) ^ 4129;
      else
	n = (n << 1);
  }
  return (n & 0xffff);
}

uint16 get_next_block_from_fat (uint16 block_number) {
	
	uint8 addr = maple_vmu_addr(FIRST_DEVICE);
	uint16 fatblock;
	
	uint8 buf[512];
	uint16 *buf16 = (uint16*)buf;

	uint8 free_blocks = 0;

	if (vmu_block_read(addr, 255, buf)) {
		return 0;
	}
	timer_sleep (30);
	
	fatblock = buf16[0x46/2];

	if (vmu_block_read(addr, fatblock, buf) != 0) {
		return 0;
	}
	timer_sleep (30);

	return buf16[block_number];

}

uint16 check_vmu_for_game (const char* game_name) {
	
	int i, n;
	uint8 addr = maple_vmu_addr(FIRST_DEVICE), *ent;

	//Make sure we have a VMU
	if (!addr) {
		//printf("No VMU present!\r\n");
		return 0;
	}

	uint8 buf[512];

	uint16 *buf16 = (uint16*)buf, dirblock, dirlength, *ent16;

	uint16 icondata = 0;

	char *rom_name = (char *)malloc(12);
	// filename
	uint8 filename_size = strlen (game_name);

	for (i=0; i<11; i++) {
		if (i < filename_size) 
			rom_name[i] = game_name[i];
		else 
			rom_name[i] = ' ';
	}
	rom_name[11] = '&';

	// Read the root block and find out where the directory is located
	if (vmu_block_read(addr, 255, buf)) {
	//	printf("Can't read VMU root block\r\n");
	//	return 0;
	}
	timer_sleep (30);

	dirblock = buf16[0x4a/2]; dirlength = buf16[0x4c/2];

	int number = 0;
	// Draw entries and look for the ICONDATA.VMS entry
	for (n=dirlength; n>0; n--) {
		// Read one dir block
		if (vmu_block_read(addr, dirblock, buf) != 0) {
		//	printf("Can't read VMU block %d\r\n", dirblock);
			return 0;
		}
		timer_sleep (30);

		// Each block has 16 entries
		for (i=0; i<16; i++) {
			ent = buf+i*32;
			
			dirent_vmu *vmu_val = (dirent_vmu *)ent;

			//dc_print (testing->filename);
			//timer_sleep (1000);
			if (!*ent) {	
				continue;
			}

			number = 0;
			for (int ii=0; ii<12; ii++) {
				if (ent[ii+4] == rom_name[ii]) {
					number++;
				}
			}
			if (number == 12) {
				
				uint8 buf2[512];
				file_hdr_vmu *hdr_ptr = (file_hdr_vmu *)buf2;

				if (vmu_block_read(addr, vmu_val->firstblk, buf2)) {
					//printf("Can't read VMU root block\r\n");
				}
				timer_sleep (30);

				if ((strcmp (hdr_ptr->desc_short, game_name) == 0) && (strcmp (hdr_ptr->app_id, "***NesterDC***") == 0)) {
					return vmu_val->firstblk;
				}
			}

		}
		dirblock--;
	}

	return 0;
}

void update_fat (uint8 *free_mem, uint8 blocks_left) {

	uint8 addr = maple_vmu_addr(FIRST_DEVICE);
	uint16 fatblock;
	
	uint8 buf[512];
	uint16 *buf16 = (uint16*)buf;
	
	uint16 this_block = 0;

	//uint16 blocks_left = 18;
	uint8 max_block = blocks_left;

	if (vmu_block_read(addr, 255, buf)) {
		return;
	}
	timer_sleep (30);
	
	fatblock = buf16[0x46/2];

	if (vmu_block_read(addr, fatblock, buf) != 0) {
		return;
	}
	timer_sleep (30);

	for (int i=0; i<200; i++) {
		if (free_mem[199-i] == 1) {
			// first file block
			if (blocks_left == max_block) {
				this_block = 199 - i;
			}
			// need the next one
			if (blocks_left < max_block) {

				buf16[this_block] = 199 - i;
				this_block = 199 - i;
			}

			blocks_left--; 
		}
		// last file block
		if (blocks_left == 0) { 
			
			buf16[this_block] = 0xfffa;
			break;
		}
	}
	if (vmu_block_write (addr, fatblock, (uint8 *)buf16) != 0) {
		dc_print ("there was an error updating the fat");
	}
	timer_sleep (30);
}

void upload_vmu_data (uint8 *vmu_file, uint8 *free_mem, uint8 blocks_left) {

	uint8 addr = maple_vmu_addr(FIRST_DEVICE);
	//uint8 blocks_left = 18;

	uint8 *temp_ptr = vmu_file;
	uint8 *temp_p = (uint8 *)malloc(512);
	
	for (int i=0; i<200; i++) {

		if (free_mem[199-i] == 1) {

			if (vmu_block_read (addr, 199-i, temp_p) != 0) {
				dc_print ("there was an error reading data");
				print_uint32 ((uint32)blocks_left);
			}

			timer_sleep (30);

			if (vmu_block_write (addr, 199-i, temp_ptr) != 0) {
				dc_print ("there was an error writing data");
				print_uint32 ((uint32)blocks_left);
			}
			blocks_left--;
			temp_ptr += 512;
			timer_sleep (30);

		}
		if (blocks_left == 0) break;
	}
	
}

void upload_data_by_block (uint8 *vmu_file, uint16 block_number, uint8 blocks_left) {

	uint8 addr = maple_vmu_addr(FIRST_DEVICE);
	uint8 *temp_p = (uint8 *)malloc(512);

	uint8 *temp_ptr = vmu_file;
	uint16 block_copy = block_number;
	
	for (int i=0; i<blocks_left; i++) {

		//dc_print ("the block number we are on is");
		//print_uint32 ((uint32)block_number);
		//timer_sleep (2000);

		block_copy = block_number;
		if (vmu_block_read (addr, block_copy, temp_p) != 0) {
			dc_print ("there was an error reading data");
			print_uint32 ((uint32)block_number);
			timer_sleep (2000);
		}
		timer_sleep (30);

		block_copy = block_number;
		if (vmu_block_write (addr, block_copy, temp_ptr) != 0) {
			dc_print ("there was an error writing data");
			print_uint32 ((uint32)block_number);
			timer_sleep (2000);
		}
		timer_sleep (30);
	
		temp_ptr += 512;

		block_number = get_next_block_from_fat (block_number);
	
	}

}

void update_vmu_dir (uint16 block, const char *filename, uint8 blocks) {
	
	int i, n, drawn = 0;
	uint8 addr = maple_vmu_addr(FIRST_DEVICE), *ent;
	uint8 buf[512];
	uint8 *buf_ptr = buf;

	uint16 *buf16 = (uint16*)buf, dirblock, dirlength, *ent16;

	uint16 icondata = 0;

	if (!addr) {
		//printf("No VMU present!\r\n");
		return;
	}

	// Read the root block and find out where the directory is located
	if (vmu_block_read(addr, 255, buf)) {
		dc_print ("there was an error reading the root block");
		//printf("Can't read VMU root block\r\n");
	}
	timer_sleep (30);

	dirblock = buf16[0x4a/2]; dirlength = buf16[0x4c/2];

	dirent_vmu *dir_entry = (dirent_vmu *)malloc(1);
	//uint8 *dir_entry = (uint8*)malloc(32);

	create_dir_vmu (dir_entry, filename, block, blocks); 

	// Draw entries and look for the ICONDATA.VMS entry
	for (n=dirlength; n>0; n--) {
		// Read one dir block
		if (vmu_block_read(addr, dirblock, buf) != 0) {
			dc_print ("there was an error reading the dirblock");
		//	printf("Can't read VMU block %d\r\n", dirblock);
			return;
		}
		timer_sleep (30);

		// Each block has 16 entries
		for (i=0; i<16; i++) {
			ent = buf+i*32;
			if (!*ent) {	
				
				uint8 *temp_ptr = (uint8 *)dir_entry;

				for (int i=0; i<32; i++) {
					ent[i] = temp_ptr[i];
				}
				if (vmu_block_write(addr, dirblock, buf) != 0) {
					dc_print ("there was an error updating the directory"); 	
				}
				vmu_block_read(addr, dirblock, buf);
				timer_sleep (30);
				return;
			}
		}
		dirblock--;
	}

}

void do_crc (uint8 *buffer, uint16 bytes) {

	uint16 crc = calcCRC (buffer, bytes);		

	file_hdr_vmu * vmu_hdr = (file_hdr_vmu *)buffer;
	vmu_hdr->crc = crc;

}

uint8 check_free_blocks (uint8 *free_mem) {
	
	uint8 addr = maple_vmu_addr(FIRST_DEVICE);
	uint16 fatblock;
	
	uint8 buf[512];
	uint16 *buf16 = (uint16*)buf;

	uint8 free_blocks = 0;

	if (vmu_block_read(addr, 255, buf)) {
		return 0;
	}
	timer_sleep (30);
	
	fatblock = buf16[0x46/2];

	if (vmu_block_read(addr, fatblock, buf) != 0) {
		return 0;
	}
	timer_sleep (30);

	for (int i=0; i<200; i++) {
		if (buf16[i] == 0xfffc) {
			free_blocks++;
			free_mem[i] = 1;
		}
		else {
		//	print_uint32 ((uint32)buf16[i]);
		//	timer_sleep (3000);
		}
	}

	return free_blocks;
}

uint16 find_first_free_block (uint8 *fat_table) {
	
	uint16 first_free = 0;
	for (int i=0; i<200; i++) {
		if (fat_table[i] == 1) {
			first_free = i;	
		}
	}
	return first_free;
}

void create_dir_vmu (dirent_vmu *dir_entry, const char *rom_name, uint16 first_free_block, uint8 blocks) {
	
	uint8 *temp_ptr = (uint8 *)dir_entry;

	// zero out dir_entry
	for (int i=0; i<32; i++) {
		temp_ptr[i] = 0x00;
	}

	uint8 filename_size = strlen (rom_name);

	for (int i=0; i<11; i++) {
		if (i < filename_size) 
			dir_entry->filename[i] = rom_name[i];
		else 
			dir_entry->filename[i] = ' ';
	}

	dir_entry->filename[11] = '&';

	//strcpy (dir_entry->filename, rom_name);

	dir_entry->firstblk = first_free_block;
	dir_entry->filetype = 0x33;
	dir_entry->copyprotect = 0x00;
	dir_entry->filesize = blocks;
	dir_entry->hdroff = 0;

}

void create_vmu_header(uint8 *header, const char *rom_name, const char *desc_long, uint16 filesize) {
	
	file_hdr_vmu *vmu_h = (file_hdr_vmu *)header;

	uint8 *temp_ptr = header;

	// zero header mem
	for (int i=0; i<640; i++) {
		*temp_ptr++ = 0x00;
	}

	strcpy (vmu_h->desc_short, rom_name);

	strcpy (vmu_h->desc_long, desc_long);
	strcpy (vmu_h->app_id, "***NesterDC***");

	vmu_h->icon_cnt = 1;
	vmu_h->file_bytes = filesize;

	for (int i=0; i<512; i++) {
		if (i < 128)
			header[i+128] = 0;
		else if (i < 256)
			header[i+128] = 5;
		else if (i < 384)
			header[i+128] = 33;
		else
			header[i+128] = 51;
	}
}

// this routine came from the ghettoplay example that comes 
// with libdream
void vmu_icon_draw(const char *vmu_icon, uint8 vmu) {
	uint8 bitmap[48*32/8] = {0};
	int x, y, xi, xb;

	if (vmu_icon) {	
		for (y=0; y<32; y++)
			for (x=0; x<48; x++) {
				xi = x / 8;
				xb = 0x80 >> (x % 8);
				if (vmu_icon[(31-y)*48+(47-x)] == '+')
					bitmap[y*(48/8)+xi] |= xb;
			}
	}
	
	vmu_draw_lcd(vmu, bitmap);
}

// the save and load routines contain some nasty code.
// i should rewrite these, but they seem to be working.
void check_user_settings(uint8 vmu_mem1) {

	// return if there is no vmu present
	if (!vmu_mem1) return;

	timer_sleep (3000);

	// look in the vmu memory for a NesterDC settings file
	uint16 block_number = check_vmu_for_game("Settings");

	// a return value of 0 indicates that we didn't find it
	if (block_number == 0) return;

	dc_print ("Loading User Settings...");

	// used to store data read from the vmu (2 blocks)
	uint8 buf[1024];
	uint8 *buf_ptr = (uint8 *)buf;

	// read the header vmu block
	if (vmu_block_read(vmu_mem1, block_number, buf_ptr) != 0) {
		dc_print ("error loading block number");
		print_uint32 ((uint32)block_number);
		dc_sleep (3000);
		return;
	}

	timer_sleep (100);
	// get the next block in the vmu for the settings file
	block_number = get_next_block_from_fat (block_number);
	buf_ptr += 512;
	
	// read the next block
	if (vmu_block_read(vmu_mem1, block_number, buf_ptr) != 0) {
		dc_print ("error loading block number");
		print_uint32 ((uint32)block_number);
		dc_sleep (3000);
		return;
	}
	timer_sleep (100);

	// debug code to make sure that what went into the vmu, came
	// out of the vmu :)

	/*file_hdr_vmu *testing = (file_hdr_vmu *)buf;
	dc_print ("The CRC from the file is");
	print_uint32 ((uint32)testing->crc);
	timer_sleep (5000);

	testing->crc = 0;
	
	uint16 crc = calcCRC(buf, 1024);
	dc_print ("The calculated crc is");
	print_uint32 ((uint32)crc);
	timer_sleep (5000);*/

	// the way the vmu file structure is set up, user settings are 
	// actually 128 bytes into the second vmu block
	user_settings *settings_ptr = (user_settings *)(buf_ptr + 128);

	// here are the settings
	settings.cont_scheme = settings_ptr->cont_scheme;
	settings.frame_skip = settings_ptr->frame_skip;
	settings.sound_on = settings_ptr->sound_on;
	settings.player_2 = settings_ptr->player_2;
	settings.save = settings_ptr->save;

/*	print_uint32 (settings.cont_scheme);
	print_uint32 (settings.frame_skip);
	print_uint32 (settings.sound_on);
	print_uint32 (settings.player_2);
	print_uint32 (settings.save);*/

}

void save_user_settings(uint8 vmu_mem1) {
	
	// don't need to be here is there is no vmu
	if (!vmu_mem1) return;
    
	// free mem is an 
	uint8 *free_mem = (uint8 *)malloc(200);
	uint8 *temp_ptr = free_mem;

	for (int i=0; i<200; i++) {
		*temp_ptr++ = '0';
	}

	uint16 block_number = check_vmu_for_game("Settings");

	// found the game in SaveRAM
	if (block_number != 0) {

		dc_print ("Saving User Settings...");

		uint8 buf[1024];
		uint8 *vmu_file = (uint8 *)buf;
		uint8 free_blocks = check_free_blocks (free_mem);

		create_vmu_header (vmu_file, "Settings", "NesterDC User Settings File", 384);

		for (int i=640; i<1024; i++) {
			vmu_file[i] = 0;
		}

		user_settings *settings_ptr = (user_settings *)(vmu_file + 640);

		settings_ptr->cont_scheme = settings.cont_scheme;
		settings_ptr->frame_skip = settings.frame_skip;
		settings_ptr->sound_on = settings.sound_on;
		settings_ptr->player_2 = settings.player_2;
		settings_ptr->save = settings.save;

		uint16 crc = calcCRC(vmu_file, 1024);

		/*dc_print ("The CRC is");
		print_uint32 ((uint32)crc);
		timer_sleep (5000);

		do_crc (vmu_file, 1024);

		file_hdr_vmu *testing = (file_hdr_vmu *)vmu_file;
		dc_print ("Test the File CRC");
		print_uint32 ((uint32)testing->crc);
		timer_sleep (5000);*/

		upload_data_by_block (vmu_file, (uint32)block_number, 2);

	}
	else {
		uint8 free_blocks = check_free_blocks (free_mem);

		if (free_blocks < 2) {
			dc_print ("You need 2 free blocks to Save user settings");
			timer_sleep (5000);
			return;
		}

		dc_print ("Saving User Settings...");

		uint8 buf[1024];
		uint8 *vmu_file = (uint8 *)buf;

		create_vmu_header (vmu_file, "Settings", "NesterDC User Settings File", 384);

		// filler
		for (int i=640; i<1024; i++) {
			vmu_file[i] = 0;
		}

		user_settings *settings_ptr = (user_settings *)(vmu_file + 640);

		settings_ptr->cont_scheme = settings.cont_scheme;
		settings_ptr->frame_skip = settings.frame_skip;
		settings_ptr->sound_on = settings.sound_on;
		settings_ptr->player_2 = settings.player_2;
		settings_ptr->save = settings.save;

		do_crc (vmu_file, 1024);

		uint8 first_free_block = find_first_free_block(free_mem);

		update_fat (free_mem, 2);

		upload_vmu_data (vmu_file, free_mem, 2);

		update_vmu_dir (first_free_block, "Settings", 2);
	}

}

