/*
* Copyright (c) 1996-2009 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 <e32std.h>
#include <e32std_private.h>
#include <e32uid.h>
#include <f32file.h>
#include "h_utl.h"
#include <string.h>
#include <stdlib.h>
#include "r_obey.h"
#include "r_rofs.h"
#include "r_coreimage.h"
#include "memmap.h"
extern TInt gLogLevel;
extern TBool gLowMem;
extern TBool gFastCompress;
extern TInt gThreadNum;
////////////////////////////////////////////////////////////////////////



inline TUint32 AlignData(TUint32 anAddr)
	{
	return ((anAddr+0x0f)&~0x0f);
	}


////////////////////////////////////////////////////////////////////////

E32Rofs::E32Rofs(CObeyFile *aObey)
//
// Constructor
//
	: iObey( aObey ), iOverhead(0)
	{

	iSize=aObey->iRomSize;
	if(gLowMem)
	{
		iImageMap = new Memmap();

		if(iImageMap == NULL)
		{
			iSize = 0;
			Print(EError, "Out of memory.\n");
		}
		else
		{
			iImageMap->SetMaxMapSize(iSize);
			if(iImageMap->CreateMemoryMap(0, 0xff) == EFalse)
			{
				iSize = 0;
				Print(EError, "Failed to create image map object");

				iImageMap->CloseMemoryMap(ETrue);
				delete iImageMap;
				iImageMap = NULL;
			}
			else
			{
				iData = iImageMap->GetMemoryMapPointer();
			}
		}
	}
	else
	{
		iData=new char [iSize];
		if (iData==NULL)
			iSize=0;
		HMem::Set(iData, 0xff, iSize);
	}

	}

E32Rofs::~E32Rofs()
//
// Destructor
//
	{

	if(gLowMem)
	{
		iImageMap->CloseMemoryMap(ETrue);
		delete iImageMap;
	}
	else
		delete iData;
	}


TInt E32Rofs::CreateExtension(MRofsImage* aImage) 
	{

	TUint8* addr=(TUint8*)iData;

	TRomNode* pRootDir = aImage->RootDirectory();
	

	const TInt extensionRofsheaderSize = KExtensionRofsHeaderSize;
	
	// aImage->iSize contains the max size of the core image

	// Layout the directory structure. Does not actually write it
	// to the image. Returns the number of bytes used for the directory
	// structure within the image.
	TInt directoryOffset = extensionRofsheaderSize;
	const TInt directorySize = LayoutDirectory( pRootDir, aImage->Size()+directoryOffset );
	if( directorySize <= 0 )
		{
		Print(EError, "Failed laying out directories - return code %d\n", directorySize);
		return KErrGeneral;
		}

	// get offset to start of file data, rounded up to next word boundary
	TInt offs = extensionRofsheaderSize + directorySize;
	const TInt fileDataStartOffset = offs + ( (4 - offs) & 3);

	// Now we traverse the list of entries placing each file
	// This updates the directory entries to point to the correct offset
	// to the start of the file
	const TInt fileDataSize = PlaceFiles( pRootDir, addr + fileDataStartOffset, fileDataStartOffset + aImage->Size(), aImage->Size());
	if( fileDataSize <= 0 )
		{
		Print(EError, "Failed placing files - return code %d\n", fileDataSize);
		return KErrGeneral;
		}

	// and then put the directory into the image
	TInt err = PlaceDirectory( pRootDir, addr - aImage->Size() ); // offset pointer by size of core image
	if( err != KErrNone )
		{
		Print(EError, "Failed placing directory - return code %d\n", err);
		return err;
		}
		
	directoryOffset+=aImage->Size(); // offset offset by size of core image
	// Now write the header
	TExtensionRofsHeader * pHeader = (TExtensionRofsHeader*)iData;
	pHeader->iIdentifier[0] = 'R';
	pHeader->iIdentifier[1] = 'O';
	pHeader->iIdentifier[2] = 'F';
	pHeader->iIdentifier[3] = 'x';
	pHeader->iHeaderSize = KExtensionRofsHeaderSize;
	pHeader->iDirTreeOffset = directoryOffset;
	pHeader->iDirTreeSize = iTotalDirectoryBlockSize;
	pHeader->iDirFileEntriesOffset = directoryOffset + iTotalDirectoryBlockSize;
	pHeader->iDirFileEntriesSize = iTotalFileBlockSize;
	pHeader->iRofsFormatVersion = KRofsFormatVersion;
	pHeader->iTime = iObey->iTime;
	iSizeUsed = fileDataSize + fileDataStartOffset;
	pHeader->iImageSize = iSizeUsed;
	if (iObey->AutoSize())
		MakeAutomaticSize(iObey->AutoPageSize()); // change iSize to nearest page size
	pHeader->iMaxImageSize = iSize;
	pHeader->iCheckSum = 0;		// not used yet

	return KErrNone;
	}
	
void E32Rofs::MakeAutomaticSize(TUint32 aPageSize)
		{
		TUint32 size = iSizeUsed;
		if (iSizeUsed % aPageSize > 0)
			{
			//used size needs to be rounded up to nearest page size
			size = (iSizeUsed/aPageSize + 1)*aPageSize;
			}
		iSize = size;
		}

TInt E32Rofs::Create()
//
// This is the main entry point to the ROFS image creation
//
	{
	TUint8* addr=(TUint8*)iData;

	TRomNode* pRootDir = iObey->iRootDirectory;
	const TInt headerSize = KRofsHeaderSize;
	// Layout the directory structure. Does not actually write it
	// to the image. Returns the number of bytes used for the directory
	// structure within the image.
	const TInt directoryOffset = headerSize;
	const TInt directorySize = LayoutDirectory( pRootDir, directoryOffset );
	if( directorySize <= 0 )
		{
		Print(EError, "Failed laying out directories - return code %d\n", directorySize);
		return KErrGeneral;
		}

	// get offset to start of file data, rounded up to next word boundary
	TInt offs = headerSize + directorySize;
	const TInt fileDataStartOffset = offs + ( (4 - offs) & 3);

	// Now we traverse the list of entries placing each file
	// This updates the directory entries to point to the correct offset
	// to the start of the file
	const TInt fileDataSize = PlaceFiles( pRootDir, addr + fileDataStartOffset, fileDataStartOffset );
	if( fileDataSize < 0 )
		{
		Print(EError, "Failed placing files - return code %d\n", fileDataSize);
		return KErrGeneral;
		}

	// and then put the directory into the image
	TInt err = PlaceDirectory( pRootDir, addr );
	if( err != KErrNone )
		{
		Print(EError, "Failed placing directory - return code %d\n", err);
		return err;
		}
		
	// Now write the header
	TRofsHeader* pHeader = (TRofsHeader*)iData;
	pHeader->iIdentifier[0] = 'R';
	pHeader->iIdentifier[1] = 'O';
	pHeader->iIdentifier[2] = 'F';
	pHeader->iIdentifier[3] = 'S';
	pHeader->iHeaderSize = KExtensionRofsHeaderSize;
	pHeader->iDirTreeOffset = directoryOffset;
	pHeader->iDirTreeSize = iTotalDirectoryBlockSize;
	pHeader->iDirFileEntriesOffset = directoryOffset + iTotalDirectoryBlockSize;
	pHeader->iDirFileEntriesSize = iTotalFileBlockSize;
	pHeader->iRofsFormatVersion = KRofsFormatVersion;
	pHeader->iTime = iObey->iTime;
	iSizeUsed = fileDataSize + fileDataStartOffset;
	pHeader->iImageSize = iSizeUsed;
	pHeader->iImageVersion = iObey->iVersion;
	if (iObey->AutoSize())
		MakeAutomaticSize(iObey->AutoPageSize()); // change iSize to nearest page size
		
	pHeader->iMaxImageSize = iSize;
	pHeader->iCheckSum = 0;		// not used yet

	return KErrNone;
	}
void E32Rofs::DisplaySizes(TPrintType aWhere)
{
	Print(aWhere, "Summary of file sizes in rofs:\n");
	TRomBuilderEntry *file=iObey->FirstFile();
	while(file)
		{
		file->DisplaySize(aWhere);
		file=iObey->NextFile();
		}
	Print( aWhere, "Directory block size: %d\n"
					"File block size:      %d\n"
					"Total directory size: %d\n"
					"Total image size:     %d\n",
					iTotalDirectoryBlockSize,
					iTotalFileBlockSize,
					iTotalDirectoryBlockSize + iTotalFileBlockSize,
					iSizeUsed );

}

void E32Rofs::LogExecutableAttributes(E32ImageHeaderV *aHdr)
{
	Print(ELog, "Uids:                    %08x %08x %08x %08x\n", aHdr->iUid1, aHdr->iUid2, aHdr->iUid3, aHdr->iUidChecksum);
	Print(ELog, "Data size:               %08x\n", aHdr->iDataSize);
	Print(ELog, "Heap min:                %08x\n", aHdr->iHeapSizeMin);
	Print(ELog, "Heap max:                %08x\n", aHdr->iHeapSizeMax);
	Print(ELog, "Stack size:              %08x\n", aHdr->iStackSize);
	Print(ELog, "Secure ID:               %08x\n", aHdr->iS.iSecureId);
	Print(ELog, "Vendor ID:               %08x\n", aHdr->iS.iVendorId);
	Print(ELog, "Priority:                %d\n\n", aHdr->iProcessPriority);
}
class Worker : public boost::thread {
    public:
    static void thrd_func(E32Rofs* rofs){
        CBytePair bpe(gFastCompress);

        bool deferred = false;
        TPlacingSection* p = rofs->GetFileNode(deferred);
        while(p) {
            if(!deferred) 
                p->len = p->node->PlaceFile(p->buf, (TUint32)-1, 0, &bpe);
            p = rofs->GetFileNode(deferred);
        }
        rofs->ArriveDeferPoint();
        p = rofs->GetDeferredJob();
        while(p) {
            p->len = p->node->PlaceFile(p->buf, (TUint32)-1, 0, &bpe);
            p = rofs->GetDeferredJob();
        }
    }
    Worker(E32Rofs* rofs) : boost::thread(thrd_func,rofs) {
    }
};

TPlacingSection* E32Rofs::GetFileNode(bool &aDeferred) {
    //get a node from the node list, the node list is protected by mutex iMuxTree.
    //The iMuxTree also helps to make sure the order in iVPS is consistent with the node list.
    //And this is the guarantee of same outputs regardless of how many threads being used.
    boost::mutex::scoped_lock lock(iMuxTree);
    aDeferred = false;
    TRomNode* node = iLastNode;
    while(node) {
        if( node->IsFile()) {
            if(!node->iHidden) {
                iLastNode = node->NextNode();
                break;
            }
        }
        node = node->NextNode();
    }

    if(node && node->IsFile() && !node->iHidden) {
        TPlacingSection* pps = new TPlacingSection(node);
        iVPS.push_back(pps);
        if(node->iAlias) {
            iQueueAliasNode.push(pps);
            aDeferred =  true;
        }
        return pps;
    }
    return NULL;
}
TPlacingSection* E32Rofs::GetDeferredJob() {
    // waiting all the normal node have been placed.
    while(iWorkerArrived < gThreadNum)
        boost::this_thread::sleep(boost::posix_time::milliseconds(10));

    // now we can safely handle the alias nodes.
    boost::mutex::scoped_lock lock(iMuxTree);
    TPlacingSection* p = NULL;
    if(!iQueueAliasNode.empty()) {
        p = iQueueAliasNode.front();
        iQueueAliasNode.pop();
    }
    return p;
}
void E32Rofs::ArriveDeferPoint() {
    boost::mutex::scoped_lock lock(iMuxTree);
    ++iWorkerArrived;
}
TInt E32Rofs::PlaceFiles( TRomNode* /*aRootDir*/, TUint8* aDestBase, TUint aBaseOffset, TInt aCoreSize ) 
	//
	// Traverses all entries placing all files and updating directory entry pointers.
	// Returns number of bytes placed or -ve error code.
	//
	{
            iLastNode = TRomNode::FirstNode();
            iWorkerArrived = 0;

            boost::thread_group thrds;
            Worker** workers = new Worker*[gThreadNum];
            int i;
            for (i = 0; i < gThreadNum; ++i) {
                workers[i] = new Worker(this);
                thrds.add_thread(workers[i]);
            }

            thrds.join_all();
            delete [] workers;

            TUint offset = aBaseOffset;
            TUint8* dest = aDestBase;
            TBool aIgnoreHiddenAttrib = ETrue;
            TInt len = iVPS.size();
            TInt maxSize;
            for(i=0;i<len;i++) {
                maxSize = aCoreSize + iSize - offset;
                if(maxSize <= 0) {
                    // Image size is too low to proceed.
                    return maxSize;
                }
                if(iVPS[i]->node->iFileStartOffset != (TUint)KFileHidden) {
                    memcpy(dest,iVPS[i]->buf,iVPS[i]->len);
                    if(iVPS[i]->node->iEntry->iFileOffset == -1) {
                        iVPS[i]->node->iEntry->iFileOffset = offset;
                        iVPS[i]->node->iFileStartOffset = iVPS[i]->node->iEntry->iFileOffset;
                    }
                    else {
                        TRomBuilderEntry* aEntry = (TRomBuilderEntry*)(iVPS[i]->node->iFileStartOffset);
                        iVPS[i]->node->iFileStartOffset = aEntry->iFileOffset;
                    }
                }
                iVPS[i]->len += (4-iVPS[i]->len) & 3;// round up to next word boundary
                dest += iVPS[i]->len;
                offset += iVPS[i]->len;

                if(gLogLevel > DEFAULT_LOG_LEVEL )
                {
                    TRomNode* node = iVPS[i]->node;
                    TInt aLen = node->FullNameLength(aIgnoreHiddenAttrib);
                    char *aBuf = new char[aLen+1];
                    if(gLogLevel & LOG_LEVEL_FILE_DETAILS)
                    {
                        node->GetFullName(aBuf, aIgnoreHiddenAttrib);
                        if(node->iEntry->iFileName)
                            Print(ELog,"%s\t%d\t%s\t%s\n", node->iEntry->iFileName, node->iEntry->RealFileSize(), (node->iHidden || node->iEntry->iHidden)? "hidden":"", aBuf);
                        else
                            Print(ELog,"%s\t%s\n", (node->iHidden || node->iEntry->iHidden) ? "hidden":"", aBuf);
                    }

                    if(gLogLevel & LOG_LEVEL_FILE_ATTRIBUTES)
                    {
                        if(!node->iHidden && !node->iEntry->iHidden)
                            Print(ELog, "Device file name:        %s\n", aBuf);
                        if(node->iEntry->iExecutable)
                            LogExecutableAttributes((E32ImageHeaderV*)(dest-len));
                    }
                    delete[] aBuf;
                }
            }
	return offset - aBaseOffset;	// number of bytes used
	}






TInt E32Rofs::LayoutDirectory( TRomNode* /*aRootDir*/, TUint aBaseOffset ) 
	//
	// Creates the directory layout but does not write it to the image.
	// All directories are given a location in the image.
	// Returns the total number of bytes used for the directory (rounded
	// up to the nearest word) or a -ve error code.
	//
	{
	TRomNode* node = TRomNode::FirstNode();

	TUint offset = aBaseOffset;
	while( node )
		{
		if( node->IsDirectory())
			{
			// it is a directory block so we have to give it a location
			node->SetImagePosition( offset );
		
			// work out how much space it requires for the directory block
			TInt dirLen;
			TInt fileLen;
			TInt err = node->CalculateDirectoryEntrySize( dirLen, fileLen );
			if( err != KErrNone )
				{
				return err;
				}
			Print( ELog, "Directory '%s' @offs=0x%x, size=%d\n", node->iName, offset, dirLen );
			dirLen += (4-dirLen) & 3;	// round up to next word boundary
			offset += dirLen;
			}

		node = node->NextNode();
		}

	TInt totalDirectoryBlockSize = offset - aBaseOffset;	// number of bytes used
	totalDirectoryBlockSize += (4 - totalDirectoryBlockSize) & 3;		// round up

	// Now go round again placing the file blocks
	offset = aBaseOffset + totalDirectoryBlockSize;
	const TUint fileBlockStartOffset = offset;
	node = TRomNode::FirstNode();
	while( node )
		{
		if( node->IsDirectory() )
			{
			// work out how much space it requires for the file block
			TInt dummy;
			TInt fileLen;
			TInt err = node->CalculateDirectoryEntrySize( dummy, fileLen );

			if( err != KErrNone )
				{
				return fileLen;
				}
			if( fileLen )
				{
				node->SetFileBlockPosition( offset );
				Print( ELog, "File block for dir '%s' @offs=0x%x, size=%d\n", node->iName, offset, fileLen );
				}
		
			fileLen += (4-fileLen) & 3;	// round up to next word boundary
			offset += fileLen;
			}

		node = node->NextNode();
		}

	TInt totalFileBlockSize = offset - fileBlockStartOffset;	// number of bytes used
	totalFileBlockSize += (4 - totalFileBlockSize) & 3;		// round up

	iTotalDirectoryBlockSize = totalDirectoryBlockSize;
	iTotalFileBlockSize = totalFileBlockSize;

	return totalDirectoryBlockSize + totalFileBlockSize;
	}


TInt E32Rofs::PlaceDirectory( TRomNode* /*aRootDir*/, TUint8* aDestBase ) 
	//
	// Writes the directory into the image. 
	// Returns KErrNone on success, or error code
	//
	{
	TRomNode* node = TRomNode::FirstNode();

	while( node )
		{
		if( node->IsDirectory() )
			{
			TInt err = node->Place( aDestBase );
			if( err != KErrNone )
				{
				return err;
				}
			}
		node = node->NextNode();
		}
	return KErrNone;
	}

TInt E32Rofs::WriteImage( TInt aHeaderType )
	{
	ofstream romFile((const char *)iObey->iRomFileName,ios::binary);
	if (!romFile)
		return Print(EError,"Cannot open ROM file %s for output\n",iObey->iRomFileName);
	Write(romFile, aHeaderType);
	romFile.close();

	return KErrNone;
	}

TRomNode* E32Rofs::CopyDirectory(TRomNode*& aLastExecutable)
	{
	return iObey->iRootDirectory->CopyDirectory(aLastExecutable);
	}


void E32Rofs::Write(ofstream &os, TInt aHeaderType)

// Output a rom image

	{

	switch (aHeaderType)
		{
	default:
	case 0:
		Print(EAlways, "\nWriting Rom image without");
		break;
	case 2:
		Print(EAlways, "\nWriting Rom image with PE-COFF");
			{
			unsigned char coffhead[0x58] = {0};  // zero all the elements

			// fill in the constant bits
			// this is supposed to be simple, remember
			coffhead[1] = 0x0a;
			coffhead[2] = 0x01;
			coffhead[0x10] = 0x1c;
			coffhead[0x12] = 0x0f;
			coffhead[0x13] = 0xa1;
			coffhead[0x14] = 0x0b;
			coffhead[0x15] = 0x01;
			coffhead[0x26] = 0x40;
			coffhead[0x2a] = 0x40;
			coffhead[0x30] = 0x2e;
			coffhead[0x31] = 0x74;
			coffhead[0x32] = 0x65;
			coffhead[0x33] = 0x78;
			coffhead[0x34] = 0x74;
			coffhead[0x3a] = 0x40;
			coffhead[0x3e] = 0x40;
			coffhead[0x44] = 0x58;
			coffhead[0x54] = 0x20;

			// now fill in the text segment size
			*(TUint32 *) (&coffhead[0x18]) = ALIGN4K(iSizeUsed);
			*(TUint32 *) (&coffhead[0x40]) = ALIGN4K(iSizeUsed);

			os.write(reinterpret_cast<char *>(coffhead), sizeof(coffhead));
			}
		break;
		}
	Print(EAlways, " header to file %s\n", iObey->iRomFileName);
	os.write( iData, iSizeUsed );
	}


TRomNode* E32Rofs::RootDirectory()
	{
	return iObey->iRootDirectory;
	}
void E32Rofs::SetRootDirectory(TRomNode* aDir)
	{
	iObey->iRootDirectory = aDir;
	}
TText* E32Rofs::RomFileName()
	{
	return iObey->iRomFileName;
	}
TInt E32Rofs::Size()
	{
	return iSize;
	}

///////////////////

