/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/ 
#include "fatimagegenerator.h"
#include "fatcluster.h"
#include "fsnode.h"
#include "h_utl.h"

#include <memory.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;

const TInt KCharsOfCmdWndLine = 80 ;
const TInt KRootEntryCount = 0x200;
const TInt KRootClusterIndex = 0;

TFatImgGenerator::TFatImgGenerator(TSupportedFatType aType ,ConfigurableFatAttributes& aAttr ) :
iType(aType),
iFatTable(0),
iFatTableBytes(0), 
iTotalClusters(0),	
iBytsPerClus(0)
{
	memset(&iBootSector,0,sizeof(iBootSector));
	memset(&iFat32Ext,0,sizeof(iFat32Ext));
	memset(&iFatHeader,0,sizeof(iFatHeader));
	if(aAttr.iDriveSectorSize != 512 && aAttr.iDriveSectorSize != 1024 && aAttr.iDriveSectorSize != 2048 && aAttr.iDriveSectorSize != 4096) {
		iType = EFatUnknown ;
		return ;
	}
	*((TUint32*)iBootSector.BS_jmpBoot) = 0x00905AEB ; 
	memcpy(iBootSector.BS_OEMName,"SYMBIAN ",8);
	*((TUint16 *)iBootSector.BPB_BytsPerSec) = aAttr.iDriveSectorSize;
	
	iBootSector.BPB_NumFATs = aAttr.iDriveNoOfFATs;
	iBootSector.BPB_Media = 0xF8 ;
	iFatHeader.BS_DrvNum = 0x80 ;
	iFatHeader.BS_BootSig = 0x29 ;

	time_t rawtime;
	time(&rawtime);
	*((TUint32*)iFatHeader.BS_VolID) = (TUint32)rawtime;
	memcpy(iFatHeader.BS_VolLab,aAttr.iDriveVolumeLabel,sizeof(iFatHeader.BS_VolLab));
	if(aAttr.iImageSize == 0){
		if(aType == EFat32)
			aAttr.iImageSize = 0x100000000LL ;// 4G
		else
			aAttr.iImageSize = 0x40000000LL ; // 1G 
	}

	TUint32 totalSectors = (TUint32)((aAttr.iImageSize + aAttr.iDriveSectorSize - 1) / aAttr.iDriveSectorSize);
	if(aType == EFat32) {
		InitAsFat32(totalSectors,aAttr.iSectorPerCluster ,aAttr.iDriveSectorSize);
	}
	else if(aType == EFat16) {
		InitAsFat16(totalSectors,aAttr.iSectorPerCluster,aAttr.iDriveSectorSize); 
	}
	if(iType == EFatUnknown) return ;
	iBytsPerClus = iBootSector.BPB_SecPerClus * aAttr.iDriveSectorSize;
//	if(iBytsPerClus > KMaxClusterBytes){
//		Print(EError,"Cluster too large!\n");
//		iType = EFatUnknown;
//		return ;
//	}
	

}
TFatImgGenerator::~TFatImgGenerator() {
	if(iFatTable)
		delete []iFatTable;  
	Interator it = iDataClusters.begin();
	while(it != iDataClusters.end()){
		TFatCluster* cluster = *it ;
		delete cluster;
		it++;
	}
}

void TFatImgGenerator::InitAsFat16(TUint32 aTotalSectors,TUint8 aSecPerClus,TUint16 aBytsPerSec){
	
	TUint32 numOfClusters ;
	if(aSecPerClus == 0) {
		//Auto-calc the SecPerClus
		// FAT32 ,Count of clusters must >= 4085 and < 65525 , however , to avoid the "off by xx" warning, 
		// proprositional value >= (4085 + 16) && < (65525 - 16)
		if(aTotalSectors < (4085 + 16)) { //when SecPerClus is 1, numOfClusters eq to aTotalSectors
			iType = EFatUnknown ;
			Print(EError,"Size is too small for FAT16, please set a bigger size !\n");
			return ;
		}
		TUint8 secPerClusMax = KMaxClusterBytes / aBytsPerSec; 
		numOfClusters = (aTotalSectors + secPerClusMax - 1) / secPerClusMax ; 
		if(numOfClusters >= (65525 - 16)) { // too big 
			iType = EFatUnknown ;
			Print(EError,"Size is too big for FAT16, please use the FAT32 format!\n");
			return ;
		}
		
		aSecPerClus = 1;
		while(aSecPerClus < secPerClusMax){
			numOfClusters = (aTotalSectors + aSecPerClus - 1) / aSecPerClus ;
			if (numOfClusters >= (4085 + 16) && numOfClusters < (65525 - 16)) {
				break;
			}
			aSecPerClus <<= 1 ; 
		}	
	}
	else {
		if( (aSecPerClus * aBytsPerSec) > KMaxClusterBytes){
			Print(EError,"Cluster too large!\n");
			iType = EFatUnknown;
			return ;
		}
		numOfClusters = (aTotalSectors + aSecPerClus - 1) / aSecPerClus;
		if(numOfClusters >= (65525 - 16)){
      Print(EError,"Cluster count is too big for FAT16, please use the FAT32 format or set a new bigger sector count of cluster!\n");
			iType = EFatUnknown ;
			return ;
		}
		else if(numOfClusters < (4085 + 16)){
      Print(EError,"Cluster count is too small for FAT16, please set a new small sector count of cluster or set the size bigger!\n");
			iType = EFatUnknown ;
			return ;
		}

	}
	iTotalClusters = (aTotalSectors + aSecPerClus - 1) / aSecPerClus ;
	iFatTableBytes = ((iTotalClusters << 1) +  aBytsPerSec - 1) & (~(aBytsPerSec - 1)); 
	iFatTable = new(std::nothrow) char[iFatTableBytes];
	if(!iFatTable) {
        Print(EError,"Memory allocation failed for FAT16 Table!\n");
		iType = EFatUnknown ;
		return ;
	}
	memset(iFatTable,0,iFatTableBytes);
	*((TUint32*)iFatTable) = 0xFFFFFFF8 ; 
	iBootSector.BPB_SecPerClus = aSecPerClus;
	*((TUint16*)iBootSector.BPB_RsvdSecCnt) = 0x0001 ;
	*((TUint16*)iBootSector.BPB_RootEntCnt) = KRootEntryCount ;
	if(aTotalSectors > 0xFFFF)
		*((TUint32*)iBootSector.BPB_TotSec32) = aTotalSectors; 
	else
		*((TUint16*)iBootSector.BPB_TotSec16) = (TUint16)aTotalSectors; 
	TUint16 sectorsForFAT = (TUint16)((iFatTableBytes + aBytsPerSec - 1) / aBytsPerSec);
	*((TUint16*)iBootSector.BPB_FATSz16) =  sectorsForFAT ; 
	memcpy(iFatHeader.BS_FilSysType,"FAT16   ",sizeof(iFatHeader.BS_FilSysType));
}
void TFatImgGenerator::InitAsFat32(TUint32 aTotalSectors,TUint8 aSecPerClus,TUint16 aBytsPerSec) { 
	
	TUint32 numOfClusters;
	if(aSecPerClus == 0) {
		//Auto-calc the SecPerClus
		// FAT32 ,Count of clusters must >= 65525, however , to avoid the "off by xx" warning, 
		// proprositional value >= (65525 + 16)			
		if(aTotalSectors < (65525 + 16)) { //when SecPerClus is 1, numOfClusters eq to aTotalSectors
			iType = EFatUnknown ;
			Print(EError,"Size is too small for FAT32, please use the FAT16 format, or set the data size bigger!\n");
			return ;
		}

		TUint8 secPerClusMax = KMaxClusterBytes / aBytsPerSec; 
		aSecPerClus = secPerClusMax;
		while(aSecPerClus > 1){
			numOfClusters = (aTotalSectors + aSecPerClus - 1) / aSecPerClus ;
			if (numOfClusters >= (65525 + 16)) {
				break;
			}
			aSecPerClus >>= 1 ; 
		}	
	}
	else {
		if( (aSecPerClus * aBytsPerSec) > KMaxClusterBytes){
			Print(EError,"Cluster too large!\n");
			iType = EFatUnknown;
			return ;
		}
		numOfClusters = (aTotalSectors + aSecPerClus - 1) / aSecPerClus;
		if(numOfClusters < (65525 + 16)) {
            Print(EError,"Cluster count is too small for FAT32, please set a new small sector count of cluster or set the size bigger or use the FAT16 format!\n");
			iType = EFatUnknown ;
			return ;
		}

	}
	iTotalClusters = (aTotalSectors + aSecPerClus - 1) / aSecPerClus ;
	iFatTableBytes = ((iTotalClusters << 2) +  aBytsPerSec - 1) & (~(aBytsPerSec - 1));
	iFatTable = new(std::nothrow) char[iFatTableBytes];
	if(!iFatTable) {
        Print(EError,"Memory allocation failed for FAT32 Table!\n");
		iType = EFatUnknown ;
		return ;
	}
	memset(iFatTable,0,iFatTableBytes);
	TUint32* fat32table = reinterpret_cast<TUint32*>(iFatTable);
	fat32table[0] = 0x0FFFFFF8 ;
	fat32table[1] = 0x0FFFFFFF ;  
	iBootSector.BPB_SecPerClus = aSecPerClus;
	iBootSector.BPB_RsvdSecCnt[0] = 0x20 ;
	*((TUint32*)iBootSector.BPB_TotSec32) = aTotalSectors; 
	*((TUint32*)iFat32Ext.BPB_FATSz32) =  (iFatTableBytes + aBytsPerSec - 1) / aBytsPerSec; 
	*((TUint32*)iFat32Ext.BPB_RootClus) = 2 ; 
	*((TUint16*)iFat32Ext.BPB_FSInfo) = 1 ;
	*((TUint16*)iFat32Ext.BPB_BkBootSec) = 6 ;
	memcpy(iFatHeader.BS_FilSysType,"FAT32   ",sizeof(iFatHeader.BS_FilSysType));
}

bool TFatImgGenerator::Execute(TFSNode* aRootDir , const char* aOutputFile){
	if(EFatUnknown == iType)
		return false ;	
	ofstream o(aOutputFile,ios_base::binary + ios_base::out + ios_base::trunc);
	TUint32 writtenBytes = 0 ;
	if(!o.is_open()) {
  	Print(EError,"Can not open \"%s\" for writing !\n",aOutputFile) ;
		return false;
	}
	TUint16 bytsPerSector = *((TUint16*)iBootSector.BPB_BytsPerSec);
	Interator it = iDataClusters.begin();
	while(it != iDataClusters.end()){
		TFatCluster* cluster = *it ;
		delete cluster;
		it++;
	}
	iDataClusters.clear();
	Print(EAlways,"Filesystem ready.\nWriting Header...");
	
	if(EFat16 == iType){		 
		char* header = new(std::nothrow) char[bytsPerSector];
		if(!header){
      Print(EError,"Can not allocate memory for FAT16 header!\n");
			o.close();
			return false ;
		}
		int offset = 0;
		memcpy(header,&iBootSector,sizeof(iBootSector));
		offset = sizeof(iBootSector);
		memcpy(&header[offset],&iFatHeader,sizeof(iFatHeader));
		offset += sizeof(iFatHeader);
		memset(&header[offset],0,bytsPerSector - offset);
		*((TUint16*)(&header[510])) = 0xAA55 ;

		o.write(header,bytsPerSector); 
		writtenBytes +=  bytsPerSector;
		delete []header ;		 
		TUint16 rootDirSectors = (KRootEntryCount * 32) / bytsPerSector ;
		TUint16 rootDirClusters = (rootDirSectors + iBootSector.BPB_SecPerClus - 1) /iBootSector.BPB_SecPerClus;		 
		TUint32 rootDirBytes = KRootEntryCount * 32;
		TFatCluster* rootDir = new(std::nothrow) TFatCluster(0,rootDirClusters);
		rootDir->Init(rootDirBytes);
		iDataClusters.push_back(rootDir);
		aRootDir->WriteDirEntries(KRootClusterIndex,rootDir->GetData());
		 
		TUint index = 2 ;
		Print(EAlways,"    OK.\nPreparing cluster list..."); 
		TFSNode* child = aRootDir->GetFirstChild() ; 
		while(child){
			if(!PrepareClusters(index,child)){
                Print(EAlways,"    Failed.\nError:Image size is expected to be big enough for all the files.\n");
				return false ;
			}
			child = child->GetSibling() ;
		}
	}
	else if(EFat32 == iType){

		TUint headerSize = ( bytsPerSector << 5 ); // 32 reserved sectors for fat32
		char* header = new(std::nothrow) char[headerSize];
		if(!header){
            Print(EError,"Can not allocate memory for FAT32 header!\n");
			o.close();
			return false ;
		}
		memset(header,0,headerSize);

		int offset = 0;
		memcpy(header,&iBootSector,sizeof(iBootSector));
		offset = sizeof(iBootSector);
		memcpy(&header[offset],&iFat32Ext,sizeof(iFat32Ext));
		offset += sizeof(iFat32Ext);
		memcpy(&header[offset],&iFatHeader,sizeof(iFatHeader));
		offset += sizeof(iFatHeader);

		TFAT32FSInfoSector* fsinfo = reinterpret_cast<TFAT32FSInfoSector*>(&header[bytsPerSector]);
		*((TUint32*)fsinfo->FSI_LeadSig) = 0x41615252 ;
		*((TUint32*)fsinfo->FSI_StrucSig) = 0x61417272 ;
		memset(fsinfo->FSI_Free_Count,0xFF,8);
		char* tailed = header + 510 ;
		for(int i = 0 ; i < 32 ; i++ , tailed += bytsPerSector )
			*((TUint16*)tailed) = 0xAA55 ;		
		 
		TUint index = 2 ;		
		Print(EAlways,"    OK.\nPreparing cluster list...");
		if(!PrepareClusters(index,aRootDir)) {
            Print(EAlways,"    Failed.\nERROR: Image size is expected to be big enough for all the files.\n");
			delete []header ;
			return false;
		}
	 
 
		*(TUint32*)(fsinfo->FSI_Free_Count) = iTotalClusters - index + 3;
		*(TUint32*)(fsinfo->FSI_Nxt_Free) =  index ;

		// write bakup boot sectors
		memcpy(&header[bytsPerSector * 6],header,(bytsPerSector << 1));
		o.write(header,headerSize); 
		writtenBytes += headerSize;
		delete []header ;
	}
	//iDataClusters.sort();
	it = iDataClusters.end() ;
	it -- ;
	int clusters = (*it)->GetIndex() + (*it)->ActualClusterCount() - 1;

	Print(EAlways,"    OK.\n%d clusters of data need to be written.\nWriting Fat table...",clusters);
	for(TUint8 w = 0 ; w < iBootSector.BPB_NumFATs ; w++){
		o.write(iFatTable,iFatTableBytes);	 
		if(o.bad() || o.fail()){
			Print(EAlways,"\nERROR:Writting failed. Please check the filesystem\n");
			delete []iFatTable,
			o.close();
			return false ;
		}
		writtenBytes += iFatTableBytes;
	}
	char* buffer = new(std::nothrow) char[KBufferedIOBytes];
	if(!buffer){
    Print(EError,"Can not allocate memory for I/O buffer !\n");
		o.close();
		return false ;
	}
	o.flush();
	Print(EAlways,"    OK.\nWriting clusters data...");
 
	int bytesInBuffer = 0;
	int writeTimes = 24; 
 
	TFatCluster* lastClust = 0 ;	
	for(it = iDataClusters.begin(); it != iDataClusters.end() ; it++ ){
		TFatCluster* cluster = *it ;
		TUint fileSize = cluster->GetSize(); 		 
		TUint toProcess = cluster->ActualClusterCount() * iBytsPerClus ; 
		if(toProcess > KBufferedIOBytes){ // big file 
			if(bytesInBuffer > 0){
				o.write(buffer,bytesInBuffer); 
				if(o.bad() || o.fail()){
					Print(EError,"Writting failed.\n");
					delete []buffer,
					o.close();
					return false ;
				}
				writtenBytes += bytesInBuffer;
				bytesInBuffer = 0;
				Print(EAlways,".");
				writeTimes ++ ;
				if((writeTimes % KCharsOfCmdWndLine) == 0){
					o.flush();
					cout << endl ;
				} 
			}
			if(cluster->IsLazy()){
				ifstream ifs(cluster->GetFileName(), ios_base::binary + ios_base::in);
				if(!ifs.is_open()){
					Print(EError,"Can not open file \"%s\"\n",cluster->GetFileName()) ;
					o.close();
					delete []buffer;
					return false ;
				}
				if(!ifs.good()) ifs.clear(); 
				TUint processedBytes = 0 ; 

				while(processedBytes < 	fileSize){
					TUint ioBytes = fileSize - processedBytes ;
					if(ioBytes > KBufferedIOBytes)
						ioBytes = KBufferedIOBytes;
					ifs.read(buffer,ioBytes);
					processedBytes += ioBytes;					 
					o.write(buffer,ioBytes); 
					if(o.bad() || o.fail()){
						Print(EError,"Writting failed.\n");
						delete []iFatTable,
						o.close();
						return false ;
					}
					writtenBytes += ioBytes;
					Print(EAlways,".");
					writeTimes ++ ;
					if((writeTimes % KCharsOfCmdWndLine) == 0){
						o.flush();
						Print(EAlways,"\n") ;
					}

				}
				TUint paddingBytes = toProcess - processedBytes;
				if( paddingBytes > 0 ){
					memset(buffer,0,paddingBytes);
					o.write(buffer,paddingBytes);
					if(o.bad() || o.fail()){
						Print(EError,"Writting failed.\n");
						delete []buffer,
						o.close();
						return false ;
					}
					writtenBytes += paddingBytes;
				}
				ifs.close();

			}
			else {
				// impossible 
        Print(EError,"Unexpected result!\n");
				o.close();
				delete []buffer;
				return false ;
			}
		}
		else {
			if(toProcess > (KBufferedIOBytes - bytesInBuffer)){
				o.write(buffer,bytesInBuffer); 
				if(o.bad() || o.fail()){
					Print(EError,"Writting failed.\n");
					delete []buffer,
					o.close();
					return false ;
				}
				writtenBytes += bytesInBuffer;
				Print(EAlways,".");
				writeTimes ++ ;
				if((writeTimes % KCharsOfCmdWndLine) == 0){
					o.flush();
					cout  << endl ;
				}
				bytesInBuffer = 0;
			}
			if(cluster->IsLazy()){
				ifstream ifs(cluster->GetFileName(), ios_base::binary + ios_base::in);
				if(!ifs.is_open()){
				    Print(EError,"Can not open file \"%s\"\n",cluster->GetFileName()) ;
					o.close();
					delete []buffer;
					return false ;
				}
				if(!ifs.good()) ifs.clear(); 
				ifs.read(&buffer[bytesInBuffer],fileSize);
				bytesInBuffer += fileSize;
				if(toProcess > fileSize) { // fill padding bytes 
					memset(&buffer[bytesInBuffer],0,toProcess - fileSize);
					bytesInBuffer += (toProcess - fileSize);
				}
				ifs.close();

			}
			else{
				if(toProcess != cluster->GetSize() && cluster->GetIndex() != KRootClusterIndex){
        	Print(EError,"Unexpected size!\n");
					o.close();
					delete []buffer;
					return false ;
				}
				memcpy(&buffer[bytesInBuffer],cluster->GetData(),cluster->GetSize());
				bytesInBuffer += cluster->GetSize();
			}

		} 
		lastClust = cluster ;	 

	}
	if(bytesInBuffer > 0){
		o.write(buffer,bytesInBuffer);
		if(o.bad() || o.fail()){
			Print(EError,"Writting failed.\n");
			delete []buffer,
			o.close();
			return false ;
		}
		writtenBytes += bytesInBuffer;
		o.flush();
	}
	Print(EAlways,"\nDone.\n\n");
	o.close();

	return true ;
}
bool TFatImgGenerator::PrepareClusters(TUint& aNextClusIndex,TFSNode* aNode) { 
	TUint sizeOfItem = aNode->GetSize();
	TUint clusters = (sizeOfItem + iBytsPerClus - 1) / iBytsPerClus;
	
	if(iTotalClusters < aNextClusIndex + clusters)
		return false ;
		
	TUint16* fat16Table = reinterpret_cast<TUint16*>(iFatTable);
	TUint32* fat32Table = reinterpret_cast<TUint32*>(iFatTable);	 
	 
	for(TUint i = aNextClusIndex + clusters - 1 ; i > aNextClusIndex  ; i--){
		if(iType == EFat16)
			fat16Table[i - 1] = i ;
		else
			fat32Table[i - 1] = i ;
	}
	if(iType == EFat16)
		fat16Table[aNextClusIndex + clusters - 1] = 0xffff ;
	else
		fat32Table[aNextClusIndex + clusters - 1] = 0x0fffffff ;
		
	TFatCluster* cluster = new TFatCluster(aNextClusIndex,clusters);
	if(aNode->IsDirectory()) {
    TUint bytes = clusters * iBytsPerClus ;
		cluster->Init(bytes);
		aNode->WriteDirEntries(aNextClusIndex,cluster->GetData());
	}
	else {
		cluster->LazyInit(aNode->GetPCSideName(),sizeOfItem);
		aNode->WriteDirEntries(aNextClusIndex,NULL);
	}
	iDataClusters.push_back(cluster);
 
	aNextClusIndex += clusters;
	if(aNode->GetFirstChild()){
		if(!PrepareClusters(aNextClusIndex,aNode->GetFirstChild()))
			return false ;
	}
	if(aNode->GetSibling()){
		if(!PrepareClusters(aNextClusIndex,aNode->GetSibling()))
			return false;
	}
	return true ;
}
