#include	<stdlib.h>
#include	<stdio.h>
#include	<string.h>
#include	"gfxfunc.h"
#include	"gb.h"

#define	SAVEGAME_ID	"GBS6"


#define	PAGE_NULL		0
#define	PAGE_MAINRAM	1
#define	PAGE_RAM		2
#define	PAGE_ROM		3

#define	VARDATA(x)	{sizeof(x),&x},
#define	ELEMENTS(x)	(sizeof(x)/sizeof(x[0]))
typedef struct{
	size_t	Size;
	void	*Location;
}STATICDATA;
typedef struct{
	long	Type;
	long	Offset;
	long	Page;
}PAGEINFO;

int		SaveStaticData(STATICDATA *stat, int NStatic, char *name, FILE *f);
int		LoadStaticData(STATICDATA *stat, int NStatic, char *name, FILE *f);
int		SaveSection(void *data, size_t size, char *name, FILE *f);
void	GetPageInfo(PAGEINFO *info, UBYTE *data);
int		LoadSection(void *data, size_t size, char *name, FILE *f);
void	*GetAddress(PAGEINFO *page);

//******************************************//
//											//
//	Static data for GB.C					//
//											//
//******************************************//
extern byte Verbose;     /* Verboseness level                            */
extern byte *RAM,*Page[8];      /* RAM and pointers to Z80 address space 8x8kB) */
extern int  VPeriod;  /* Number of Z80 cycles between VBlanks         */
extern byte UPeriod;     /* Number of VBlanks per screen update          */
extern byte LineDelay;     /* When 1, CMPLINE interrupts are delayed       */
extern byte CheckCRC;     /* When 1, VGB checks cartridge CRC on loading  */
extern char *SaveName;  /* .sav file name                               */
extern byte SIOBuf;             /* Next byte to output via the serial line      */
extern byte SIOFlag;            /* Bit0 -> SIO interrupt should be generated    */
extern byte IMask;              /* A mask to reset an event bit in IFLAGS       */
extern byte MBCType;            /* MBC type: 1 for MBC2, 0 for MBC1             */
extern byte *ROMMap[256];       /* Addresses of ROM banks                       */
extern byte ROMBank;            /* Number of ROM bank currently used            */
extern byte ROMMask;            /* Mask for the ROM bank number                 */
extern int  ROMBanks;           /* Total number of ROM banks                    */
extern byte *RAMMap[256];       /* Addresses of RAM banks                       */ 
extern byte RAMBank;            /* Number of RAM bank currently used            */
extern byte RAMMask;            /* Mask for the RAM bank number                 */
extern int  RAMBanks;           /* Total number of RAM banks                    */
extern byte JoyState;  /* Joystick state: START.SELECT.B.A.D.U.L.R     */
extern byte BPal[4];            /* Background palette                           */
extern byte SPal0[4],SPal1[4];  /* Sprite palettes                              */
extern byte *ChrGen;            /* Character generator                          */
extern byte *BgdTab,*WndTab;    /* Background and window character tables       */
extern byte SprFlag;     /* <>0: sprites were enabled during the frame   */
extern int IFreq;

extern unsigned TimerTestParam;
extern unsigned TimerRecurParam;



STATICDATA	GBStatic[]={
VARDATA(Verbose)     /* Verboseness level                            */
VARDATA(RAM)
VARDATA(Page)      /* RAM and pointers to Z80 address space 8x8kB) */
VARDATA(VPeriod)  /* Number of Z80 cycles between VBlanks         */
VARDATA(UPeriod)     /* Number of VBlanks per screen update          */
VARDATA(LineDelay)     /* When 1, CMPLINE interrupts are delayed       */
VARDATA(CheckCRC)     /* When 1, VGB checks cartridge CRC on loading  */
VARDATA(SaveName)  /* .sav file name                               */
VARDATA(SIOBuf)             /* Next byte to output via the serial line      */
VARDATA(SIOFlag)            /* Bit0 -> SIO interrupt should be generated    */
VARDATA(IMask)              /* A mask to reset an event bit in IFLAGS       */
VARDATA(MBCType)            /* MBC type: 1 for MBC2, 0 for MBC1             */
VARDATA(ROMMap)       /* Addresses of ROM banks                       */
VARDATA(ROMBank)            /* Number of ROM bank currently used            */
VARDATA(ROMMask)            /* Mask for the ROM bank number                 */
VARDATA(ROMBanks)           /* Total number of ROM banks                    */
VARDATA(RAMMap)       /* Addresses of RAM banks                       */ 
VARDATA(RAMBank)            /* Number of RAM bank currently used            */
VARDATA(RAMMask)            /* Mask for the RAM bank number                 */
VARDATA(RAMBanks)           /* Total number of RAM banks                    */
VARDATA(JoyState)  /* Joystick state: START.SELECT.B.A.D.U.L.R     */
VARDATA(BPal)            /* Background palette                           */
VARDATA(SPal0)
VARDATA(SPal1)  /* Sprite palettes                              */
VARDATA(ChrGen)            /* Character generator                          */
VARDATA(BgdTab)
VARDATA(WndTab)    /* Background and window character tables       */
VARDATA(SprFlag)     /* <>0: sprites were enabled during the frame   */
VARDATA(IFreq)
VARDATA(TimerTestParam)
VARDATA(TimerRecurParam)
};
int	NGBStatic=sizeof(GBStatic)/sizeof(GBStatic[0]);

//******************************************//
//											//
//	Static data for Z80.C					//
//											//
//******************************************//
extern reg R;
extern byte CPURunning;
extern int  IPeriod; /* Number of cmds between int. intrpts */
extern byte IntSync;     /* 1 to generate internal interrupts   */
extern byte IFlag;       /* If IFlag==1, gen. int. and set to 0 */
extern int  ICount;          /* Variable used to count CPU cycles   */
extern byte TrapBadOps;


STATICDATA	Z80Static[]={
VARDATA(R)
VARDATA(CPURunning)
VARDATA(IPeriod) /* Number of cmds between int. intrpts */
VARDATA(IntSync)     /* 1 to generate internal interrupts   */
VARDATA(IFlag)       /* If IFlag==1, gen. int. and set to 0 */
VARDATA(ICount)          /* Variable used to count CPU cycles   */
VARDATA(TrapBadOps)
};
int	NZ80Static=sizeof(Z80Static)/sizeof(Z80Static[0]);



int	LoadSnapshot(char *name)
{
	FILE		*f;
	int			j;
	char		SectionName[256];
	PAGEINFO	PageInfo[8], page;
	int			success;

	//	Zero all memory pointers etc..
	RAM=0;
	memset(RAMMap, 0, sizeof(RAMMap));
	memset(ROMMap, 0, sizeof(ROMMap));
	f=0;
	success=0;

	f=fopen(name,"rb");
	if(!f)goto exit;

	if(4!=fread(SectionName, 1, 4, f))goto exit;

	SectionName[4]=0;
	if(strcmp(SectionName,SAVEGAME_ID))goto exit;

	//	load static data
	if(!LoadStaticData(Z80Static, NZ80Static, "Z80D", f))goto exit;
	if(!LoadStaticData(GBStatic, NGBStatic, "GBD_", f))goto exit;
	GXPrintf("ROMBanks=%x\n",ROMBanks);
	GXPrintf("RAMBanks=%x\n",RAMBanks);
	
	RAM=(byte *)GXGetMem(0x10000, MEMF_ABORT);
	if(!RAM)goto exit;
	if( !LoadSection(RAM, 0x10000, "RAM_", f) )goto exit;

	ROMMap[0]=RAM;
	GXPrintf("ROMBanks=%x\n",ROMBanks);
	GXPrintf("RAMBanks=%x\n",RAMBanks);
	for(j=1; j<ROMBanks; j++){
		sprintf(SectionName, "RO%2d", j);
		ROMMap[j]=(byte *)GXGetMem(0x4000, MEMF_ABORT);
		if(!LoadSection(ROMMap[j], 0x4000, SectionName, f))goto exit;
	}
	for(j=0; j<RAMBanks; j++){
		sprintf(SectionName, "RA%2d", j);
		RAMMap[j]=(byte *)GXGetMem(0x2000, MEMF_ABORT);
		if(!LoadSection(RAMMap[j], 0x2000, SectionName, f))goto exit;
	}

	//	Set up page pointers
	if(!LoadSection(PageInfo, 8*sizeof(PAGEINFO), "PAGE", f))goto exit;
	for(j=0; j<8; j++){
		Page[j]=(byte *)GetAddress(&PageInfo[j]);
	}
	if(!LoadSection(&page, sizeof(PAGEINFO), "CHRG", f))goto exit;
	ChrGen=(byte *)GetAddress(&page);
	if(!LoadSection(&page, sizeof(PAGEINFO), "BGDT", f))goto exit;
	BgdTab=(byte *)GetAddress(&page);
	if(!LoadSection(&page, sizeof(PAGEINFO), "WNDT", f))goto exit;
	WndTab=(byte *)GetAddress(&page);

	fclose(f);
	f=0;
	
	//	Play the game
	Z80(R);
	success=1;
exit:
	GXPrintf("Ending snapshot\n");
	//	Free up everything
	GXPrintf("Freeing RAMMap\n");
	for(j=0; j<ELEMENTS(RAMMap); j++){
		if(RAMMap[j]) GXFreeMem(RAMMap[j]);
	}
	GXPrintf("Freeing ROMMap\n");
	for(j=1; j<ELEMENTS(ROMMap); j++){
		if(ROMMap[j]) GXFreeMem(ROMMap[j]);
	}
	GXPrintf("Freeing RAM\n");
	if(RAM)GXFreeMem(RAM);
	if(f)fclose(f);
	GXPrintf("Done snapshot\n");
	return success;
}
int	SaveSnapshot(char *name)
{
	FILE	*f;
	int		j;
	char	SectionName[256];
	PAGEINFO	PageInfo[8], page;

	f=fopen(name,"wb");
	if(!f)goto fail;

	if(4!=fwrite(SAVEGAME_ID, 1, 4, f))goto fail;

	//	Save static data
	if(!SaveStaticData(Z80Static, NZ80Static, "Z80D", f))goto fail;
	if(!SaveStaticData(GBStatic, NGBStatic, "GBD_", f))goto fail;

	//	Save gameboy memory blocks
	if(!SaveSection(RAM, 0x10000, "RAM_", f))goto fail;
	GXPrintf("ROMBanks=%d\nRAMBanks=%d\n",ROMBanks,RAMBanks);
	for(j=1; j<ROMBanks; j++){
		sprintf(SectionName, "RO%2d", j);
		if(!SaveSection(ROMMap[j], 0x4000, SectionName, f))goto fail;
	}
	for(j=0; j<RAMBanks; j++){
		sprintf(SectionName, "RA%2d", j);
		if(!SaveSection(RAMMap[j], 0x2000, SectionName, f))goto fail;
	}
	for(j=0; j<8; j++){
		GetPageInfo(&PageInfo[j],Page[j]);
	}
	if(!SaveSection(PageInfo, 8*sizeof(PAGEINFO), "PAGE", f))goto fail;

	GetPageInfo(&page, ChrGen);	
	if(!SaveSection(&page, sizeof(PAGEINFO), "CHRG", f))goto fail;
	GetPageInfo(&page, BgdTab);	
	if(!SaveSection(&page, sizeof(PAGEINFO), "BGDT", f))goto fail;
	GetPageInfo(&page, WndTab);	
	if(!SaveSection(&page, sizeof(PAGEINFO), "WNDT", f))goto fail;
	
	//	Save static data
	if(f)fclose(f);
	return 1;
fail:
	if(f)fclose(f);
	return 0;
}

void	*GetAddress(PAGEINFO *page)
{
	switch(page->Type){
	case PAGE_MAINRAM:
		return RAM+page->Offset;
	case PAGE_RAM:
		return RAMMap[page->Page]+page->Offset;
	case PAGE_ROM:
		return ROMMap[page->Page]+page->Offset;
	case PAGE_NULL:
		return 0;
	default:
		GXQuitCode("Invalid address\n");
		return 0;
	}
}

int	LoadStaticData(STATICDATA *stat, int NStatic, char *name, FILE *f)
{
	size_t		length;
	STATICDATA	*s;
	UBYTE		*data=0, *from;
	int			j;

	length=0;
	s=stat;
	for(j=0; j<NStatic; j++){
		length+=s->Size;
		s++;
	}
	data=(UBYTE *)GXGetMem(length, MEMF_ZERO|MEMF_ABORT);
	if(!LoadSection(data, length, name, f))goto fail;
	s=stat;
	from=data;
	for(j=0; j<NStatic; j++){
		memcpy(s->Location, from, s->Size);
		if(j==11){
			GXPrintf("ROMBanks should be at %d\n",from-data);
			GXPrintf("Size = %d\n",s->Size);
			GXPrintf("Value = %x\n",*((int *)s->Location) );
		}
		from+=s->Size;
		s++;
	}
	GXFreeMem(data);
	return 1;
fail:
	GXPrintf("Failed to load static data\n");
	if(data){
		GXFreeMem(data);
	}
	return 0;
}


int	SaveStaticData(STATICDATA *stat, int NStatic, char *name, FILE *f)
{
	size_t		length;
	STATICDATA	*s;
	UBYTE		*data, *to;
	int			j, ret;
	
	GXPrintf("Saving static data: %s\n",name);
	GXPrintf("elements = %d\n",NStatic);
	length=0;
	s=stat;
	for(j=0; j<NStatic; j++){
		length+=s->Size;
		s++;
	}
	GXPrintf("Static length = %d\n",length);
	data=(UBYTE *)GXGetMem(length, MEMF_ZERO|MEMF_ABORT);
	to=data;
	s=stat;	
	for(j=0; j<NStatic; j++){
		if(j==11){
			GXPrintf("ROMBanks should be at %d\n",to-data);
			GXPrintf("Size = %d\n",s->Size);
			GXPrintf("Value = %x\n",*((int *)s->Location) );
		}
		memcpy(to, s->Location, s->Size);
		to+=s->Size;
		s++;
	}
	ret=SaveSection(data, length, name, f);
	GXFreeMem(data);
	return ret;
}

int	LoadSection(void *data, size_t size, char *name, FILE *f)
{
	long	length;
	char	SectionName[5];

	GXPrintf("Loading section %s\n",name);
	if(4!=fread(SectionName, 1, 4, f))return 0;
	SectionName[4]=0;
	if(4!=fread(&length, 1, 4, f))return 0;
	if(!strcmp(SectionName, name)){
		if(size==fread(data, 1, size, f))return 1;
	}
	return 0;
}

int	SaveSection(void *data, size_t size, char *name, FILE *f)
{
	long	length;

	GXPrintf("Saving section %s\n",name);
	if(strlen(name)!=4)return 0;
	if(4!=fwrite(name, 1, 4, f))return 0;
	length=(long)size;
	if(4!=fwrite(&length, 1, 4, f))return 0;
	if(size!=fwrite(data, 1, size, f))return 0;
	return 1;
}
void	GetPageInfo(PAGEINFO *info, UBYTE *data)
{
	size_t	offset;
	int		j;
	
	offset=(data-RAM);
	if(offset<0x10000){
		info->Type=PAGE_MAINRAM;
		info->Offset=offset;
		return;
	}
	for(j=0; j<RAMBanks; j++){
		offset=(data-RAMMap[j]);
		if(offset<0x2000){
			info->Type=PAGE_RAM;
			info->Offset=offset;
			info->Page=j;
			return;
		}
	}
	for(j=0; j<ROMBanks; j++){
		offset=(data-ROMMap[j]);
		if(offset<0x4000){
			info->Type=PAGE_ROM;
			info->Offset=offset;
			info->Page=j;
			return;
		}
	}
	info->Type=PAGE_NULL;
}


