/*
** NesterDC - NES emulator for Dreamcast
** Copyright (C) 2001  Ken Friece
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful, 
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
** Library General Public License for more details.  To obtain a 
** copy of the GNU Library General Public License, write to the Free 
** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** Any permitted reproduction of these routines, in whole or in part,
** must bear this legend.
*/

#include "stdlib.h"
#include "nes.h"
#include "vmu.h"
#include "null_sound_mgr.h"
#include "NES_screen_mgr.h"
#include "dc_NES_screen_mgr.h"
#include "emulator.h"
#include "NES_pad.h"
#include "dream.h"
#include "image.h"
#include "sound/aica_fw.h"
#include "shapes.h"
#include "bitmap.h"

// this code is needed for all C++ dc programs 
// to compile
extern "C" {
	void *__builtin_new(int size)
	{
		return malloc(size);
	}
 
	void __builtin_delete(void *ptr)
	{
		free(ptr);
	}

	void *__builtin_vec_new (int size)
	{
		return __builtin_new (size);
	}
   
} // end extern "C"

// vmu screen image
#include "vmu_welcome.h"

// device addresses
static uint8 mvmu1 = 0;
static uint8 mvmu2 = 0;
static uint8 mcont1 = 0;
static uint8 mcont2 = 0;
static uint8 vmu_mem1 = 0;
static uint8 vmu_mem2 = 0;

uint32 free_vmu_blocks = 0;

// this is a pointer into the aica sound memory
// check it to see when to stop playing sound
volatile unsigned long *stop_sound  = (unsigned long*)(0x10010 + 0xa0800000);
// pointer into sound memory. tell when to start sound.
volatile unsigned long *start_sound  = (volatile unsigned long*)(0x10000 + 0xa0800000);

// define some common colors 
#define _yellow ((255 >> 3) << 11) | ((255 >> 2) << 5) | ((0 >> 3) << 0)
#define _red ((255 >> 3) << 11) | ((0 >> 2) << 5) | ((0 >> 3) << 0)
#define _green ((0 >> 3) << 11) | ((255 >> 2) << 5) | ((0 >> 3) << 0)
#define _blue ((0 >> 3) << 11) | ((0 >> 2) << 5) | ((255 >> 3) << 0)
#define _black ((0 >> 3) << 11) | ((0 >> 2) << 5) | ((0 >> 3) << 0)
#define _white ((255 >> 3) << 11) | ((255 >> 2) << 5) | ((255 >> 3) << 0)

// global user settings data
user_settings settings;

// pointers to bmp images
uint8 *no_game_bmp;
uint8 *menu_bmp;
uint8 *credits_bmp;
uint8 *game_menu_bmp;
uint8 *options_bmp;

// game name array of pointers
char *game_names[2048];

// current controller button config data
short _CONT_A = CONT_A;
short _CONT_B = CONT_X;

// global flag to tell when to exit
// the game loop
uint8 exit_game_loop = FALSE;

// nes controllers
NES_pad pad1;
NES_pad pad2;

// a couple of local routines in main
void do_game_menu();
void do_options();
void do_credits();
void run_game(char *, uint8);

// load the default font
char *fnt8x8 = (char *)NULL;

void load_font() {

	fnt8x8 = load_misc("/GFX/8X8.FNT");
	font_set((unsigned char *)(fnt8x8 + 4), 8);

}

// default user settings
void load_default_settings() {
	settings.cont_scheme = 0;
	settings.frame_skip = 2;
	settings.sound_on = TRUE;
	settings.player_2 = FALSE;
	settings.save = FALSE;
}

// check and return the controller status
inline cont_cond_t getButtons (int port) {
	
	cont_cond_t cond;

	if (port == 0) {
		cont_get_cond (mcont1, &cond);
	}
	else {
		cont_get_cond (mcont2, &cond);
	}

	return cond;
}

// poll the dc controller, and set the NES pads
// based on the result
inline void poll_input() {

	cont_cond_t cont_out = getButtons(0);
	pad1.set_button_state(NES_UP, !(cont_out.buttons & CONT_DPAD_UP));
	pad1.set_button_state(NES_DOWN, !(cont_out.buttons & CONT_DPAD_DOWN));
	pad1.set_button_state(NES_LEFT, !(cont_out.buttons & CONT_DPAD_LEFT));
	pad1.set_button_state(NES_RIGHT, !(cont_out.buttons & CONT_DPAD_RIGHT));
	pad1.set_button_state(NES_SELECT, !(cont_out.buttons & CONT_Y));
	pad1.set_button_state(NES_START, !(cont_out.buttons & CONT_START));
	pad1.set_button_state(NES_B, !(cont_out.buttons & _CONT_B));
	pad1.set_button_state(NES_A, !(cont_out.buttons & _CONT_A));

	// check for exit game signal
	if (cont_out.rtrig & cont_out.ltrig) {
		exit_game_loop = TRUE;
	}

	// polling for player_2 wastes cpu cycles when playing with only 
	// one player
	if (settings.player_2) {
		cont_out = getButtons(1);
		pad2.set_button_state(NES_UP, !(cont_out.buttons & CONT_DPAD_UP));
		pad2.set_button_state(NES_DOWN, !(cont_out.buttons & CONT_DPAD_DOWN));
		pad2.set_button_state(NES_LEFT, !(cont_out.buttons & CONT_DPAD_LEFT));
		pad2.set_button_state(NES_RIGHT, !(cont_out.buttons & CONT_DPAD_RIGHT));
		pad2.set_button_state(NES_SELECT, !(cont_out.buttons & CONT_Y));
		pad2.set_button_state(NES_START, !(cont_out.buttons & CONT_START));
		pad2.set_button_state(NES_B, !(cont_out.buttons & _CONT_B));
		pad2.set_button_state(NES_A, !(cont_out.buttons & _CONT_A));
	}

	
}

// the main menu code
void load_main_menu (){

	display_bmp(menu_bmp);

	exit_game_loop = FALSE;

	timer_sleep(100);
	
	while (1) {
		if (!(getButtons(0).buttons & CONT_A)) {
			do_game_menu();
		}
		else if (!(getButtons(0).buttons & CONT_X)) {
			do_options();
		}
		else if (!(getButtons(0).buttons & CONT_Y)) {
			do_credits();
		}
		else {
			timer_sleep (25);
		}
	
	}
}

void do_options () {

	display_bmp (options_bmp);

	draw_string (20, 15, _yellow, "START : Return to Main Menu");

	switch (settings.frame_skip) {
		case 0: draw_string (20, 35, _white, "     Frame Skip : 0 frames skipped");
				break;
		case 1: draw_string (20, 35, _white, "     Frame Skip : 1 per 3 frames");
				break;
		case 2:	draw_string (20, 35, _white, "     Frame Skip : 1 per 2 frames");
				break;
		case 3: draw_string (20, 35, _white, "     Frame Skip : 2 per 4 frames");
				break;
		case 4: draw_string (20, 35, _white, "     Frame Skip : 2 per 3 frames");
				break;
	}

	draw_string (20, 25, _white, "DOWN  : Slower and Smoother");
	draw_string (20, 30, _white, "UP : Faster and Choppier");

	draw_string (20, 45, _white, "Y  : Adjust NES A and B Buttons");
	switch (settings.cont_scheme) {
		case 0:	draw_string (20, 50, _white, "     [NES_B = DC_X] [NES_A = DC_A]");
				break;
		case 1: draw_string (20, 50, _white, "     [NES_B = DC_A] [NES_A = DC_B]");
				break;
		case 2: draw_string (20, 50, _white, "     [NES_B = DC_X] [NES_A = DC_B]");
				break;
	}

	draw_string (20, 60, _white, "A  : Toggle Sound");
	switch (settings.sound_on) {
		case 0: draw_string (240, 60, _white, "[OFF]");
			break;
		case 1: draw_string (240, 60, _white, "[ON]");
			break;
	}

	draw_string (20, 70, _white, "B  : Toggle 1 / 2 Player Mode");
	switch (settings.player_2) {
		case 0: draw_string (20, 75, _white, "     1 Player");
			break;
		case 1: draw_string (20, 75, _white, "     2 Players");
			break;
	}

	draw_string (20, 85, _white, "X  : Toggle Save Support");
	switch (settings.save) {
		case FALSE: draw_string (240, 85, _white, "[OFF]");
			break;
		case TRUE: draw_string (240, 85, _white, "[ON]");
			break;
	}

	timer_sleep (500);

	while (1) {
		if (!(getButtons(0).buttons & CONT_START)) {
			if (settings.save) {
				save_user_settings(vmu_mem1);
			}
			load_main_menu();
		}
		else if (!(getButtons(0).buttons & CONT_DPAD_UP)) {
			if (settings.frame_skip < 4) {
				settings.frame_skip++;
			}
			do_options();
		}
		else if (!(getButtons(0).buttons & CONT_DPAD_DOWN)) {
			if (settings.frame_skip > 0) {
				settings.frame_skip--;
			}
			do_options();
		}
		else if (!(getButtons(0).buttons & CONT_Y)) {
			if (settings.cont_scheme == 0) {
				settings.cont_scheme++;
				_CONT_A = CONT_B;
				_CONT_B = CONT_A;
			}
			else if (settings.cont_scheme == 1) {
				settings.cont_scheme++;
				_CONT_A = CONT_B;
				_CONT_B = CONT_X;	
			}
			else {
				settings.cont_scheme = 0;
				_CONT_A = CONT_A;
				_CONT_B = CONT_X;
			}
			do_options();
		}
		else if (!(getButtons(0).buttons & CONT_A)) {
			if (settings.sound_on == TRUE) {
				settings.sound_on = FALSE;
			}
			else {
				settings.sound_on = TRUE;
			}
			do_options();
		}
		else if (!(getButtons(0).buttons & CONT_B)) {
			if (settings.player_2 == FALSE) {
				mcont2 = maple_controller_addr(SECOND_DEVICE);
				if (mcont2) {
					settings.player_2 = TRUE;
				}
			}
			else {
				settings.player_2 = FALSE;
			}
			do_options();
		}
		else if (!(getButtons(0).buttons & CONT_X)) {
			if (settings.save == TRUE) {
				settings.save = FALSE;
			}
			else {
				settings.save = TRUE;
			}
			do_options();
		}
		else {
			timer_sleep (25);
		}
		
	}
}

void draw_game_name (char *game_name, uint8 y_pos, uint8 x_pos, uint8 selected) {
	
	if (selected) {
		draw_string (x_pos, y_pos, _yellow, game_name); 	
	}
	else {
		draw_string (x_pos, y_pos, _white, game_name); 
	}
}

void draw_game_names (int position, int num_games) {
	
	display_bmp(game_menu_bmp);
	int y_pos = 10;
	int x_pos = 75;
	int display = position;

	if (num_games < 20) {
		for (int i=0; i<num_games; i++) {
			if (i == position)
				draw_game_name (game_names[i], y_pos, x_pos, TRUE);
			else 
				draw_game_name (game_names[i], y_pos, x_pos, FALSE);

			y_pos += 5;
		}
	}
	else {
		for (int i=0; i<20; i++) {
			if (display == position) { 
				draw_game_name (game_names[display], y_pos, x_pos, TRUE);
			}
			else {
				draw_game_name (game_names[display], y_pos, x_pos, FALSE);
			}
	
			y_pos += 5;

			if (display == (num_games-1)) {
				display = 0;
			}
			else {
				display++;
			}
			
		}
	}
}

void do_game_menu() {

	int temp;
	int position = 0;
	int num_games = 0;
	int exit_menu = FALSE;
	char *temp_char_ptr;

	DIR *d = (DIR *)malloc(1);
	struct dirent *de = (dirent *)malloc(1);

	d = cd_opendir ("/GAMES/");

	while (!d) {
		//vid_clear (255,255,255);
		display_bmp (no_game_bmp);
		while ((getButtons(0).buttons & CONT_A)) {
			timer_sleep (200);
		}
		timer_sleep(200);
		d = cd_opendir ("/GAMES/");
	}

	for (int i=0; i < 2048; i++) {
		de = cd_readdir (d);
		if (de != (dirent *)NULL) {
			de->d_name[de->d_size + 1] = '\0';
			strcpy (game_names[i], de->d_name);
			num_games++;
		}
		else {
			temp = cd_closedir (d);
			strcpy (game_names[i], "'\0'");
			break;
		}
		timer_sleep (10);
	}

	vid_clear (0, 0, 0);

	draw_game_names (position, num_games);

	timer_sleep (100);

	while (1) {

		if (num_games == 0) {
			dc_print ("There are no games in the GAMES directory");
			timer_sleep (2000);
			break;
		}

		if (!(getButtons(0).buttons & CONT_DPAD_UP)) {
			if (position == 0) {
				position = num_games - 1;
			}
			else {
				position--;
			}
			draw_game_names(position, num_games);
		}
		else if (!(getButtons(0).buttons & CONT_DPAD_DOWN)) {
			if (position == (num_games - 1)) {
				position = 0;	
			}
			else {
				position++;
			}
			draw_game_names(position, num_games);
		}
		else if (!(getButtons(0).buttons & CONT_DPAD_LEFT)) {
			
			for (int i=0; i<20; i++) {
				if (position == 0) {
					position = num_games - 1;
				}
				else {
					position--;
				}
			}

			draw_game_names(position, num_games);
		}
		else if (!(getButtons(0).buttons & CONT_DPAD_RIGHT)) {
			
			for (int i=0; i<20; i++) {
				if (position == (num_games - 1)) {
					position = 0;
				}
				else {
					position++;
				}
			}

			draw_game_names(position, num_games);
		}
		else if (!(getButtons(0).buttons & CONT_START)) {
			run_game (game_names[position], TRUE);
		}

		else if (getButtons(0).rtrig  & getButtons(0).ltrig) {
			break;
		}
		if (exit_game_loop) {
			exit_game_loop = FALSE;
			break;
		}
		timer_sleep (50);
	}

	load_main_menu();
}

void do_credits() {
	
	display_bmp(credits_bmp);

	timer_sleep (10000);
	load_main_menu();
}

void run_game(char *game_name, uint8 in_game_dir) {

	snd_init();
	NES *emu = (NES *)NULL;
	null_sound_mgr *no_sound = (null_sound_mgr *)NULL;
	dc_NES_screen_mgr *scr_mgr = (dc_NES_screen_mgr *)NULL;

	vid_clear (0, 0, 0);
	no_sound = new null_sound_mgr();
	scr_mgr = new dc_NES_screen_mgr();

	char *game_value = (char *)malloc(256);
	if (in_game_dir) {
		game_value = strcpy (game_value, "GAMES/");
		game_value = strcat (game_value, game_name);
	}
    else {
		game_value = strcpy (game_value, game_name);	
	}

	emu = new NES (game_value, scr_mgr, no_sound);

	scr_mgr->setParentNES((NES*)emu);
	emu->set_pad1(&pad1);
    emu->set_pad2(&pad2);

	vid_clear (0, 0, 0);
	vid_empty();

	if (!emu->invalid_mapper) {

	if (settings.sound_on) {

		snd_init();

		snd_load_arm(NDC_sound, sizeof(NDC_sound));

		timer_sleep (1500);

		snd_memset (0x11000, 0, (735*24));

		*start_sound = TRUE;

	}

	if (settings.frame_skip == 0)  {
	
		while (1) {
			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);

			if (exit_game_loop) {
				break;
			}
		}
	}
	else if (settings.frame_skip == 1) {

		while (1) {
			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			if (exit_game_loop) {
				break;
			}
		}
	}
	else if (settings.frame_skip == 2) {
		while (1) {
			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			if (exit_game_loop) {
				break;
			}
		}
	}
	else if (settings.frame_skip == 3) {
		while (1) {
			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);
			poll_input();
			emu -> emulate_frame_skip (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame (settings.sound_on);
			poll_input();
			emu -> emulate_frame_skip (settings.sound_on);
			emu -> emulate_frame_skip (settings.sound_on);

			if (exit_game_loop) {
				break;
			}
		}
	}
	else { // frame_skip = 4

		while (1) {
			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip(settings.sound_on);
			emu -> emulate_frame_skip(settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip(settings.sound_on);
			emu -> emulate_frame_skip(settings.sound_on);

			poll_input();
			emu -> emulate_frame (settings.sound_on);
			emu -> emulate_frame_skip(settings.sound_on);
			emu -> emulate_frame_skip(settings.sound_on);

			if (exit_game_loop) {
				break;
			}
		}
	}

//_delete_nes:
	}
	else {
		exit_game_loop = TRUE;
	}

	vid_set_start ((uint32)vram_s);
	//dc_print ("got here");
	*stop_sound = TRUE;

	if (settings.save) {
		emu -> Save_SaveRAM ();
	}

	delete (no_sound);
//	dc_print ("delete 1");
	delete (scr_mgr);
//	dc_print ("delete 2");
	delete (emu);
//	dc_print ("delete 3");
//	while (1) {}
	//dc_print ("The exit worked");

}

bool default_game_exists () {

	// directory types
	DIR *d;
	struct dirent *de;

	// open the games directory
	d = cd_opendir ("/GAMES/");

	// there should be no reason that this happens,
	// but you never know!!!
	if (!d) return FALSE;

	// read all enties in the root directory
	while (1) {
		de = cd_readdir (d);
		if (de != (dirent *)NULL) {
			if (strcmp (de->d_name, "GAME.NES") == 0) {
				// found the default game
				cd_closedir (d);
				return TRUE;
			}
		}
		else {
			// done reading all entries
			cd_closedir (d);
			// exit the while loop
			break;
		}
		// give the maple bus some time to breath
		timer_sleep (10);
	}
	
	// didn't find the default game
	return FALSE;

}

extern "C" void dc_main() {
	// Initialize Dreamcast hardware
	serial_disable();

	dc_setup(DM_320x240, PM_RGB565);

	load_font();

	// allocate memory for bmp images
	no_game_bmp = (uint8 *)malloc (240*320*3);
	credits_bmp = (uint8 *)malloc (240*320*3);
	menu_bmp = (uint8 *)malloc (240*320*3);
	game_menu_bmp = (uint8 *)malloc (240*320*3);
	options_bmp = (uint8 *)malloc (240*320*3);

	// display disclaimer
	load_bmp("/PICS/STARTUP.BMP", credits_bmp);
	display_bmp(credits_bmp);

	// load bmp images into memory
	// they may not be on a users game cd
	load_bmp ("/PICS/MENU.BMP", menu_bmp);
	load_bmp ("/PICS/CREDITS.BMP", credits_bmp);
	load_bmp ("/PICS/NOGAMECD.BMP", no_game_bmp);
	load_bmp ("/PICS/MENU_SELECTION.BMP", game_menu_bmp);
	load_bmp ("/PICS/OPTIONS.BMP", options_bmp);

	//timer_sleep (3000);

	// get the addresses of devices
	mvmu1 = maple_lcd_addr(FIRST_DEVICE);
	mvmu2 = maple_lcd_addr(SECOND_DEVICE);

	mcont1 = maple_controller_addr(FIRST_DEVICE);
	mcont2 = maple_controller_addr(SECOND_DEVICE);

	vmu_mem1 = maple_vmu_addr(FIRST_DEVICE);
	vmu_mem2 = maple_vmu_addr(SECOND_DEVICE);

	// if vmu is present, draw to the screen
    if (mvmu1) {
		vmu_icon_draw (vmu_welcome_xpm, mvmu1);
	}

	if (mvmu2) {
		vmu_icon_draw (vmu_welcome_xpm, mvmu2);
	}

	// load the default user settings
	load_default_settings();

	// if a vmu is present, check it for a nesterdc
	// save file
	if (vmu_mem1) {
	//	dc_print ("got here");
		check_user_settings(vmu_mem1);
	}

	// allocate memory once for 2048 roms
	for (int i=0; i<2048; i++) {
		game_names[i] = (char *)malloc(256);
	}

	// check for the default game
	if (default_game_exists()) {
		run_game ("GAME.NES", TRUE);
	}

	load_main_menu();
	
}


