//fileio.h
//Utilities for reading and writing to files in the game's subdirectories


//fileio works by creating a table of all files within the applications directory
//at startup. this way all files we need can be addressed simply using reference numbers.
//sometimes also refered to as "ID numbers".

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "fileio.h"
#include "platform.h"
#include "gamemem.h"
#include "error.h"
#include "parser.h"

int gFileTableSize;				//size of the file reference table
int gFileTableExtendedSize;
int gFileTampered=false;
tFileTableEntry *gFileTable;	//the file reference table
char gNoName[]="";

void FileInitFileTable(tFileTableEntry *fileTable,int maxSize,int *fileTableSize,int reInit);

#define kNumValidateFiles 42

char gValidateNames[][256]={
	"500gt.car",
	"500gthotrod.car",
	"959.car",
	"maserati.car",
	"bmw2002.car",
	"charger.car",
	"chargerdragdrag.car",
	"corvette.car",
	"diablo.car",
	"dmc12.car",
	"dmc12flying.car",
	"gt40.car",
	"gti.car",
	"mini.car",
	"55ford.car",
	"tt.car",
	"ttpimp.car",
	"viper.car",
	"viperracer.car",
	"accelbrake.mapinfo",
	"accelbrake2.mapinfo",
	"canyon.mapinfo",
	"canyoncorner.mapinfo",
	"city2.mapinfo",
	"citycorner.mapinfo",
	"downhill.mapinfo",
	"highspeed.mapinfo",
	"highspeedtrial.mapinfo",
	"mountainside.mapinfo",
	"offroad.mapinfo",
	"quarter.mapinfo",
	"ralley2.mapinfo",
	"scorner.mapinfo",
	"slalom.mapinfo",
	"slalom2.mapinfo",
	"snow.mapinfo",
	"snowtrial.mapinfo",
	"tight.mapinfo",
	"canyon.road",
	"city2.road",
	"mountainside.road",
	"ralley2.road",
	"snow.road",
};

int gValidateChecksums[]={
	0xc98d7f36,
	0x833e4a7d,
	0xdcd7e89,
	0x3e6a08b7,
	0x251efab,
	0xed2a6577,
	0x6d654b5,
	0x1c468570,
	0x1380a984,
	0x7c69956,
	0xcc2746e4,
	0x22349629,
	0x8d0d50b4,
	0x51994161,
	0x90a4cc85,
	0x931cbd6c,
	0xe42301b9,
	0xcc9bd572,
	0x1b7951e7,
	0x72d87975,
	0x1eb05d11,
	0xa2182ab9,
	0xde1122e6,
	0xc44b1aab,
	0x815cb099,
	0x8eb6b530,
	0x6c93ce2,
	0x77263bb7,
	0x18ebbbad,
	0xc57f8785,
	0x117ba80c,
	0xb513b806,
	0x75938236,
	0x2437d140,
	0xb249c233,
	0x6e92dc33,
	0x9e5c31de,
	0x8766b155,
	0x1bbdc55d,
	0x28f6a9b8,
	0x66e5492e,
	0x8b5a55d8,
};
//Initialize file reference table
void FileInitIO()
{
	gFileTableExtendedSize=0;
	gFileTableSize=0;
	gFileTable=(tFileTableEntry*)calloc(kMaxFiles,sizeof(tFileTableEntry));
	FileInitFileTable(gFileTable,kMaxFiles,&gFileTableSize,false);
	
	for(int i=0;i<kNumValidateFiles;i++)
	{
		if(FileGetChecksum(FileGetReference(gValidateNames[i]))!=gValidateChecksums[i])
		{
			gFileTampered=true;
			printf("Bad File! %s. Found Checksum %x. Need Checksum %x.\n",FileGetName(FileGetReference(gValidateNames[i])),
			FileGetChecksum(FileGetReference(gValidateNames[i])),gValidateChecksums[i]);
		}
		//printf("0x%x,\n",FileGetChecksum(FileGetReference(gValidateNames[i])));
	}
}

void FileRescanDirectory()
{
	int totalSize=gFileTableSize+gFileTableExtendedSize;
	FileInitFileTable(gFileTable,kMaxFiles,&totalSize,true);
	gFileTableExtendedSize=totalSize-gFileTableSize;
	printf("ex:%d\n",gFileTableExtendedSize);
}

void FileReleaseData(tFileRef reference)
{
	if(gFileTable[reference].loaded)
	{
		MemoryFreeBlock(gFileTable[reference].data);
		gFileTable[reference].loaded=false;
	}
}

//Compare two File Table entries for sorting and searching
int FileTableCompare(const void *a,const void *b)
{
	return _stricmp(((tFileTableEntry*)a)->name,((tFileTableEntry*)b)->name);
}

int gFileErrorReporting=true;

//Search file reference table for a file matching a name and return its reference number
tFileRef FileGetReference(char *name)
{
	if(*name=='\0')
		return -1;
	//the key to search for	
	tFileTableEntry key;
#ifndef __TARGET_TEXTURECOMPRESSOR 
#ifndef __TARGET_TOOLAPP
	snprintf(key.name,32,"%s.txr",name);
	
	//search for an entry matching our search key
	tFileTableEntry *result=(tFileTableEntry*)bsearch(&key,gFileTable,gFileTableSize,sizeof(tFileTableEntry),&FileTableCompare);
	
	//return results
	if(result)
		return result-gFileTable;
	else
	{
	snprintf(key.name,32,"%s.ima",name);
	
	//search for an entry matching our search key
	tFileTableEntry *result=(tFileTableEntry*)bsearch(&key,gFileTable,gFileTableSize,sizeof(tFileTableEntry),&FileTableCompare);
	
	//return results
	if(result)
		return result-gFileTable;
	else

	{
#endif
#endif
		strcpy(key.name,name);
		tFileTableEntry *result=(tFileTableEntry*)bsearch(&key,gFileTable,gFileTableSize,sizeof(tFileTableEntry),&FileTableCompare);
		if(result)
			return result-gFileTable;
		else
		{
			for(int i=gFileTableSize;i<gFileTableSize+gFileTableExtendedSize;i++)
				if(_stricmp(gFileTable[i].name,name)==0)	
					return i; 	
			if(gFileErrorReporting)
			{
				//or give an error message
				PrintConsoleString("File not found!: %s\n",name);
			}
			return -1;
		}
#ifndef __TARGET_TEXTURECOMPRESSOR 
#ifndef __TARGET_TOOLAPP
	}
	}
#endif
#endif
}

tFileRef FileGetIndexedReference(tFileRef base,int i)
{
	if(i>0)
	{
		char nameCopy[kMaxFileNameLength];
		strcpy(nameCopy,FileGetName(base));
		char *found=strchr(nameCopy,'.');

		char indexedName[kMaxFileNameLength];
		if(found)
		{
			*found='\0';
			sprintf(indexedName,"%s#%x.%s",nameCopy,i,found+1);
		}
		else
			sprintf(indexedName,"%s#%x",nameCopy,i);
		
		int seek=base-1;
		while(_stricmp(gFileTable[seek].name,indexedName)>0&&seek>0)
			seek--;
		
		if(_stricmp(gFileTable[seek].name,indexedName))
			return base;
		else
			return seek;
	}
	else
		return base;
}

tFileRef FileGetBaseReference(tFileRef ref)
{
	if(ref<0||ref>=gFileTableExtendedSize+gFileTableSize)
		return kFileErr;
	char *found=strchr(FileGetName(ref),'.');
	if(!found)
		return ref;
	
	char *cross=found;
	while(*cross!='#'&&cross>FileGetName(ref))
		cross--;
	if(*cross!='#')
		return ref;

	int l=cross-FileGetName(ref);
	char nameCopy[kMaxFileNameLength];
	memcpy(nameCopy,FileGetName(ref),l);
	strcpy(nameCopy+l,found);
	tFileRef base=FileGetReference(nameCopy);
	return base!=kFileErr?base:ref;	
}

//platform specific, can be found in macfileio.cpp
int FileLoadData(tFileRef fileRef);
void FileStoreData(tFileRef fileRef);

//return the extension of a file given it's reference number
char *FileGetExtension(tFileRef reference)
{
	if(reference<0||reference>=gFileTableExtendedSize+gFileTableSize)
		return NULL;
	char *found=NULL;
	char *dot=gFileTable[reference].name;
	do{
		dot=strchr(dot,'.');
		if(dot){
			found=dot;
			dot++;
		}
	}while(dot);
	if(found)
		return found;
	else 
		return gFileTable[reference].name+strlen(gFileTable[reference].name);//'\0';
}

//return the name of a file given it's reference number
char *FileGetName(tFileRef reference)
{
	if(reference<0||reference>=gFileTableExtendedSize+gFileTableSize)
		return gNoName;
	return gFileTable[reference].name;
}

//return the size of a file given it's reference number
int FileGetSize(tFileRef reference)
{
	if(reference<0||reference>=gFileTableExtendedSize+gFileTableSize)
		return 0;
	if(!gFileTable[reference].loaded)
		gFileTable[reference].loaded=FileLoadData(reference);
	return gFileTable[reference].size;
}

//return a pointer to the file's data (and load the file if necessary)
void *FileGetDataPtr(tFileRef reference)
{
	if(reference<0||reference>=gFileTableExtendedSize+gFileTableSize)
		return NULL;
		
	if(!gFileTable[reference].loaded)
		gFileTable[reference].loaded=FileLoadData(reference);
	return gFileTable[reference].loaded?gFileTable[reference].data:(void*)0;
}

unsigned int FileGetChecksum(tFileRef reference)
{
	if(reference<0||reference>=gFileTableExtendedSize+gFileTableSize)
		return 0;
	return MemoryChecksum(FileGetDataPtr(reference),FileGetSize(reference));
}

//Changes a file's contents to data, and writes the file to disk.
void FileSetData(tFileRef reference,void *data)
{
	if(reference<0||reference>=gFileTableExtendedSize+gFileTableSize)
		return;
	if(gFileTable[reference].loaded&&data!=gFileTable[reference].data)
		MemoryFreeBlock(gFileTable[reference].data);
	gFileTable[reference].data=data;
	gFileTable[reference].loaded=true;
	gFileTable[reference].size=MemoryBlockSize(data);
	FileStoreData(reference);
}

//parses a file's data using a file format parser 
//and return a pointer to parsed data structure
void* FileGetParsedDataPtr(tFileRef reference, int parserType, int dataSize)
{
	if(reference<0||reference>=gFileTableExtendedSize+gFileTableSize)
		return NULL;
	if(!gFileTable[reference].parsed)
	{
		gFileTable[reference].parsedData=MemoryAllocateZeroedBlock(dataSize);
		ParseFile(reference,gFileTable[reference].parsedData,parserType);
		gFileTable[reference].parsed=true;
	}
	return gFileTable[reference].parsedData;		
}