// Functions for processing FATX partitions
// (c) 2001 Andrew de Quincey

#include "boot.h"
#include "BootFATX.h"
#include <sys/types.h>


#undef FATX_DEBUG

//#define FATX_INFO

int checkForLastDirectoryEntry(unsigned char* entry) {

	// if the filename length byte is 0 or 0xff,
	// this is the last entry
	if ((entry[0] == 0xff) || (entry[0] == 0)) {
		return 1;
	}

	// wasn't last entry
	return 0;
}

int LoadFATXFilefixed(FATXPartition *partition,char *filename, FATXFILEINFO *fileinfo,u8* Position) {

	if(partition == NULL) {
		VIDEO_ATTR=0xffe8e8e8;
	} else {
		if(FATXFindFile(partition,filename,FATX_ROOT_FAT_CLUSTER,fileinfo)) {
#ifdef FATX_DEBUG
			printk("ClusterID : %d\n",fileinfo->clusterId);
			printk("fileSize  : %d\n",fileinfo->fileSize);
#endif
			fileinfo->buffer = Position;
			memset(fileinfo->buffer,0xff,fileinfo->fileSize);
			
			if(FATXLoadFromDisk(partition, fileinfo)) {
				return true;
			} else {
#ifdef FATX_INFO
				printk("LoadFATXFile : error loading %s\n",filename);
#endif
				return false;
			}
		} else {
#ifdef FATX_INFO
			printk("LoadFATXFile : file %s not found\n",filename);
#endif
			return false;
		}
	}
	return false;
}

int LoadFATXFile(FATXPartition *partition,char *filename, FATXFILEINFO *fileinfo) {

	if(partition == NULL) {
		VIDEO_ATTR=0xffe8e8e8;
#ifdef FATX_INFO
		printk("LoadFATXFile : no open FATX partition\n");
#endif
	} else {
		if(FATXFindFile(partition,filename,FATX_ROOT_FAT_CLUSTER,fileinfo)) {
#ifdef FATX_DEBUG
			printk("ClusterID : %d\n",fileinfo->clusterId);
			printk("fileSize  : %d\n",fileinfo->fileSize);
#endif
			fileinfo->buffer = malloc(fileinfo->fileSize);
			memset(fileinfo->buffer,0,fileinfo->fileSize);
			if(FATXLoadFromDisk(partition, fileinfo)) {
				return true;
			} else {
#ifdef FATX_INFO
				printk("LoadFATXFile : error loading %s\n",filename);
#endif
				return false;
			}
		} else {
#ifdef FATX_INFO
			printk("LoadFATXFile : file %s not found\n",filename);
#endif
			return false;
		}
	}
	return false;
}

void PrintFAXPartitionTable(int nDriveIndex) {

	u8 ba[512];
	FATXPartition *partition = NULL;
	FATXFILEINFO fileinfo;

	VIDEO_ATTR=0xffe8e8e8;
	printk("FATX Partition Table:\n");
	memset(&fileinfo,0,sizeof(FATXFILEINFO));

	if(FATXSignature(nDriveIndex,SECTOR_SYSTEM,ba)) {
		VIDEO_ATTR=0xffe8e8e8;
		printk("Partition SYSTEM\n");
		partition = OpenFATXPartition(nDriveIndex,SECTOR_SYSTEM,SYSTEM_SIZE);
		if(partition == NULL) {
			VIDEO_ATTR=0xffe8e8e8;
			printk("PrintFAXPartitionTable : error on opening STORE\n");
		} else {
			DumpFATXTree(partition);
		}
	}

	VIDEO_ATTR=0xffc8c8c8;
}

int FATXSignature(int nDriveIndex,unsigned int block,u8 *ba) {

	if(BootIdeReadSector(0, &ba[0], block, 0, 512)) {
		VIDEO_ATTR=0xffe8e8e8;
#ifdef FATX_INFO
		printk("FATXSignature : Unable to read FATX sector\n");
#endif
		return false;
	} else {
		if( (ba[0]=='F') && (ba[1]=='A') && (ba[2]=='T') && (ba[3]=='X') ) {
			return true;
		} else {
			return false;
		}
	}
}

FATXPartition *OpenFATXPartition(int nDriveIndex,
		unsigned int partitionOffset,
		u_int64_t partitionSize) {
	unsigned char partitionInfo[FATX_PARTITION_HEADERSIZE];
	FATXPartition *partition;
	int readSize;
	unsigned int chainTableSize;

#ifdef FATX_DEBUG
	printk("OpenFATXPartition : Read partition header\n");
#endif
	// load the partition header
	readSize = FATXRawRead(nDriveIndex, partitionOffset, 0,
			FATX_PARTITION_HEADERSIZE, (char *)&partitionInfo);

  	if (readSize != FATX_PARTITION_HEADERSIZE) {
		VIDEO_ATTR=0xffe8e8e8;
#ifdef FATX_INFO
		printk("OpenFATXPartition : Out of data while reading partition header\n");
#endif
		return NULL;
	}

	// check the magic
	if (*((u_int32_t*) &partitionInfo) != FATX_PARTITION_MAGIC) {
		VIDEO_ATTR=0xffe8e8e8;
#ifdef FATX_INFO
		printk("OpenFATXPartition : No FATX partition found at requested offset\n");
#endif
		return NULL;
	}

#ifdef FATX_DEBUG
	printk("OpenFATXPartition : Allocating Partition struct\n");
#endif
	// make up new structure
	partition = (FATXPartition*) malloc(sizeof(FATXPartition));
	if (partition == NULL) {
#ifdef FATX_INFO
		printk("OpenFATXPartition : Out of memory\n");
#endif
		return NULL;
	}
#ifdef FATX_DEBUG
	printk("OpenFATXPartition : Allocating Partition struct done\n");
#endif
	memset(partition,0,sizeof(FATXPartition));

	// setup the easy bits
	partition->nDriveIndex = nDriveIndex;
	partition->partitionStart = partitionOffset;
	partition->partitionSize = partitionSize;
	partition->clusterSize = 0x4000;
	partition->clusterCount = partition->partitionSize / 0x4000;
	partition->chainMapEntrySize = (partition->clusterCount >= 0xfff4) ? 4 : 2;

	// Now, work out the size of the cluster chain map table
	chainTableSize = partition->clusterCount * partition->chainMapEntrySize;
	if (chainTableSize % FATX_CHAINTABLE_BLOCKSIZE) {
		// round up to nearest FATX_CHAINTABLE_BLOCKSIZE bytes
		chainTableSize = ((chainTableSize / FATX_CHAINTABLE_BLOCKSIZE) + 1)
				* FATX_CHAINTABLE_BLOCKSIZE;
	}

#ifdef FATX_DEBUG
	printk("OpenFATXPartition : Allocating chaintable struct\n");
#endif
  	// Load the cluster chain map table
	partition->clusterChainMap.words = (u_int16_t*) malloc(chainTableSize);
    	if (partition->clusterChainMap.words == NULL) {
		VIDEO_ATTR=0xffe8e8e8;
#ifdef FATX_INFO
		printk("OpenFATXPartition : Out of memory\n");
#endif
		return NULL;
	}

#ifdef FATX_DEBUG
	printk("Part stats : CL Count	%d \n", partition->clusterCount);
	printk("Part stats : CL Size	%d \n", partition->clusterSize);
	printk("Part stats : CM Size	%d \n", partition->chainMapEntrySize);
	printk("Part stats : Table Size	%d \n", chainTableSize);
	printk("Part stats : Part Size	%d \n", partition->partitionSize);
#endif

	readSize = FATXRawRead(nDriveIndex, partitionOffset, FATX_PARTITION_HEADERSIZE,
			chainTableSize, (char *)partition->clusterChainMap.words);

    	if (readSize != chainTableSize) {
		VIDEO_ATTR=0xffe8e8e8;
#ifdef FATX_INFO
		printk("Out of data while reading cluster chain map table\n");
#endif
	}
	partition->cluster1Address = ( ( FATX_PARTITION_HEADERSIZE + chainTableSize) );

	return partition;
}

void DumpFATXTree(FATXPartition *partition) {
	// OK, start off the recursion at the root FAT
	_DumpFATXTree(partition, FATX_ROOT_FAT_CLUSTER, 0);
}

void _DumpFATXTree(FATXPartition* partition, int clusterId, int nesting) {

	int endOfDirectory;
	unsigned char* curEntry;
	unsigned char clusterData[partition->clusterSize];
	int i,j;
	char writeBuf[512];
	char filename[50];
	u_int32_t filenameSize;
	u_int32_t fileSize;
	u_int32_t entryClusterId;
	unsigned char flags;
	char flagsStr[5];

	// OK, output all the directory entries
	endOfDirectory = 0;
	while(clusterId != -1) {
		LoadFATXCluster(partition, clusterId, clusterData);

		// loop through it, outputing entries
		for(i=0; i< partition->clusterSize / FATX_DIRECTORYENTRY_SIZE; i++) {

			// work out the currentEntry
			curEntry = clusterData + (i * FATX_DIRECTORYENTRY_SIZE);

			// first of all, check that it isn't an end of directory marker
			if (checkForLastDirectoryEntry(curEntry)) {
				endOfDirectory = 1;
				break;
			}

			// get the filename size
			filenameSize = curEntry[0];

			// check if file is deleted
			if (filenameSize == 0xE5) {
				continue;
			}

			// check size is OK
			if ((filenameSize < 1) || (filenameSize > FATX_FILENAME_MAX)) {
				VIDEO_ATTR=0xffe8e8e8;
				printk("_DumpFATXTree : Invalid filename size: %i\n", filenameSize);
			}

			// extract the filename
			memset(filename, 0, 50);
			memcpy(filename, curEntry+2, filenameSize);
			filename[filenameSize] = 0;

			// get rest of data
			flags = curEntry[1];
			entryClusterId = *((u_int32_t*) (curEntry + 0x2c));
			fileSize = *((u_int32_t*) (curEntry + 0x30));

			// wipe fileSize
			if (flags & FATX_FILEATTR_DIRECTORY) {
				fileSize = 0;
			}

			// zap flagsStr
			strcpy(flagsStr, "    ");

			// work out other flags
			if (flags & FATX_FILEATTR_READONLY) {
	              		flagsStr[0] = 'R';
			}
			if (flags & FATX_FILEATTR_HIDDEN) {
				flagsStr[1] = 'H';
			}
			if (flags & FATX_FILEATTR_SYSTEM) {
				flagsStr[2] = 'S';
			}
			if (flags & FATX_FILEATTR_ARCHIVE) {
				flagsStr[3] = 'A';
			}

			// check we don't have any unknown flags
/*
			if (flags & 0xc8) {
				VIDEO_ATTR=0xffe8e8e8;
				printk("WARNING: file %s has unknown flags %x\n", filename, flags);
			}
*/
			
			// Output it
			for(j=0; j< nesting; j++) {
				writeBuf[j] = ' ';
			}

			VIDEO_ATTR=0xffe8e8e8;
			printk("/%s  [%s] (SZ:%i CL%x))\n",filename, flagsStr,
					fileSize, entryClusterId);

			// If it is a sub-directory, recurse
			/*
			if (flags & FATX_FILEATTR_DIRECTORY) {
				_DumpFATXTree(partition, entryClusterId, nesting+1);
			}
			*/
			// have we hit the end of the directory yet?
		}		
		if (endOfDirectory) {
			break;
		}	
		clusterId = getNextClusterInChain(partition, clusterId);
	}
}

int FATXLoadFromDisk(FATXPartition* partition, FATXFILEINFO *fileinfo) {

	unsigned char clusterData[partition->clusterSize];
	int fileSize = fileinfo->fileSize;
	int written;
	int clusterId = fileinfo->clusterId;
	u8 *ptr;

	fileinfo->fileRead = 0;
	ptr = fileinfo->buffer;

	// loop, outputting clusters
	while(clusterId != -1) {
		// Load the cluster data
		LoadFATXCluster(partition, clusterId, clusterData);

		// Now, output it
		written = (fileSize <= partition->clusterSize) ? fileSize : partition->clusterSize;
		memcpy(ptr,clusterData,written);
		fileSize -= written;
		fileinfo->fileRead+=written;
		ptr+=written;

		// Find next cluster
		clusterId = getNextClusterInChain(partition, clusterId);
	}

	// check we actually found enough data
	if (fileSize != 0) {
#ifdef FATX_INFO
   	printk("Hit end of cluster chain before file size was zero\n");
#endif
		//free(fileinfo->buffer);
		//fileinfo->buffer = NULL;
		return false;
	}
	return true;
}

int FATXFindFile(FATXPartition* partition,
                    char* filename,
                    int clusterId, FATXFILEINFO *fileinfo) {

	int i = 0;
#ifdef FATX_DEBUG
	VIDEO_ATTR=0xffc8c8c8;
	printk("FATXFindFile : %s\n",filename);
#endif

  	// convert any '\' to '/' characters
  	while(filename[i] != 0) {
	    	if (filename[i] == '\\') {
      			filename[i] = '/';
	    	}
	    	i++;
  	}
  	
	// skip over any leading / characters
  	i=0;
  	while((filename[i] != 0) && (filename[i] == '/')) {
	    	i++;
  	}

	return _FATXFindFile(partition,&filename[i],clusterId,fileinfo);
}

int _FATXFindFile(FATXPartition* partition,
                    char* filename,
                    int clusterId, FATXFILEINFO *fileinfo) {
	unsigned char* curEntry;
	unsigned char clusterData[partition->clusterSize];
	int i = 0;
	int endOfDirectory;
	u_int32_t filenameSize;
	u_int32_t flags;
	u_int32_t entryClusterId;
	u_int32_t fileSize;
	char seekFilename[50];
	char foundFilename[50];
	char* slashPos;
	int lookForDirectory = 0;
	int lookForFile = 0;


	// work out the filename we're looking for
	slashPos = strrchr0(filename, '/');
	if (slashPos == NULL) {
	// looking for file
		lookForFile = 1;

		// check seek filename size
		if (strlen(filename) > FATX_FILENAME_MAX) {
#ifdef FATX_INFO
			printk("Bad filename supplied (one leafname is too big)\n");
#endif
			return false;
		}

		// copy the filename to look for
		strcpy(seekFilename, filename);
	} else {
		// looking for directory
		lookForDirectory = 1;

		// check seek filename size
		if ((slashPos - filename) > FATX_FILENAME_MAX) {
#ifdef FATX_INFO
			printk("Bad filename supplied (one leafname is too big)\n");
#endif
			return false;
		}

		// copy the filename to look for
		memcpy(seekFilename, filename, slashPos - filename);
		seekFilename[slashPos - filename] = 0;
	}

#ifdef FATX_DEBUG
	VIDEO_ATTR=0xffc8c8c8;
	printk("_FATXFindFile : %s\n",filename);
#endif
	// OK, search through directory entries
	endOfDirectory = 0;
	while(clusterId != -1) {
    		// load cluster data
    		LoadFATXCluster(partition, clusterId, clusterData);

		// loop through it, outputing entries
		for(i=0; i< partition->clusterSize / FATX_DIRECTORYENTRY_SIZE; i++) {
			// work out the currentEntry
			curEntry = clusterData + (i * FATX_DIRECTORYENTRY_SIZE);

			// first of all, check that it isn't an end of directory marker
			if (checkForLastDirectoryEntry(curEntry)) {
				endOfDirectory = 1;
				break;
			}

			// get the filename size
			filenameSize = curEntry[0];

			// check if file is deleted
			if (filenameSize == 0xE5) {
				continue;
			}

			// check size is OK
			if ((filenameSize < 1) || (filenameSize > FATX_FILENAME_MAX)) {
#ifdef FATX_INFO
				printk("Invalid filename size: %i\n", filenameSize);
#endif
				return false;
			}

			// extract the filename
			memset(foundFilename, 0, 50);
			memcpy(foundFilename, curEntry+2, filenameSize);
			foundFilename[filenameSize] = 0;

			// get rest of data
			flags = curEntry[1];
			entryClusterId = *((u_int32_t*) (curEntry + 0x2c));
			fileSize = *((u_int32_t*) (curEntry + 0x30));

			// is it what we're looking for...
			if (strlen(seekFilename)==strlen(foundFilename) && strncmp(foundFilename, seekFilename,strlen(seekFilename)) == 0) {
				// if we're looking for a directory and found a directory
				if (lookForDirectory) {
					if (flags & FATX_FILEATTR_DIRECTORY) {
						return _FATXFindFile(partition, slashPos+1, entryClusterId,fileinfo);
					} else {
#ifdef FATX_INFO
						printk("File not found\n");
#endif
						return false;
					}
				}

				// if we're looking for a file and found a file
				if (lookForFile) {
					if (!(flags & FATX_FILEATTR_DIRECTORY)) {
						fileinfo->clusterId = entryClusterId;
						fileinfo->fileSize = fileSize;
						memset(fileinfo->filename,0,sizeof(fileinfo->filename));
						strcpy(fileinfo->filename,filename);
						return true;
					} else {
#ifdef FATX_INFO
						printk("File not found %s\n",filename);
#endif
						return false;
					}
				}
			}
		}

		// have we hit the end of the directory yet?
		if (endOfDirectory) {
			break;
		}

		// Find next cluster
		clusterId = getNextClusterInChain(partition, clusterId);
	}

	// not found it!
#ifdef FATX_INFO
	printk("File not found\n");
#endif
	return false;
}



u_int32_t getNextClusterInChain(FATXPartition* partition, int clusterId) {
	int nextClusterId = 0;
	u_int32_t eocMarker = 0;
	u_int32_t rootFatMarker = 0;
	u_int32_t maxCluster = 0;

	// check
	if (clusterId < 1) {
		VIDEO_ATTR=0xffe8e8e8;
		printk("getNextClusterInChain : Attempt to access invalid cluster: %i\n", clusterId);
	}

	// get the next ID
	if (partition->chainMapEntrySize == 2) {
		nextClusterId = partition->clusterChainMap.words[clusterId];
	        eocMarker = 0xffff;
		rootFatMarker = 0xfff8;
		maxCluster = 0xfff4;
	} else if (partition->chainMapEntrySize == 4) {
		nextClusterId = partition->clusterChainMap.dwords[clusterId];
		eocMarker = 0xffffffff;
		rootFatMarker = 0xfffffff8;
		maxCluster = 0xfffffff4;
	} else {
		VIDEO_ATTR=0xffe8e8e8;
		printk("getNextClusterInChain : Unknown cluster chain map entry size: %i\n", partition->chainMapEntrySize);
	}

	// is it the end of chain?
  	if ((nextClusterId == eocMarker) || (nextClusterId == rootFatMarker)) {
		return -1;
	}
	
	// is it something else unknown?
	if (nextClusterId == 0) {
		VIDEO_ATTR=0xffe8e8e8;
		printk("getNextClusterInChain : Cluster chain problem: Next cluster after %i is unallocated!\n", clusterId);
        }
	if (nextClusterId > maxCluster) {
		printk("getNextClusterInChain : Cluster chain problem: Next cluster after %i has invalid value: %i\n", clusterId, nextClusterId);
	}
	
	// OK!
	return nextClusterId;
}

void LoadFATXCluster(FATXPartition* partition, int clusterId, unsigned char* clusterData) {
	u_int64_t clusterAddress;
	u_int64_t readSize;
	
	// work out the address of the cluster
	clusterAddress = partition->cluster1Address + ((unsigned long long)(clusterId - 1) * partition->clusterSize);

	// Now, load it
	readSize = FATXRawRead(partition->nDriveIndex, partition->partitionStart,
			clusterAddress, partition->clusterSize, clusterData);

        if (readSize != partition->clusterSize) {
		printk("LoadFATXCluster : Out of data while reading cluster %i\n", clusterId);
	}
}
	    

int FATXRawRead(int drive, int sector, unsigned long long byte_offset, int byte_len, char *buf) {

	int byte_read;
	
	byte_read = 0;

//	printk("rawread: sector=0x%X, byte_offset=0x%X, len=%d\n", sector, byte_offset, byte_len);

        sector+=byte_offset/512;
        byte_offset%=512;

        while(byte_len) {
		int nThisTime=512;
		if(byte_len<512) nThisTime=byte_len;
                if(byte_offset) {
	                u8 ba[512];
			if(BootIdeReadSector(drive, buf, sector, 0, 512)) {
				VIDEO_ATTR=0xffe8e8e8;
				printk("Unable to get first sector\n");
                                return false;
			}
			memcpy(buf, &ba[byte_offset], nThisTime-byte_offset);
			buf+=nThisTime-byte_offset;
			byte_len-=nThisTime-byte_offset;
			byte_read += nThisTime-byte_offset;
			byte_offset=0;
		} else {
			if(BootIdeReadSector(drive, buf, sector, 0, nThisTime)) {
				VIDEO_ATTR=0xffe8e8e8;
				printk("Unable to get first sector\n");
				return false;
			}
			buf+=nThisTime;
			byte_len-=nThisTime;
			byte_read += nThisTime;
		}
		sector++;
	}
	return byte_read;
}

void CloseFATXPartition(FATXPartition* partition) {
	if(partition != NULL) {
		free(partition->clusterChainMap.words);
		free(partition);
		partition = NULL;
	}
}
