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

	state.c

	Save / Load state

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

#ifdef SAVE_STATE

#include "ncdz.h"
#include "zlib/zlib.h"
#include <time.h>

#define MAX_STATE	0x200

#define SCREEN_STSIZE	(152 * 112 * 2)

// state data size (include safety area)
#define M68K_STSIZE		0x100
#define Z80_STSIZE		0x80
#define INPUT_STSIZE	0x80
#define TIMER_STSIZE	0x100
#define DRIVER_STSIZE	0x500
#define SOUND_STSIZE	0x400
#define CDDA_STSIZE		0x100
#define VIDEO_STSIZE	0x24100
#define LOAD_STSIZE		(0x40 * MAX_STATE + 4)
#define PRGRAM_STSIZE	0x200000
#define Z80RAM_STSIZE	0x10000

// state write offset
#define M68K_STOFFS		(0)
#define Z80_STOFFS		(M68K_STOFFS   + M68K_STSIZE)
#define INPUT_STOFFS	(Z80_STOFFS    + Z80_STSIZE)
#define TIMER_STOFFS	(INPUT_STOFFS  + INPUT_STSIZE)
#define DRIVER_STOFFS	(TIMER_STOFFS  + TIMER_STSIZE)
#define SOUND_STOFFS	(DRIVER_STOFFS + DRIVER_STSIZE)
#define CDDA_STOFFS		(SOUND_STOFFS  + SOUND_STSIZE)
#define VIDEO_STOFFS	(CDDA_STOFFS   + CDDA_STSIZE)
#define LOAD_STOFFS		(VIDEO_STOFFS  + VIDEO_STSIZE)
#define PRGRAM_STOFFS	(LOAD_STOFFS   + LOAD_STSIZE)
#define Z80RAM_STOFFS	(PRGRAM_STOFFS + PRGRAM_STSIZE)

// total size
#define STATE_SIZE		(Z80RAM_STOFFS + Z80RAM_STSIZE)

#define STATE_SAFETY	0x100


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

u16 thumbnail_buf[152 * 112 + STATE_SAFETY];
char date_str[16];
char time_str[16];
char stver_str[16];


/***************************************************************************
	Private structure
 **************************************************************************/

struct state_t
{
	char name[16];
	u16 type;
	u16 bank;
	u32 offset;
	u32 length;;
	u32 start;;
	u32 end;;
	struct state_t *prev;
	struct state_t *next;
//	struct state_t *upload;
//	u32 upload_src;
};

static struct state_t state_data[MAX_STATE];
static struct state_t *state_head;
static struct state_t *state_free_head;


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

static const char *state_version = "NCDZSV03";
static int state_count;
static u8 inbuf[STATE_SIZE + STATE_SAFETY];
static u8 outbuf[STATE_SIZE + STATE_SAFETY];


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

static u8 *state_set_save_offset(int offset);
static u8 *state_set_load_offset(int offset);
static u32 do_compress(u32 size);
static int do_uncompress(u32 size);
static int state_save_loadfile(u8 *state_buf);
static int state_load_loadfile(u8 *state_buf);


/*------------------------------------------------------
	Set next block offset
 -----------------------------------------------------*/

void state_init(void)
{
	int i;

	memset(&state_data, 0, sizeof(state_data));

	for (i = 0; i < MAX_STATE - 1; i++)
		state_data[i].next = &state_data[i + 1];

	state_data[MAX_STATE - 1].next = NULL;

	state_head = NULL;
	state_free_head = &state_data[0];
	state_count = 0;
}


/*------------------------------------------------------
	Save state data
 -----------------------------------------------------*/

int state_save(int slot)
{
	u32 size;
	FILE *fp;
	char path[MAX_PATH];

	sprintf(path, "%sstate/%s.sv%d", launchDir, game_name, slot);

	memset(inbuf, 0, sizeof(inbuf));
	memset(outbuf, 0, sizeof(outbuf));

	sceIoRemove(path);

	if ((fp = fopen(path, "wb")) != NULL)
	{
		u8 *in;
		pspTime nowtime;

		sceRtcGetCurrentClockLocalTime(&nowtime);

		// write header
		draw_state_menu(2);
		fwrite(state_version, 1, 8, fp);
		fwrite(&nowtime, 1, 16, fp);
		fwrite(inbuf, 1, 42, fp);

		// save thumbnail
		draw_state_menu(3);
		state_save_screen(inbuf);
		draw_state_menu(4);
		if ((size = do_compress(SCREEN_STSIZE)) == 0)
		{
			fclose(fp);
			sceIoRemove(path);
			return 0;
		}
		draw_state_menu(5);
		fwrite(&size, 1, 4, fp);
		fwrite(outbuf, 1, size, fp);

		// save state
		draw_state_menu(6);
		memset(inbuf, 0, sizeof(inbuf));
		in = state_set_save_offset(M68K_STOFFS);   state_save_m68000(in);
		in = state_set_save_offset(Z80_STOFFS);    state_save_z80(in);
		in = state_set_save_offset(INPUT_STOFFS);  state_save_inputport(in);
		in = state_set_save_offset(TIMER_STOFFS);  state_save_timer(in);
		in = state_set_save_offset(DRIVER_STOFFS); state_save_driver(in);
		in = state_set_save_offset(SOUND_STOFFS);  state_save_ym2610(in);
		in = state_set_save_offset(CDDA_STOFFS);   state_save_cdda(in);
		in = state_set_save_offset(VIDEO_STOFFS);  state_save_vidhrdw(in);
		in = state_set_save_offset(LOAD_STOFFS);   state_save_loadfile(in);
		in = state_set_save_offset(PRGRAM_STOFFS); state_save_byte(neogeo_prg_memory, 0x200000);
		in = state_set_save_offset(Z80RAM_STOFFS); state_save_byte(neogeo_z80_memory, 0x10000);

		draw_state_menu(7);
		if ((size = do_compress(STATE_SIZE)) == 0)
		{
			fclose(fp);
			sceIoRemove(path);
			return 0;
		}
		draw_state_menu(8);
		fwrite(&size, 1, 4, fp);
		fwrite(outbuf, 1, size, fp);
		fclose(fp);

		return 1;
	}
	else
	{
		ErrorMsg("ERROR: Could not open file \"%s.sv%d\"", game_name, slot);
	}
	return 0;
}


/*------------------------------------------------------
	Load state data
 -----------------------------------------------------*/

int state_load(int slot)
{
	u32 size;
	FILE *fp;
	char path[MAX_PATH];

	sprintf(path, "%sstate/%s.sv%d", launchDir, game_name, slot);

	memset(inbuf, 0, sizeof(inbuf));
	memset(outbuf, 0, sizeof(outbuf));

	if ((fp = fopen(path, "rb")) != NULL)
	{
		u8 *out;
		char version[12];

		// read header
		draw_state_menu(2);
		fread(version, 1, 8, fp);
		fread(outbuf, 1, 58, fp);

		// skip thumbnail
		draw_state_menu(3);
		fread(&size, 1, 4, fp);
		fread(outbuf, 1, size, fp);

		// load state
		draw_state_menu(4);
		fread(&size, 1, 4, fp);
		fread(inbuf, 1, size, fp);
		fclose(fp);

		draw_state_menu(5);
		if (do_uncompress(size) == 0)
			return 0;

		draw_state_menu(6);
		out = state_set_load_offset(M68K_STOFFS);   state_load_m68000(out);
		out = state_set_load_offset(Z80_STOFFS);    state_load_z80(out);
		out = state_set_load_offset(INPUT_STOFFS);  state_load_inputport(out);
		out = state_set_load_offset(TIMER_STOFFS);  state_load_timer(out);
		out = state_set_load_offset(DRIVER_STOFFS); state_load_driver(out);
		out = state_set_load_offset(SOUND_STOFFS);  state_load_ym2610(out);
		out = state_set_load_offset(CDDA_STOFFS);   state_load_cdda(out);
		out = state_set_load_offset(VIDEO_STOFFS);  state_load_vidhrdw(out);
		out = state_set_load_offset(LOAD_STOFFS);   state_load_loadfile(out);
		out = state_set_load_offset(PRGRAM_STOFFS); state_load_byte(neogeo_prg_memory, 0x200000);
		out = state_set_load_offset(Z80RAM_STOFFS); state_load_byte(neogeo_z80_memory, 0x10000);

		return 1;
	}
	else
	{
		ErrorMsg("ERROR: Could not open file \"%s.sv%d\"", game_name, slot);
	}
	return 0;
}


/*------------------------------------------------------
	Load snap shot thumbnail
 -----------------------------------------------------*/

int state_load_screen(int slot)
{
	FILE *fp;
	char path[MAX_PATH];

	sprintf(path, "%sstate/%s.sv%d", launchDir, game_name, slot);

	memset(inbuf, 0, sizeof(inbuf));
	memset(outbuf, 0, sizeof(outbuf));

	if ((fp = fopen(path, "rb")) != NULL)
	{
		u32 size;

		memset(stver_str, 0, 16);
		fread(stver_str, 1, 8, fp);

		if (!strcmp(stver_str, "NCDZST01") || !strcmp(stver_str, "NCDZSV01") || !strcmp(stver_str, "NCDZSV02"))
		{
			time_t savetime;
			struct tm *t;

			fread(&savetime, 1, 4, fp);

			if (savetime != 0)
			{
				t = localtime(&savetime);

				switch (neogeo_region)
				{
				case 0:
					savetime += 9 * 60 * 60;
					t = localtime(&savetime);
					sprintf(date_str, "%04d/%02d/%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday);
					sprintf(time_str, "%02d:%02d:%02d (JST)", t->tm_hour, t->tm_min, t->tm_sec);
					break;

				case 1:
					sprintf(date_str, "%02d/%02d/%04d", t->tm_mon + 1, t->tm_mday, t->tm_year + 1900);
					sprintf(time_str, "%02d:%02d:%02d (GMT)", t->tm_hour, t->tm_min, t->tm_sec);
					break;

				case 2:
					sprintf(date_str, "%02d/%02d/%04d", t->tm_mday, t->tm_mon + 1, t->tm_year + 1900);
					sprintf(time_str, "%02d:%02d:%02d (GMT)", t->tm_hour, t->tm_min, t->tm_sec);
					break;
				}
			}

			fread(outbuf, 1, 54, fp);
		}
		else
		{
			pspTime t;

			fread(&t, 1, 16, fp);

			switch (neogeo_region)
			{
			case 0: sprintf(date_str, "%04d/%02d/%02d", t.year, t.month, t.day); break;
			case 1: sprintf(date_str, "%02d/%02d/%04d", t.month, t.day, t.year); break;
			case 2: sprintf(date_str, "%02d/%02d/%04d", t.day, t.month, t.year); break;
			}

			sprintf(time_str, "%02d:%02d:%02d", t.hour, t.minutes, t.seconds);

			fread(outbuf, 1, 42, fp);
		}

		fread(&size, 1, 4, fp);
		fread(inbuf, 1, size, fp);
		fclose(fp);

		if (do_uncompress(size))
			memcpy(thumbnail_buf, outbuf, SCREEN_STSIZE);
		else
			memset(thumbnail_buf, 0, SCREEN_STSIZE);

		return 1;
	}
	else
	{
		ErrorMsg("ERROR: Could not open file \"%s.sv%d\"", game_name, slot);
	}

	return 0;
}


/*------------------------------------------------------
	Set next block offset
 -----------------------------------------------------*/

static u8 *state_set_save_offset(int offset)
{
	return inbuf + offset;
}

static u8 *state_set_load_offset(int offset)
{
	return outbuf + offset;
}


/*------------------------------------------------------
	Do compress (gzip)
 -----------------------------------------------------*/

static u32 do_compress(u32 size)
{
	z_stream stream;
	u32 outsize;

	stream.next_in   = inbuf;
	stream.avail_in  = size;
	stream.next_out  = outbuf;
	stream.avail_out = STATE_SIZE;

	stream.zalloc = Z_NULL;
	stream.zfree  = Z_NULL;
	stream.opaque = Z_NULL;

	if (deflateInit(&stream, 9) != Z_OK)
	{
		ErrorMsg("ERROR: deflateInit() %s", stream.msg);
		return 0;
	}

	if (deflate(&stream, Z_FINISH) != Z_STREAM_END)
	{
		ErrorMsg("ERROR: deflate() %s", stream.msg);
		deflateEnd(&stream);
		return 0;
	}

	outsize = stream.total_out;

	if (deflateEnd(&stream) != Z_OK)
	{
		ErrorMsg("ERROR: deflateEnd() %s", stream.msg);
		return 0;
	}

	return outsize;
}


/*------------------------------------------------------
	Do uncompress (gzip)
 -----------------------------------------------------*/

static int do_uncompress(u32 size)
{
	z_stream stream;

	stream.next_in   = inbuf;
	stream.avail_in  = size;
	stream.next_out  = outbuf;
	stream.avail_out = STATE_SIZE;

	stream.zalloc = Z_NULL;
	stream.zfree  = Z_NULL;

	if (inflateInit(&stream) != Z_OK)
	{
		ErrorMsg("ERROR: inflateInit() %s", stream.msg);
		return 0;
	}

	if (inflate(&stream, Z_FINISH) != Z_STREAM_END)
	{
		ErrorMsg("ERROR: inflate() %s", stream.msg);
		inflateEnd(&stream);
		return 0;
	}

	if (inflateEnd(&stream) != Z_OK)
	{
		ErrorMsg("ERROR: inflateEnd() %s", stream.msg);
		return 0;
	}
	return 1;
}


/*------------------------------------------------------
	Save load files history
 -----------------------------------------------------*/

static int state_save_loadfile(u8 *state_buf)
{
	u8 *in = state_buf;
	int i;
	struct state_t *p = state_head;

	state_save_long(&state_count, 1);

	for (i = 0; i < state_count; i++)
	{
		state_save_byte(p->name, 16);
		state_save_word(&p->type, 1);
		state_save_word(&p->bank, 1);
		state_save_long(&p->offset, 1);
		state_save_long(&p->length, 1);
		state_save_long(&p->start, 1);
		state_save_long(&p->end, 1);
		p = p->next;
	}
}


/*------------------------------------------------------
	Load load files history
 -----------------------------------------------------*/

static int state_load_loadfile(u8 *state_buf)
{
	u8 *out = state_buf;
	int i;
	struct state_t *p, *last = NULL;

	zip_open(game_dir);

	state_init();
	state_load_long(&state_count, 1);

	for (i = 0; i < state_count; i++)
	{
		p = state_free_head;

		if (last)
			last->next = p;
		else
			state_head = p;

		state_free_head = state_free_head->next;

		p->prev = last;
		p->next = NULL;

		state_load_byte(p->name, 16);
		state_load_word(&p->type, 1);
		state_load_word(&p->bank, 1);
		state_load_long(&p->offset, 1);
		state_load_long(&p->length, 1);
		state_load_long(&p->start, 1);
		state_load_long(&p->end, 1);

		state_load_file(p->type, p->name, p->bank, p->offset, p->length);

		last = p;
	}

	zip_close();
}


/*------------------------------------------------------
	Check load files history
 -----------------------------------------------------*/

static int state_check_tree(int type, u32 start, u32 end)
{
	struct state_t *p = state_head;
	struct state_t *q;

	if (state_count == MAX_STATE)
		return 0;

	while (p)
	{
		if (type == p->type)
		{
			if (start <= p->start && end >= p->end)
			{
				if (p->prev)
					p->prev->next = p->next;
				else
					state_head = p->next;

				if (p->next)
					p->next->prev = p->prev;

				q = p->next;
				p->next = state_free_head;
				state_free_head = p;
				state_count--;
				p = q;
				continue;
			}
		}

		p = p->next;
	}

	return 1;
}


/*------------------------------------------------------
	Insert load files history
 -----------------------------------------------------*/

static void state_insert_tree(int type, const char *fname, int bank, u32 offset, u32 length, u32 start, u32 end)
{
	struct state_t *p, *last = NULL;

	if (state_check_tree(type, start, end) == 0)
	{
		ErrorMsg("State is MAX!!");
		return;
	}

	p = state_head;
	while (p)
	{
		last = p;
		p = p->next;
	}

	p = state_free_head;

	if (last)
		last->next = p;
	else
		state_head = p;

	state_free_head = state_free_head->next;

	memset(p->name, 0, 16);
	strcpy(p->name, fname);
	p->prev   = last;
	p->next   = NULL;
	p->type   = type;
	p->bank   = bank;
	p->offset = offset;
	p->length = length;
	p->start  = start;
	p->end    = end;

	state_count++;
}


/*------------------------------------------------------
	Register FIX file
 -----------------------------------------------------*/

void state_fix_mark_loadfile(const char *fname, u32 offset, u32 length)
{
	u32 start, end;

	start = offset >> 1;
	end   = start + length - 1;

	state_insert_tree(FIX_TYPE, fname, 0, offset, length, start, end);
}


/*------------------------------------------------------
	Register SPR file
 -----------------------------------------------------*/

void state_spr_mark_loadfile(const char *fname, int bank, u32 offset, u32 length)
{
	u32 start, end;

	start = offset + (bank << 20);
	end   = start + length - 1;

	state_insert_tree(SPR_TYPE, fname, bank, offset, length, start, end);
}

/*------------------------------------------------------
	Register PCM file
 -----------------------------------------------------*/

void state_pcm_mark_loadfile(const char *fname, int bank, u32 offset, u32 length)
{
	u32 start, end;

	start = (offset >> 1) + (bank << 19);
	end   = start + length - 1;

	state_insert_tree(PCM_TYPE, fname, bank, offset, length, start, end);
}

#endif
