/************************************************************************************
	ISL PSX LIBRARY	(c) 1999 Interactive Studios Ltd.

	islfile.c:		File handling

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

#include <sys/types.h>
#include <libcd.h>
#include <libsn.h>
#include <stdio.h>
#include <string.h>
#include <islmem.h>
#include <islutil.h>
#include <islfile.h>
#include "global.h"
#include "maths.h"

static char FILEIO_CDINDEX[64] = "\\CRTEST.DAT;1"; //	"\\MYGAME.DAT;1"
#define FLA_MAGIC			0x32414c46			// Magic number 'FLA2' INTEL

typedef struct _FileIODataType
{
	int				DATsector;
	unsigned char	*index;
	unsigned char	CDresult[8];
	char			bkgFilename[256];
	int				bkgLoading;
	unsigned char	*bkgBuffer;
} FileIODataType;


//FileIODataType	fileIO;	// already exists

extern FileIODataType	fileIO;	// already exists


static char		sectorBuf[2048];
static char		sectorBuf2[2048];

static char		lastFileLoaded[64];
static int		lastFileSize;

/* CD READING CODE *********************************************************************************/

/**************************************************************************
	FUNCTION:	fileCDreadIndex()
	PURPOSE:	Read index sectors from CD
	PARAMETERS:	Filename of DAT file
	RETURNS:	>0 if errors
**************************************************************************/

static int fileCDreadIndex(char *fName)
{
	CdlFILE	fp;

	if (CdSearchFile(&fp, fName)==NULL)									// Find file location
	{
		printf("Reading CD index: CdSearchFile() error\n");
		return 1;
	}
	fileIO.DATsector = CdPosToInt(&fp.pos);
	if (CdReadFile(fName, (unsigned long *)fileIO.index, 8*2048)==0)			// Read file
	{
		printf("Reading CD index: CdReadFile() error\n");
		return 2;
	}
	if (CdReadSync(0, fileIO.CDresult))										// Wait for read
	{
		printf("Reading CD index: Read error\n");
		return 3;
	}
	return 0;
}


/**************************************************************************
	FUNCTION:	fileCDInitialise()
	PURPOSE:	Initialise CD file I/O
	PARAMETERS:	
	RETURNS:	
**************************************************************************/

static void fileCDInitialise(char *fileSystem)
{
	strcpy(FILEIO_CDINDEX, fileSystem);
	CdInit();

	fileIO.index = MALLOC(8*2048);

	while(fileCDreadIndex(FILEIO_CDINDEX))
	{
		printf("Problem reading CD index - retry\n");
	}

	printf("Read CD index\n");
}


/**************************************************************************
	FUNCTION:	fileCDDATgetLocation()
	PURPOSE:	Get location info from CD index
	PARAMETERS:	Filename, *first sector, *file length
	RETURNS:	
**************************************************************************/

static void fileCDDATgetLocation(char *fName, long *sector, long *length)
{
	long	crc, *iPtr;
	char	name[40];
	int		loop;

	strcpy(name, fName);
	utilUpperStr(name);
	crc = utilStr2CRC(name);
	iPtr = (int *)fileIO.index;
	loop = iPtr[1];
	iPtr += 2;
//	printf("Looking for '%s' (0x%x) %d files\n", name, crc, loop);
	while(loop>0)
	{
		if (iPtr[0]==crc)
		{
			*sector = iPtr[1]+fileIO.DATsector+8;
			*length = iPtr[2];
//			printf("Found file %d [%d]\n", *sector, *length);
			return;
		}
		iPtr += 3;
		loop--;
	}
	*sector = *length = -1;
	printf("'%s' not found in DAT file\n", fName);
}

/**************************************************************************
	FUNCTION:	fileCDloadDATfile()
	PURPOSE:	Load in file from CD DAT file
	PARAMETERS:	Filename, buffer
	RETURNS:	Ptr to data or NULL
**************************************************************************/

static unsigned long *fileCDloadDATfile(char *fName, int *length)
{
	long	sec, sz;//, num, roundSz;
	CdlFILE	fp;
	unsigned char	*buffer, *ptr;
	unsigned long	*lPtr;
	int		rtn;

//	if (fileCDGetLocation(fileIO.index, fName, &sec, &sz, &num))
	fileCDDATgetLocation(fName, &sec, &sz);
	if (sec==-1)
		return NULL;
	CdIntToPos(sec, &fp.pos);
	if (CdControl(CdlSetloc, (unsigned char *)&fp.pos, fileIO.CDresult)==0)		// Seek to file
	{
		printf("Load file: CDControl() error\n");
		return NULL;
	}
	if (fileIO.CDresult[0]==0)											// Check seek OK
	{
		printf("Load file: Seek error\n");
		return NULL;
	}
	if (CdRead(1, (unsigned long *)sectorBuf, CdlModeSpeed)==0)					// Start read
	{
		printf("Load file: CdRead() error\n");
		return NULL;
	}
	lPtr = (unsigned long *)sectorBuf;

	while((rtn=CdReadSync(1, fileIO.CDresult))>0);						// Wait for first sector
	if (rtn<0)
	{
		printf("Load file: CdReadSync() error\n");
		return NULL;
	}

	CdIntToPos(sec, &fp.pos);
	if (CdControl(CdlSetloc, (unsigned char *)&fp.pos, fileIO.CDresult)==0)		// Seek to file
	{
		printf("Load file: CDControl() error\n");
		return NULL;
	}
	if (fileIO.CDresult[0]==0)											// Check seek OK
	{
		printf("Load file: Seek error\n");
		return NULL;
	}

	if (lPtr[0]==FLA_MAGIC)												// Check compression
	{																	// Compressed file
//		printf("### COMPRESSED FILE %d/%d ###\n", lPtr[1], ((sz>>11)+1)<<11);
		buffer = MALLOC(lPtr[1]+2048);
		if (!buffer)
		{
			printf("Can't malloc buffer space for file read: %s\n",fName);
			return NULL;
		}
		ptr = buffer+(lPtr[1]-(((sz>>11)+1)<<11))+2048;
		if (CdRead((sz>>11)+1, ptr, CdlModeSpeed)==0)					// Start read
		{
			printf("Load file: CdRead() error\n");
			FREE(buffer);
			return NULL;
		}
		while((rtn=CdReadSync(1, fileIO.CDresult))>0);					// Wait for whole file
		if (rtn<0)
		{
			printf("Load file: CdReadSync()[2] error\n");
			FREE(buffer);
			return NULL;
		}
//		printf("### UNRAVELLING ###\n");							// Decompress
		utilDecompressBuffer(ptr+8, buffer);
//		printf("### DECOMPRESSED! ###\n");
		*length = lPtr[1];
	}
	else
	{																	// Uncompressed file
		if ((buffer = MALLOC(((sz>>11)+1)<<11))==NULL)			// Get RAM
		{
			printf("Load file: Not enough RAM\n");
			return NULL;
		}
		if (CdRead((sz>>11)+1, buffer, CdlModeSpeed)==0)				// Start read
		{
			printf("Load file: CdRead() error\n");
			FREE(buffer);
			return NULL;
		}
		while((rtn=CdReadSync(1, fileIO.CDresult))>0);					// Wait for whole file
		*length = sz;
	}
	return buffer;
}


/**************************************************************************
	FUNCTION:	fileCDcheckLoaded()
	PURPOSE:	Load in file from CD
	PARAMETERS:	
	RETURNS:	0 = Ready	<0 = Errors		>0 = Not ready yet
**************************************************************************/

static int fileCDcheckLoaded()
{
	return CdReadSync(1, fileIO.CDresult);
}


/**************************************************************************
	FUNCTION:	fileCDLoad()
	PURPOSE:	Load in file from CD
	PARAMETERS:	Filename
	RETURNS:	Ptr to file data or NULL
**************************************************************************/

static unsigned char *fileCDLoad(char *fName, int *length)
{
	unsigned long	*buffer;
	//int		rtn;

	while (1)
	{
		if ((buffer = fileCDloadDATfile(fName, length))!=NULL)
		{
//			while((rtn=fileCDcheckLoaded())>0);
//			if (rtn==0)
				return (unsigned char *)buffer;
		}		
		printf("Problem reading file - retry\n");
	}
	return (unsigned char *)buffer;
}


/**************************************************************************
	FUNCTION:	fileCDloadDATbinary()
	PURPOSE:	Load in file from CD DAT file
	PARAMETERS:	Filename, buffer
	RETURNS:	Ptr to data or NULL
**************************************************************************/

static unsigned char fileCDloadDATbinary(char *fName, int *loc)
{
	long	sec, sz;//, num;
	CdlFILE	fp;
	int		rtn;
	unsigned char	*ptr;

//	if (fileCDGetLocation(fileIO.index, fName, &sec, &sz, &num))
//		return 1;
	fileCDDATgetLocation(fName, &sec, &sz);
	if (sec==-1)
		return 1;
	CdIntToPos(sec, &fp.pos);
	if (CdControl(CdlSetloc, (unsigned char *)&fp.pos, fileIO.CDresult)==0)		// Seek to file
	{
		printf("Load file: CDControl() error\n");
		return 1;
	}
	if (fileIO.CDresult[0]==0)											// Check seek OK
	{
		printf("Load file: Seek error\n");
		return 1;
	}
	if (CdRead((sz>>11), (unsigned long *)loc, CdlModeSpeed)==0)				// Start read
	{
		printf("Load file: CdRead() error\n");
		return 1;
	}

	while((rtn=fileCDcheckLoaded())>0);									// Wait for read

	CdIntToPos(sec+(sz>>11), &fp.pos);
	if (CdControl(CdlSetloc, (unsigned char *)&fp.pos, fileIO.CDresult)==0)		// Seek to file
	{
		printf("Load file: CDControl() error\n");
		return 1;
	}
	if (fileIO.CDresult[0]==0)											// Check seek OK
	{
		printf("Load file: Seek error\n");
		return 1;
	}
	if (CdRead(1, (unsigned long *)sectorBuf, CdlModeSpeed)==0)					// Start read last bit
	{
		printf("Load file: CdRead() error\n");
		return 1;
	}

	while((rtn=fileCDcheckLoaded())>0);									// Wait for read

	ptr = loc;
	ptr += ((sz>>11)<<11);
	memcpy(ptr, sectorBuf, sz & 0x7ff);
	return 0;
}


/**************************************************************************
	FUNCTION:	fileInitialise()
	PURPOSE:	Initialise file I/O
	PARAMETERS:	
	RETURNS:	
**************************************************************************/

void TESTfileInitialise(char *fileSystem)
{
	fileCDInitialise(fileSystem);
	
	lastFileLoaded[0] = 0;
	lastFileSize = 0;
}


/**************************************************************************
	FUNCTION:	fileLoad()
	PURPOSE:	Load in file
	PARAMETERS:	Filename, ptr to length var
	RETURNS:	Ptr to file data or NULL
**************************************************************************/

unsigned char *extractTarget;
void extractBegin(int sz)
{
	sz = (sz + 3) & (~3);
	extractTarget = MALLOC(sz);
//	printf("extract begin. Loc = %d\n",(int)extractTarget);
}

void extractRoutine(int offset, int amount,char *data)
{
//	printf("writing %d %d\n",offset,amount);
	amount = (amount + 3) & (~3);
	FMEMCPY(extractTarget + offset, data, amount);
}

/*
// Well, that works, but somewhat slowly...
int TESTfileStreamLoad(char *fName, int *length)
{
	long	sec, sz;//, num, roundSz;
	CdlFILE	fp;
	unsigned char	*buffer, *ptr;
	unsigned long	*lPtr;
	int		rtn;
	char *pointers[2] = {sectorBuf,sectorBuf2};
	int which = 0;
	int loopcount = 0;
	int retry = 0;
	int failed = 0;
	int sex;
	int reading = 0;

	fileCDDATgetLocation(fName, &sec, &sz);
	if (sec==-1)
		return 0;

	sex = (sz + 2047) >> 11;
	if(length)
		*length = sz;

	extractBegin(sz);

	failed = 1;	// Prevent the first run through from trying to extract data

	for(loopcount = 0;loopcount < sex; )
	{

// set the new one loading in the background

		do
		{
			retry = 0;
			CdIntToPos(sec, &fp.pos);
			if (CdControl(CdlSetloc, (unsigned char *)&fp.pos, fileIO.CDresult)==0)		// Seek to file
			{
				printf("StreamLoad: CDControl() retrying\n");
				retry = 1;
			}
			if(!retry)
			{
				if (fileIO.CDresult[0]==0)											// Check seek OK
				{
					printf("StreamLoad: Seek error. retrying\n");
					retry = 1;
				}
			}
		}
		while(retry);

		do
		{
			retry = 0;
			if (CdRead(1, (unsigned long *)pointers[which], CdlModeSpeed)==0)					// Start read
			{
				printf("StreamLoad: CdRead() retrying\n");
				retry = 1;
			}
		}
		while(retry);

		which = 1-which;

// while we extract the previous one
		if(!failed)
		{
			if(loopcount == sex-1)
			{
				extractRoutine(loopcount<<11,sz & 2047,pointers[which]);
				return 1;	// Wahey! We've finished!
			}
			else
			{
				extractRoutine(loopcount<<11, 2048, pointers[which]);
			}
			loopcount++;
		}

	
// Wait for previous sector to finish loading
		failed = 0;
		while((rtn=CdReadSync(1, fileIO.CDresult))>0);

		if (rtn<0)
		{
			printf("Load file: CdReadSync() error. retrying\n");
			failed = 1;
		}

// If that sector loaded ok, set ourselves going to read the next one, otherwise, re-read the previous
		if(!failed)
			sec++;
	}
	return 1;
}

*/

// =====================================================================================================
// Stuff for streaming SPT files in a sector at a time...
// It's assumed that this rou is sent sectors in the correct order. No duplicates. No skips

// Prob with spt files - no initial count of number of sprites...
/*
	for(sprcount1 = 0; !(file1[sprcount1].flags & NLASTSPR); sprcount1++)
	{
	}
	sprcount1++;
	for(sprcount2 = 0; !(file2[sprcount2].flags & NLASTSPR); sprcount2++)
	{
	}
	sprcount2++;
//	printf("%d + %d sprites\n",sprcount1,sprcount2);

// Initial State of each file...
// (sprcount1 * sizeof NSPRITE) header
// Sprites1
*/

// Storage for the last ickle bit of sprite that OV's to the next sector
unsigned char sptStoreBuffer[2048 * 2];
int sptStoreSize;
int sptStoreNeed;

// transfer a single sprite
void TESTSptTransfer(NSPRITE *src)
{
}

// "data" is a 2048-byte bufffer that's been CdGetSector-ed
void TESTSptStreamer(unsigned char * data, int sectornumber)
{
	static int spt_store;
	int offset_now;


	if(sectornumber==0)
	{
		sptStoreSize = 0;
		sptStoreNeed = 0;
	}

	offset_now = 0;
	if(sptStoreNeed)
	{
		FMEMCPY(&sptStoreBuffer[sptStoreSize,data,sptStoreNeed);
		TESTSptTransfer(sptStoreBuffer);
		offset_now = sptStoreNeed;
	}

	{
		// calc size of current spt
	}

	NSPRITE *ptr
	pHeader[n].pal = (unsigned short *)((unsigned long)pHeader+((unsigned long)pHeader[n].pal));
	pHeader[n].image = (unsigned char *)((unsigned long)pHeader+((unsigned long)pHeader[n].image));

}


// =====================================================================================================

// okay.... Fast low-level loading
// Adapted from sample\scee\cd\cdexmpl\cdsample.zip...  example5.

int gSectors;
int readSectors;
int gDecodedSectors;
unsigned char resetRequest = 0;
unsigned char* constructAddr;
unsigned char* gInitialAddr;
unsigned char gDoorStatus = 0;
unsigned long totalFileSize;

int gfilesize;
CdlFILE	global_fp;
u_long* memAddr;

#define FORM1_SIZE	2048
#define Sectors(x)	(((x)+2048-1)/2048)
#define Words(x)	(((x)+3) / 4)


void CheckLid(void)
{
	CdControl(CdlNop,0,0);		// To get the very latest CD status
	gDoorStatus = (CdStatus() & CdlStatShellOpen);
	return;
}
int DoneLoading(void)
{
// Check that there is at least one sector to load in
	if ( !gSectors )
		return 1;
	else
		return ( gDecodedSectors >= gSectors );
}

//To be absolutely safe, you should do CdDataSync(0,0) immediately after the CdGetSector, in case
//there are changes to the way CdGetSector works. It’s documented as being non-blocking, and maybe

// Once the dma's done, it calls CdDataCallback, in any case, so that's where to put the texture upload stuff...

void CdReadyCallBackFunc(int intr,unsigned char* result)
{
	unsigned long* currentAddress;
// sector size, used to increment memory location and file size, the final
// sector will probably be less than 2048 and this is calculated from the
// expected file size minus the number of read sectors.
	unsigned short size = FORM1_SIZE;
	unsigned long sectorCheck[3];

	if(intr == CdlDataReady)
	{
		if(readSectors == gSectors)
		{
			// Reset Cd Callback as we have finished reading the file,
			// however we may not having finished transferring the data
			CdReadyCallback((CdlCB)NULL);
			return;
		}

		currentAddress = (unsigned long*)constructAddr;
			
		// Calculate final size of data as it won't be a full sector
		if(readSectors == gSectors-1)
		{
//			size = CdFileData[gCurrentFile].fileDetails.size - (readSectors * FORM1_SIZE);
			size = gfilesize - (readSectors * FORM1_SIZE);
		}

		// Add the amount read, this enables us to set the correct memory location next loop and
		// to calculate the size of the file to compare against the expected file
		constructAddr +=size;
		totalFileSize +=size;

		readSectors++;

// note - we should grab 1st 3 words, & check they're the expected sector number
// (ummm)
		CdGetSector(currentAddress,Words(size));	// There's notes in the bbs files that 2048/4 is the only value that works here
	}
	else
	{
		// Line in most used order, might be quicker to OR first two
		if(intr==CdlComplete)
		{
			printf("CdlComplete\n");
			return;
		}
		if(intr==CdlDataEnd)
		{
			printf("CdlDataEnd\n");
			return;
		}
		if(intr== CdlDiskError)
		{
			printf("CdlDiskError\n");
			resetRequest = 1;
		}
	}

	return;
}


void DataReadyCallBackFunc(void)
{
	gDecodedSectors++;

	if(readSectors == gSectors)
	{
		CdDataCallback(NULL);
		printf("DataReadyCallBack ended, file read\n");
		gDecodedSectors = gSectors;
	}
	return;
}


void StartLoadingCd(void)
{
	int ready=0;
	unsigned char CDmode = (CdlModeSpeed|CdlModeRT);

// Loop until ready is set, the following checks are performed:
//	1) Wait for the lid to be shut
//	2) Seek to files position on disk
//	3) Set up callbacks
//	4) Attempt the starting read

// Ready is set to 1 when we have managed to get through the seek and start
// reading the data
	while(!ready)
  	{

		VSync(0);

		CheckLid();
		// Shut the bloody door !
		while(gDoorStatus)
		{
			CheckLid();
		}

		if(!CdControl(CdlSeekL,(unsigned char*)&global_fp.pos,0))
		{
			// break to top, retry
			continue;
		}

		if(!CdControl(CdlSetmode,&CDmode,0))
		{
			printf("** Failed to set double speed\n");
			continue;
		}

// Set up both data and CD callbacks
		CdDataCallback(DataReadyCallBackFunc);
		CdReadyCallback((CdlCB)CdReadyCallBackFunc);

// Start reading
		if(!CdControl(CdlReadN,0,0))
		{
			// bugger, reset callbacks and retry
			printf("** Failed to read data\n");
			CdDataCallback(NULL);
			CdReadyCallback(NULL);
			continue;
		}

		// Hurrah we have started to read the data
		ready=1;	
	}	

	return;
}


// tbd - we want to reinit to a specific sector of the file, not right back to the beginning
void CdReInit(void)
{
	printf("CD ReInit\n");

	gSectors = Sectors(gfilesize);
	totalFileSize	= 0;
	gDecodedSectors	= 0;
	readSectors		= 0;
	
	// Set pointer back to start of memory address passed in
	constructAddr = gInitialAddr;

	CheckLid();

	if(gDoorStatus)
		return;
		
	StartLoadingCd();
		
	return;
}

void CdPoll(void)
{
// Check lid has not been lifted
	if(!gDoorStatus && !resetRequest)
	{
		CheckLid();
	}
	else
	{
// If we've received a reset request or the lid has been opened then reset
// and start loading from scratch	
		EnterCriticalSection();
		CdDataCallback(NULL);
		CdReadyCallback(NULL);
		ExitCriticalSection();

		if(resetRequest)
		{
			resetRequest=0;
			printf("received reset\n");
		}

		CdReInit();			// Reinitialise the load
	}

	return;
}

int TESTfileStreamLoad(char *fName, int *length)
{
	long	sec, sz;//, num, roundSz;
	CdlFILE	fp;
//	unsigned char	*buffer, *ptr;
//	unsigned long	*lPtr;
//	int		rtn;
//	char *pointers[2] = {sectorBuf,sectorBuf2};
	int sex;


	fileCDDATgetLocation(fName, &sec, &sz);
	if (sec==-1)
		return 0;


	CdIntToPos(sec, &global_fp.pos);
	gfilesize = sz;
	CheckLid();

	// Loop until the case lid is shut
	while(gDoorStatus)
	{
		printf("Shut the bloody lid!\n");
		CheckLid();
	}


	gSectors = (sz + 2047) >> 11;
	gDecodedSectors	= 0;
	totalFileSize	= 0;
	readSectors		= 0;
	resetRequest	= 0;


	// Set up pointers gInitialAddr holds the starting memory location for use in 
	// reinitialising the loading of the file whilst constructAddr
	// is used to load data as it is read in
	memAddr = MALLOC(sz);
	constructAddr = gInitialAddr = (unsigned char *)memAddr;

	StartLoadingCd();
	while(!DoneLoading())
	{
		CdPoll();
	}

	EnterCriticalSection();
	CdDataCallback(NULL);
	CdReadyCallback(NULL);
	ExitCriticalSection();

	return 1;

}



unsigned char *TESTfileLoad(unsigned char *fName, int *length)
{
	printf("Loading...\n");
	if(!TESTfileStreamLoad(fName,length))
		return NULL;

	printf("Done.\n");
//	return extractTarget;
	return memAddr;
}


// Okay, for our next attempt, let's try copying scee\cd\example5\cd.c...






/*

unsigned char *TESTfileLoad(unsigned char *fName, int *length)
{
	unsigned char	*data, *realData;
	unsigned long	*lPtr;
	int		len;

	utilUpperStr(fName);

	data = fileCDLoad(fName, &len);
	if (data==NULL)
		return NULL;

	lPtr = (unsigned long *)data;
	if (lPtr[0] == FLA_MAGIC)
	{
		realData = MALLOC(lPtr[1]);
		if (realData==NULL)
			printf("Can't allocate memory for file read: %s\n",fName);
		else
		{
			utilDecompressBuffer(data+8, realData);
			FREE(data);
			printf("READ FILE: %s [compressed]\n", fName);
			return realData;
		}
	}
	printf("READ FILE: %s\n", fName);
	if (length!=NULL)
		*length = len;

	// quick fix so people know what the hell is getting loaded and how big
	strcpy(lastFileLoaded, fName);
	lastFileSize = len;

	return data;
}

*/













