//macfileio.cpp
//mac-specific file handling code.

#include <string.h>
#include <stdlib.h>
#include "fileio.h"
#include "gamemem.h"
#include "error.h"
#include "config.h"
#include "screen.h"

extern "C"{
#include "compress.h"
}

#define kFileNotPackaged -1

//System specific unique file locator
typedef struct{
	SInt16 vRefNum;
	SInt16 unused;
	SInt32 dirID;
	Str255 pName;
	unsigned int packageOffset,packageSize;
} tFileSystemLocator;

//fills an FSSpecPtr with the file system information for 
//the host application's Bundle directory
OSErr GetApplicationBundleFSSpec(FSSpecPtr theFSSpecPtr)
{
	FSRef location;
	CFBundleRef refMainBundle = NULL;
	CFURLRef refMainBundleURL = NULL; 
	refMainBundle = CFBundleGetMainBundle(); 
	refMainBundleURL = CFBundleCopyBundleURL (refMainBundle); 
	CFURLGetFSRef(refMainBundleURL,&location);
	
	return FSGetCatalogInfo(&location,kFSCatInfoNone,NULL,NULL,theFSSpecPtr,NULL);
}

typedef struct{
	unsigned int offset,unPackedSize,packedSize;
	char name[256];
}tPackFileHeaderEntry;

typedef struct{
	char magic[8];
	int numEntries;
	int replaceFlag,unused2,unused3;
	tPackFileHeaderEntry entries[];
}tPackFileHeader;

#define kMagicString "R3Dl1n3"

tFileRef SimpleFileGetReference(char *name,tFileTableEntry *fileTable,int fileTableSize)
{
	for(int i=0;i<fileTableSize;i++)
		if(_stricmp(fileTable[i].name,name)==0)
			return i; 	
	return -1;
}

void IteratePackage(SInt16 vRefNum,SInt32 dirID,Str255 name,tFileTableEntry *fileTable,int *fileTableSize,int maxSize)
{
	short ref;
	HandleError(HOpenDF(vRefNum,dirID,name,fsCurPerm,&ref));
	
	long headerSize=sizeof(tPackFileHeader);

	long eof;
	HandleError(GetEOF(ref,&eof));
	if(eof<headerSize)
		return;
	
	tPackFileHeader *header=(tPackFileHeader*)NewPtr(headerSize);
	HandleError(FSRead(ref,&headerSize,header));
	if(_stricmp(header->magic,kMagicString))
		return;
	int numEntries=EndianU32_BtoN(header->numEntries);
	DisposePtr((Ptr)header);
	
	HandleError(SetFPos(ref,fsFromStart,0));
	
	headerSize=sizeof(tPackFileHeader)+sizeof(tPackFileHeaderEntry)*numEntries;	
	header=(tPackFileHeader*)NewPtr(headerSize);
	HandleError(FSRead(ref,&headerSize,header));
		
	for(int i=0;i<numEntries;i++)
		if(*fileTableSize<maxSize)//is there still space in the file table?
		{
			int exists=SimpleFileGetReference(header->entries[i].name,fileTable,*fileTableSize);
			int replaceable=false;
			if(exists)
			{
				char *extension=FileGetExtension(i);
				if(extension)
					replaceable=(_stricmp(extension,kFileTypeCarDefinition)&&_stricmp(extension,kFileTypeMapDefinition)); 
				else
					replaceable=true;
			}
			if(exists==-1||(header->replaceFlag&&replaceable))
			{
				tFileRef ref;
				if(exists==-1)
					ref=(*fileTableSize)++;
				else
					ref=exists;
				//add file record to file reference table
				fileTable[ref].loaded=false;
				fileTable[ref].parsed=false;
				fileTable[ref].size=EndianU32_BtoN(header->entries[i].unPackedSize);
				((tFileSystemLocator*)&(fileTable[ref].fsLoc))->vRefNum=vRefNum;
				((tFileSystemLocator*)&(fileTable[ref].fsLoc))->dirID=dirID;
				BlockMove(name,((tFileSystemLocator*)&(fileTable[ref].fsLoc))->pName,name[0]+1);
				((tFileSystemLocator*)&(fileTable[ref].fsLoc))->packageOffset=EndianU32_BtoN(header->entries[i].offset);
				((tFileSystemLocator*)&(fileTable[ref].fsLoc))->packageSize=EndianU32_BtoN(header->entries[i].packedSize);
				strcpy(fileTable[ref].name,header->entries[i].name);
			}	
		}
		else
		{
			ShowAlert("Too many files in Plug-ins folder.","Please remove some files from your Plug-ins folder, and restart Redline.");
			ExitToShell();
		}
	DisposePtr((Ptr)header);
	HandleError(FSClose(ref));
}

void AddFile(SInt16 vRefNum,SInt32 dirID,const Str255 name,tFileTableEntry *fileTable,int *fileTableSize,int maxSize)
{
	char cName[256];
	CopyPascalStringToC(name,cName);
	if(*fileTableSize<maxSize)//is there still space in the file table?
		if(SimpleFileGetReference(cName,fileTable,*fileTableSize)==-1)
		{
			//add file record to file reference table
			fileTable[*fileTableSize].loaded=false;
			fileTable[*fileTableSize].parsed=false;
			fileTable[*fileTableSize].tagged=false;
			((tFileSystemLocator*)&(fileTable[*fileTableSize].fsLoc))->vRefNum=vRefNum;
			((tFileSystemLocator*)&(fileTable[*fileTableSize].fsLoc))->dirID=dirID;
			BlockMove(name,((tFileSystemLocator*)&(fileTable[*fileTableSize].fsLoc))->pName,name[0]+1);
			((tFileSystemLocator*)&(fileTable[*fileTableSize].fsLoc))->packageOffset=kFileNotPackaged;
			strcpy(fileTable[*fileTableSize].name,cName);
			(*fileTableSize)++;
		}
		else
		{
		//	if(_stricmp(cName,".DS_Store"))
		//		PrintConsoleString("File duplicate!: %s\n",cName);
		}
	else
	{
		ShowAlert("Too many files in Plug-ins folder.","Please remove some files from your Plug-ins folder, and restart Redline.");
		ExitToShell();
	}
}

#define IsPackage(i) (_stricmp(cName+strlen(cName)-strlen(kFileTypePackageFile),kFileTypePackageFile)==0)
//recursivly add all files in a directory and subdirectories
//to the file reference table
void IterateDirectoryLevel(SInt16 vRefNum,SInt32 dirID,tFileTableEntry *fileTable,int *fileTableSize,int maxSize)
{
	CInfoPBRec		cinfo;
	Str255			name;
	for(int i=1; ;i++)
	{
		//Get info about the next file in the folder
		cinfo.hFileInfo.ioVRefNum = vRefNum;
		cinfo.hFileInfo.ioDirID = dirID;
		cinfo.hFileInfo.ioNamePtr = name;
		cinfo.hFileInfo.ioFDirIndex = i;
		OSErr error=PBGetCatInfoSync(&cinfo);
		
		char cName[256];
		CopyPascalStringToC(name,cName);
		
		//no more files?
		if(error==fnfErr)break;
		HandleError(error);
		
		//is this a directory?
		if(cinfo.hFileInfo.ioFlAttrib&kioFlAttribDirMask)
			//recursively process subdirectory
			IterateDirectoryLevel(vRefNum,cinfo.dirInfo.ioDrDirID,fileTable,fileTableSize,maxSize);
		
		#ifndef __PACKER
		else if(IsPackage(cName))
			IteratePackage(vRefNum,dirID,name,fileTable,fileTableSize,maxSize);
		#endif
		else 
			AddFile(vRefNum,dirID,name,fileTable,fileTableSize,maxSize);
	}			
}


//declared in fileio.cpp
int FileTableCompare(const void *a,const void *b);

SInt32 GetDirID(FSSpec *spec)
{
	CInfoPBRec		cinfo;
	cinfo.hFileInfo.ioVRefNum = spec->vRefNum;
	cinfo.hFileInfo.ioDirID = spec->parID;
	cinfo.hFileInfo.ioNamePtr = spec->name;
	cinfo.hFileInfo.ioFDirIndex = 0;
	OSErr error=PBGetCatInfoSync(&cinfo);
	return cinfo.dirInfo.ioDrDirID;
}

#define kPlugInDirName "Plug-Ins"
#define kPlugInDirPName "\pPlug-Ins"
//Initialize file reference table
void FileInitFileTable(tFileTableEntry *fileTable,int maxSize,int *fileTableSize,int reInit)
{
	FSSpec spec;
	OSErr err;
	
	SInt16 vRefNum; 	//application's vRefNum
	SInt32 dirID;		//application's directory ID	
 
 	if(!reInit)
	 	*fileTableSize=0;

	#ifdef __TARGET_PACKER
	spec=packFolderFSS;
	#else
	#ifdef __TARGET_TOOLAPP
	err=FSMakeFSSpec(0,0,"\p::::Redline.app",&spec);
	if(err==fnfErr)
	{
		err=FSMakeFSSpec(0,0,"\p:::::Redline.app",&spec);
		if(err==fnfErr)
		{
			ShowAlert("Can't find Redline","Please run the Tool Chest apps from the same folder Redline is in, or from the Tool Chest folder inside the Redline folder.");
			ExitToShell();
		}
	}
	#else
    //get FSSpec for application's Bundle
	GetApplicationBundleFSSpec(&spec);
	#endif	
	#endif
	
	//process file's in application bundle
	//and subdirectories into file reference table
	IterateDirectoryLevel(spec.vRefNum,GetDirID(&spec),fileTable,fileTableSize,maxSize);

	#ifndef __TARGET_PACKER	
	//process Plug-In folder if it exists
	err=FSMakeFSSpec(spec.vRefNum,spec.parID,kPlugInDirPName,&spec);
	if(err!=fnfErr)
		IterateDirectoryLevel(spec.vRefNum,GetDirID(&spec),fileTable,fileTableSize,maxSize);
	
	//get reference to Preferences folder
	SInt16 prefVRefNum; 	
	SInt32 prefDirID;		
	HandleError(FindFolder(kOnAppropriateDisk,kPreferencesFolderType,kCreateFolder,&prefVRefNum,&prefDirID));
	
	//see if Redline folder exists
/*	FSSpec configFolderSpec;
	err=FSMakeFSSpec(prefVRefNum,prefDirID,kConfigDirPName,&configFolderSpec);
	SInt32 configFolderDirID;
	//if not create it
	if(err==fnfErr)
		HandleError(FSpDirCreate(&configFolderSpec,smSystemScript,&configFolderDirID));
	//otherwise get the dirID
	else
		configFolderDirID=GetDirID(&configFolderSpec);
*/
	//see if config file exists
	FSSpec configSpec;
	err=FSMakeFSSpec(prefVRefNum,prefDirID,kConfigFilePName,&configSpec);
	//if not create it
	if(err==fnfErr)
		FSpCreate(&configSpec,'????','????',smSystemScript);
	
	//Add Config folder to file table
	AddFile(prefVRefNum,prefDirID,kConfigFilePName,fileTable,fileTableSize,maxSize);
	#endif
	
	//and sort the filetable by file names
	if(!reInit)
		qsort(fileTable,*fileTableSize,sizeof(tFileTableEntry),&FileTableCompare);
	
	printf("%d files.\n",*fileTableSize);
	
	#ifndef __TARGET_PACKER
	if(!FileGetSize(FileGetReference(kConfigFileName)))
	{
		tFileRef defaults=FileGetReference(kConfigDefault16Name);
		int vram=GetVRAMSize();
		if(vram>16*1024*1024)
			defaults=FileGetReference(kConfigDefault32Name);
		if(vram>32*1024*1024)
			defaults=FileGetReference(kConfigDefault64Name);
		FileSetData(FileGetReference(kConfigFileName),FileGetDataPtr(defaults));
	}
	#endif
}


//load a file's data
int FileLoadData(tFileRef fileRef)
{
	short ref;
	HandleError(HOpenDF(((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->vRefNum
					,((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->dirID
					,((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->pName,fsCurPerm,&ref));
	long eof;
	HandleError(GetEOF(ref,&eof));
	
	if(((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->packageOffset==kFileNotPackaged)
	{
		gFileTable[fileRef].data=NewPtr(eof);
		gFileTable[fileRef].size=eof;
		HandleError(FSRead(ref,&eof,gFileTable[fileRef].data));
	}
	else
	{
		long packedSize=((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->packageSize;
		Ptr packedData=NewPtr(packedSize);
		gFileTable[fileRef].data=NewPtr(gFileTable[fileRef].size);
		unsigned long offset=((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->packageOffset;
		HandleError(SetFPos(ref,fsFromStart,offset));
		HandleError(FSRead(ref,&packedSize,packedData));
		
		
		compress_identity *iden;
		xcompress(COMPRESS_ACTION_IDENTITY,0,0,0,0,0,(ULONG*)&iden);
		Ptr workBuffer=NewPtr(iden->memory);	


		UInt32 ignore;
		xcompress(COMPRESS_ACTION_DECOMPRESS,(UBYTE*)workBuffer,(UBYTE*)packedData,packedSize,gFileTable[fileRef].size,(UBYTE*)gFileTable[fileRef].data,(ULONG*)&ignore);
	
		DisposePtr(workBuffer);
		DisposePtr(packedData);
	}
	
	HandleError(FSClose(ref));
	return true;
}

void *FileGetPartialDataPtr(tFileRef fileRef,int offset,int size)
{
	if(((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->packageOffset==kFileNotPackaged)
	{
		short ref;
		HandleError(HOpenDF(((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->vRefNum
						,((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->dirID
					,((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->pName,fsCurPerm,&ref));	
		void *data=NewPtr(size);
		HandleError(SetFPos(ref,fsFromStart,offset));
		long readSize=size;
		HandleError(FSRead(ref,&readSize,data));
		HandleError(FSClose(ref));
		return data;
	}
	else
		FailWithErrorString("Cannot get partial data from packaged file.");
}

//write a file's data
void FileStoreData(tFileRef fileRef)
{
	if(((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->packageOffset==kFileNotPackaged)
	{
		short ref;
		HandleError(HOpenDF(((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->vRefNum
						,((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->dirID
						,((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->pName,fsCurPerm,&ref));
		long eof=gFileTable[fileRef].size;
		HandleError(SetEOF(ref,eof));
		HandleError(FSWrite(ref,&eof,gFileTable[fileRef].data));
		HandleError(FSClose(ref));
	}
	else
		FailWithErrorString("Cannot write to package file!");
}

void FileAppendData(tFileRef fileRef,void *data,int size)
{
	if(((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->packageOffset==kFileNotPackaged)
	{
		short ref;
		HandleError(HOpenDF(((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->vRefNum
						,((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->dirID
						,((tFileSystemLocator*)&(gFileTable[fileRef].fsLoc))->pName,fsCurPerm,&ref));
		long writeSize=size;
		HandleError(SetFPos(ref,fsFromLEOF,0));
		HandleError(FSWrite(ref,&writeSize,data));
		HandleError(FSClose(ref));
	}
	else
		FailWithErrorString("Cannot write to package file!");
}