/***************************************************************************

	ncdz.c

	NEOGEO CDZ emulation core.

***************************************************************************/

#include "ncdz.h"
#include <stdarg.h>


#define FRAMESKIP_LEVELS	12
#define FRAMES_TO_SKIP		20


/***************************************************************************
	Global variables
 **************************************************************************/

// config values
int neogeo_region;
int neogeo_showfps;
int neogeo_speedlimit;
int neogeo_autoframeskip;
int neogeo_autoframeskip_limit;
int neogeo_frameskip;
int neogeo_vsync;

int neogeo_boot_bios;
u32 frames_displayed;

char global_error[80];


/***************************************************************************
	Private variables
 **************************************************************************/

static char fpsbuf[32];
static int frameskip;
static int frameskip_adjust;
static int frameskip_counter;
static int fps_framecount;
static int rendered_framecount;
static double speed;
static double limit;
static double fps;
static double refresh_rate;
static TICKER prev;
static TICKER frame_base;

static int cpu_reset_flag;
static int warming_up;

static char popup_message[256];
static int popup_counter = 0;

#ifdef DEBUG
static FILE *errorlog = NULL;
#endif

#include "binary/startup.c"


/***************************************************************************
	Function prototypes
 **************************************************************************/

static int neogeo_init(void);
static void cpu_run(void);
static void update_frame(void);
static void show_popup(void);
static void log_open(void);
static void log_close(void);


/*--------------------------------------------------------
	Initialize frameskip variables
 -------------------------------------------------------*/

void init_autoframeskip(void)
{
	frameskip = neogeo_autoframeskip ? 0 : neogeo_frameskip;
	frameskip_adjust = 0;
	frameskip_counter = 0;

	fps_framecount = FRAMESKIP_LEVELS - 1;
	rendered_framecount = FRAMESKIP_LEVELS - 1;

	limit = 90.0 + (double)neogeo_autoframeskip_limit;

	if (neogeo_vsync)
		refresh_rate = PSP_REFRESHRATE;
	else
		refresh_rate = 60.0;

	warming_up = 1;
}


/*--------------------------------------------------------
	Show "Now loading" screen
 -------------------------------------------------------*/

void show_loading_image(void)
{
	char path[MAX_PATH];
	int found;

	video_clear_screen();

#ifdef SUPPORT_BMP
	sprintf(path, "%s%s", launchDir, "loading.png");
	if ((found = load_png(path)) == 0)
	{
		sprintf(path, "%s%s", launchDir, "loading.bmp");
		if ((found = load_bmp(path)) == 0)
		{
			font_print_shadow_center(136-10, COLOR_WHITE, "Now Loading...", 4);
		}
	}
#else
	sprintf(path, "%s%s", launchDir, "loading.png");
	if ((found = load_png(path)) == 0)
	{
		font_print_shadow_center(136-10, COLOR_WHITE, "Now Loading...", 4);
	}
#endif

	video_flip_screen(1);
}


/*--------------------------------------------------------
	Start NEOGEO CD emulation
 -------------------------------------------------------*/

void neogeo_main(void)
{
#ifdef DEBUG
	log_open();
#endif

	popup_counter = 0;

	while (bLoop >= LOOP_RESTART)
	{
		bLoop = LOOP_EXEC;

		frames_displayed = 0;
		global_error[0] = '\0';

		video_clear_screen();

		if (memory_init())
		{
			if (neogeo_sound_init())
			{
				if (cdda_init())
				{
					input_init();
					cdrom_init();

					if (neogeo_init())
					{
						cpu_run();
						save_keyconfig(game_name);
					}

					neogeo_boot_bios = 0;

					cdrom_shutdown();
					input_shutdown();
				}
				cdda_shutdown();
			}
			neogeo_sound_exit();
		}
		memory_shutdown();

		if (global_error[0])
		{
			ErrorMsg(global_error);
			bLoop = LOOP_BROWSER;
		}
	}

	popup_counter = 0;

#ifdef DEBUG
	log_close();
#endif
}


/*--------------------------------------------------------
	Initialize NEOGEO CD emulation
 -------------------------------------------------------*/

int neogeo_init(void)
{
#ifdef SAVE_STATE
	state_init();
#endif
	timer_init();

	m68000_init();
	z80_init();
	cpu_reset_flag = 0;

	neogeo_driver_init();
	neogeo_video_init();

	memset(neogeo_prg_memory, 0, 0x200000);
	memset(neogeo_z80_memory, 0, 0x10000);
	swab(startup_bin, neogeo_prg_memory + 0x10f300, 0xd00);

	m68k_write_memory_32(0x120002, 0xffffffff);
	m68k_write_memory_32(0x11c808, 0xc0c760);	// load screen setup function
	m68k_write_memory_32(0x11c80c, 0xc0c814);	// load screen progress function
	m68k_write_memory_32(0x11c810, 0xc190e2);	// load screen default anime data

	if (!neogeo_boot_bios)
	{
		if (!cdrom_process_ipl())
		{
			strcpy(global_error, "ERROR: Error while processing IPL.TXT.");
			return 0;
		}

		neogeo_check_game();
		load_keyconfig(game_name);

		if (NGH_NUMBER(0x0085))
		{
			u16 *mem16 = (u16 *)neogeo_prg_memory;

			mem16[0x132020 >> 1] = 0x4ef9;
			mem16[0x132022 >> 1] = 0x00c0;
			mem16[0x132024 >> 1] = 0xdb60;

			mem16[0x132026 >> 1] = 0x4ef9;
			mem16[0x132028 >> 1] = 0x00c0;
			mem16[0x13202a >> 1] = 0xdb6a;
		}
	}
	else
	{
		game_name = games[99].name;
		game_index = 99;
		neogeo_ngh_number = 0;

		load_keyconfig(game_name);
	}

	snapshot_init();

	return 1;
}


/*--------------------------------------------------------
	Reset NEOGEO CD emulation
 -------------------------------------------------------*/

void neogeo_reset(void)
{
	m68000_reset(cpu_reset_flag);
	z80_reset(cpu_reset_flag);
	timer_reset();

	if (game_index != 99)
	{
		if (cpu_reset_flag == 0)
		{
			m68000_set_reg(M68K_PC, 0xc0a822);

			m68k_write_memory_32(0x108000 + 0x76ee, m68k_read_memory_32(0x68));	// IRQ vector
//			m68k_write_memory_32(0x108000 + 0x76f2, m68k_read_memory_32(0x64));	// VBLANK

			m68k_write_memory_8(0x108000 + 0x76d9, 0x01);
			m68k_write_memory_8(0x108000 + 0x76c3, 0x01);
			m68k_write_memory_8(0x108000 + 0x764b, 0xff);
			m68k_write_memory_8(0x108000 + 0x76f6, 0xff);
			m68k_write_memory_8(0x108000 + 0x7e85, 0x01);
		}
		else
		{
			m68000_set_reg(M68K_PC, 0x000122);

			m68k_write_memory_8(0x108000 + 0x7d80, 0x82);
			m68k_write_memory_8(0x108000 + 0x7daf, 0x01);
			m68k_write_memory_8(0x108000 + 0x7ee1, 0x0a);
			m68k_write_memory_8(0x108000 + 0x7675, 0x01);
			m68k_write_memory_8(0x108000 + 0x7ebf, 0x00);
			m68k_write_memory_32(0x108000 + 0x7db6, 0);
			m68k_write_memory_32(0x108000 + 0x7dba, 0);
		}

		cpu_reset_flag = 1;
	}

	m68k_write_memory_16(0xff011c, ~(neogeo_region << 8));
	m68k_write_memory_8(0x10fd83, neogeo_region);

	neogeo_driver_reset();
	neogeo_video_reset();
	neogeo_sound_reset();

	cdda_current_track = 0;

	init_autoframeskip();

	popup_counter = 0;

	bLoop = LOOP_EXEC;
}


/*--------------------------------------------------------
	Restart NEOGEO CD emulation
 -------------------------------------------------------*/

void neogeo_restart(void)
{
	bLoop = LOOP_RESTART;
}


/*--------------------------------------------------------
	Exit NEOGEO CD emulation (called by BIOS)
 -------------------------------------------------------*/

void neogeo_exit(void)
{
	bLoop = LOOP_BROWSER;
}


/*--------------------------------------------------------
	Run NEOGEO CD emulator
 -------------------------------------------------------*/

static void cpu_run(void)
{
	while (bLoop >= LOOP_RESET)
	{
		neogeo_reset();

		while (bLoop == LOOP_EXEC)
		{
			if (bSleep)
			{
				do
				{
					sceKernelDelayThread(5000000);
				} while (bSleep);

				init_autoframeskip();
			}

			timer_scedule_cpu();

			if (watchdog_counter > 0)
			{
				if (--watchdog_counter == 0)
				{
					ui_popup("reset caused by the watchdog");
					bLoop = LOOP_RESET;
				}
			}

			if (!skip_this_frame())
			{
#ifdef EMULATE_RASTER_EFFECT
				if (driver_type == RASTER)
					neogeo_raster_screenrefresh();
				else
#endif
					neogeo_screenrefresh();

				show_fps();
				show_popup();
			}

			update_frame();

			if (neogeo_loadfinished)
			{
				neogeo_loadfinished = 0;
				video_enable_w(1);
				fix_disable_w(0);
				spr_disable_w(0);
			}

			update_autofire();
			update_inputport();
		}

		video_clear_screen();
		neogeo_sound_mute(1);
		cdda_stop();
	}
}


/*--------------------------------------------------------
	Frame skip table
 -------------------------------------------------------*/

int skip_this_frame(void)
{
	static const int skiptable[FRAMESKIP_LEVELS][FRAMESKIP_LEVELS] =
	{
		{ 0,0,0,0,0,0,0,0,0,0,0,0 },
		{ 0,0,0,0,0,0,0,0,0,0,0,1 },
		{ 0,0,0,0,0,1,0,0,0,0,0,1 },
		{ 0,0,0,1,0,0,0,1,0,0,0,1 },
		{ 0,0,1,0,0,1,0,0,1,0,0,1 },
		{ 0,1,0,0,1,0,1,0,0,1,0,1 },
		{ 0,1,0,1,0,1,0,1,0,1,0,1 },
		{ 0,1,0,1,1,0,1,0,1,1,0,1 },
		{ 0,1,1,0,1,1,0,1,1,0,1,1 },
		{ 0,1,1,1,0,1,1,1,0,1,1,1 },
		{ 0,1,1,1,1,1,0,1,1,1,1,1 },
		{ 0,1,1,1,1,1,1,1,1,1,1,1 }
	};

	return skiptable[frameskip][frameskip_counter];
}


/*--------------------------------------------------------
	Update video frame
 -------------------------------------------------------*/

static void update_frame(void)
{
	TICKER this_frame_base;

	neogeo_cdda_check();

	if (warming_up)
	{
		video_wait_vsync();
		prev = ticker() - (TICKER)FRAMESKIP_LEVELS * TICKS_PER_SEC / refresh_rate;
		warming_up = 0;
	}

	if (frameskip_counter == 0)
		this_frame_base = prev + (TICKER)FRAMESKIP_LEVELS * TICKS_PER_SEC / refresh_rate;

	frames_displayed++;
	fps_framecount++;

	if (!skip_this_frame())
	{
		TICKER curr, target;

		curr = ticker();

		if (neogeo_vsync)
		{
			video_wait_vsync();
			curr = ticker();
			video_flip_screen(0);
		}
		else
		{
			video_flip_screen(neogeo_vsync);

			if (neogeo_speedlimit)
			{
				TICKER target = this_frame_base + (TICKER)frameskip_counter * TICKS_PER_SEC / FPS;

				while (curr - target < 0) curr = ticker();
			}
		}

		rendered_framecount++;

		if (frameskip_counter == 0)
		{
			double seconds_elapsed = (double)(curr - prev) * (1.0 / (double)TICKS_PER_SEC);
			double frames_per_sec = (double)fps_framecount / seconds_elapsed;

			speed = 100.0 * frames_per_sec / (double)refresh_rate;
			fps = (double)rendered_framecount / seconds_elapsed;

			prev = curr;
			fps_framecount = 0;
			rendered_framecount = 0;

			if (neogeo_autoframeskip)
			{
				if (neogeo_speedlimit)
				{
					if (speed >= limit)
					{
						frameskip_adjust++;

						if (frameskip_adjust >= 3)
						{
							frameskip_adjust = 0;
							if (frameskip > 0) frameskip--;
						}
					}
					else
					{
						if (speed < 80)
						{
							frameskip_adjust -= (90 - speed) / 5;
						}
						else if (frameskip < 8)
						{
							frameskip_adjust--;
						}

						while (frameskip_adjust <= -2)
						{
							frameskip_adjust += 2;
							if (frameskip < FRAMESKIP_LEVELS - 1)
								frameskip++;
						}
					}
				}
			}
		}
	}

	frameskip_counter = (frameskip_counter + 1) % FRAMESKIP_LEVELS;
}

/*--------------------------------------------------------
	Show FPS
 -------------------------------------------------------*/

void show_fps(void)
{
	if (neogeo_showfps)
	{
		int disp_speed = (int)speed;
		int disp_fps = (int)fps;
		int sx;
		char buf[256];

		if (speed > 999.0) speed = 999.0;
		sprintf(fpsbuf, "%s%2d %3d%% %3dFPS", neogeo_autoframeskip ? "auto" : "fskp", frameskip, (int)disp_speed, (int)disp_fps);

		sx = SCR_WIDTH - (strlen(fpsbuf) << 3);
		small_font_print(sx, 0, 0xffff, fpsbuf, 1);
	}
}


/*--------------------------------------------------------
	Set popup message
 -------------------------------------------------------*/

void ui_popup(const char *text, ...)
{
	va_list arg;

	va_start(arg, text);
	vsprintf(popup_message, text, arg);
	va_end(arg);

	popup_counter = 3 * FPS;
}


/*--------------------------------------------------------
	Show popup message
 -------------------------------------------------------*/

static void show_popup(void)
{
	if (popup_counter > 0)
	{
		small_font_print(0, 0, 0x7fff, popup_message, 1);
		screen_is_dirty = 2;
		popup_counter--;
	}

	if (!scePowerIsBatteryCharging())
	{
		int bat = scePowerGetBatteryLifePercent();

		if (bat < 10)
		{
			static u32 counter = 0;

			counter++;
			if ((counter % 120) < 40)
			{
				char warning[128];

				sprintf(warning, "Battery is low (%d%%). Please charge battery!", bat);
				small_font_print_center(SCR_HEIGHT - 8, COLOR_RED, warning, 1);
				screen_is_dirty = 2;
			}
		}
	}
}


/*--------------------------------------------------------
	Show popup message (user interface)
 -------------------------------------------------------*/

void ui_show_popup(void)
{
	if (popup_counter > 0)
	{
		int sx, sy, ex, ey;
		int width = font_get_string_width(popup_message);

		sx = (SCR_WIDTH - width) >> 1;
		sy = (SCR_HEIGHT - 16) >> 1;
		ex = sx + width;
		ey = sy + 16;

		draw_dialog(sx - 6, sy - 4, ex + 6, ey + 8);
		font_print_shadow_center(sy, COLOR_WHITE, popup_message, 4);

		popup_counter--;
	}
}


#ifdef DEBUG

/*--------------------------------------------------------
	Debug logging
 -------------------------------------------------------*/

static void log_pen(void)
{
	char path[MAX_PATH];

	sprintf(path, "%serror.log", launchDir);
	errorlog = fopen(path, "w");
}


static void log_close(void)
{
	if (errorlog)
	{
		fclose(errorlog);
		errorlog = NULL;
	}
}


void logerror(const char *text, ...)
{
	if (errorlog)
	{
		char buf[256];
		va_list arg;

		va_start(arg, text);
		vsprintf(buf, text, arg);
		va_end(arg);

		fprintf(errorlog, buf);
	}
}

#endif
