/*
* Copyright (c) 2007-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: 
*
*/


#pragma warning (disable: 4786)

// System includes
#include <iostream>
#include <fstream>

// User includes
#include "rommanager.h"
#include "parameterlist.h"
#include "stringutils.h"
#include "is_utils.h"

// String constants
const std::wstring KRomManagerSysBinPath 			= L"z:\\sys\\bin\\";
const std::string KRomManagerRomDrive               = "Z:\\";
const std::string KRomManagerRomLogProcessingFile   = "Processing file ";
const std::string KRomManagerRomLogReadingResource  = "Reading resource ";
const std::string KRomManagerRomLogReadingResource2 = " to rom linear address";
const std::string KRomManagerRomLogUids             = "Uids:                    ";
const std::string KRomManagerRomLogSecureId         = "Secure ID:               ";
const std::string KRomManagerRomLogVendorId         = "Vendor ID:               ";
const std::string KRomManagerRomLogDeviceFileName   = "Device file name:        ";


RomEntry::RomEntry( const std::string& aSourceLogFile )
:   iSourceLogFile( aSourceLogFile ), iSecInfo( 0 ), iUid1( 0 ), iUid2( 0 ), iUid3( 0 )
    {
    iSecInfo = new SBinarySecurityInfo();
    iEnvFileName = "";
    iRomFileName = "";
    }


RomEntry::~RomEntry()
    {
    delete iSecInfo;
    }


RomManager::RomManager()
    {
    }

RomManager* RomManager::New( const CParameterList& aParamList )
    {
    RomManager* ret = NULL;
    
    //
    // Are we dealing with a full EPOC32-based environment, i.e. "-z" on the Command Line
    // or are we working with ROM/ROFSBUILD log files ("-r").
    //	
    if ( aParamList.IsFlagSet(CParameterList::EFlagsDisableZDriveChecksSet))
		{
	    ret = new RomManagerEmpty();
		}
	else if ( aParamList.RomLogFileNames().size() )
        {
        ret = new RomManagerLogFiles( aParamList.RomLogFileNames() );
        }
    else
        {
        ret = new RomManagerFileSystem( aParamList.RomDrivePath() );
        }
    //
    return ret;
    }


RomManagerFileSystem::RomManagerFileSystem( const std::wstring& aPath )
:   iRomPath( aPath )
    {
    }

bool RomManagerFileSystem::RomFileExists( const std::wstring& aFileName )
    {
    std::wstring target = std::wstring( aFileName );
    ConvertToLocalPath( target, iRomPath );
    bool exists = FileExists( target );
    return exists;
    }

void RomManagerFileSystem::FindAllAdornedVariants(const std::wstring& aSearchNameWild, std::list<std::wstring>& aAdornedFileNamesFound)
	{
	// do nothing
	}

bool RomManagerFileSystem::SidExistsInRom(std::wstring& aFile, TUint32 aSid)
	{
	std::wstring romDir(KRomManagerSysBinPath);
    ConvertToLocalPath( romDir, iRomPath );

	// search through the binaries on the ROM drive
	// to find a matching SID
	std::list<std::wstring> dirContents;
	GetDirContents(romDir, dirContents);

	std::list<std::wstring>::iterator curr = dirContents.begin();
	std::list<std::wstring>::iterator end = dirContents.end();
	for ( ; curr != end; ++curr )
		{
			std::wstring romFile(KRomManagerSysBinPath);
			romFile.append(*curr);

			SBinarySecurityInfo info;
			ReadSecurityInfo(info,romFile);

			TUint32 sid = info.iSecureId;

			if (sid != 0 && sid == aSid)
			{
				aFile = KRomManagerSysBinPath + romFile;
				return true;
			}
		}
	return false;
	}

TInt RomManagerFileSystem::ReadSecurityInfo( SBinarySecurityInfo& aInfo, const std::wstring aFileName )
    {
	// Need to convert the file name to the local path so it can find it
	std::wstring target = std::wstring( aFileName );
	ConvertToLocalPath( target, iRomPath );
    return ::ReadSecurityInfo(aInfo,target);
    }

RomManagerLogFiles::RomManagerLogFiles( const std::list<std::wstring>& aLogFileNames )
:   iLogFileNames( aLogFileNames )
    {
    for( std::list<std::wstring>::const_iterator it = aLogFileNames.begin() ; it != aLogFileNames.end(); it++ )
        {
    	std::string narrowLogFileName = Ucs2ToUtf8( *it );
    	std::string narrowUpperCaseLogFileName = StringUtils::ToUpper( narrowLogFileName );

        // Get the base name
        int dotPos = narrowUpperCaseLogFileName.find( ".LOG" );
        if ( dotPos == std::string::npos )
            {
            }
        else
            {
            const std::string baseName = narrowLogFileName.substr( 0, dotPos );

            // Two step process. First, read the log, then, if the log file didn't
            // contain sufficient meta data (old version of ROFS/ROMBUILD) we resort
            // to reading a corresponding OBY too (if it exists).
            const bool needToReadOby = ReadLogFile( narrowLogFileName );

            if ( needToReadOby )
                {           
                ReadObyFile( baseName + ".OBY" );
                }
            }
        }
    }


RomManagerLogFiles::~RomManagerLogFiles()
    {
    for( RomEntryMap::iterator it=iEntriesIndexedByEnvFileName.begin(); it != iEntriesIndexedByEnvFileName.end(); it++ )
        {
        RomEntry* entry = (*it).second;
		delete entry;
        }
    }


bool RomManagerLogFiles::RomFileExists( const std::wstring& aFileName )
    {
    bool exists = false;

    // Get filename & convert it to uppercase, since our map key is
    // also in upper case form.
    std::string narrowFileName = Ucs2ToUtf8( aFileName );
    narrowFileName = StringUtils::ToUpper( narrowFileName );

    // Do we have a corresponding entry?
    RomEntry* entry = iEntriesIndexedByRomFileName[ narrowFileName ];
    if ( entry != NULL )
        {
        exists = true;
        }
    //
    return exists;
    }

void RomManagerLogFiles::FindAllAdornedVariants(const std::wstring& aSearchNameWild, std::list<std::wstring>& aAdornedFileNamesFound)
	{
	RomEntryMap::const_iterator curr = iEntriesIndexedByRomFileName.begin();
	RomEntryMap::const_iterator end = iEntriesIndexedByRomFileName.end();

	std::wstring searchNameWild = StringUtils::ToUpper(aSearchNameWild);

	for ( ; curr != end ; ++curr)
		{
		std::wstring romFile = Utf8ToUcs2(curr->first);
		if (StringUtils::WildcardCompare(searchNameWild,romFile))
			{
			aAdornedFileNamesFound.push_back(romFile);
			}
		}
	}

bool RomManagerLogFiles::SidExistsInRom(std::wstring& aFile, TUint32 aSid)
	{
	RomEntryMap::const_iterator curr = iEntriesIndexedByRomFileName.begin();
	RomEntryMap::const_iterator end = iEntriesIndexedByRomFileName.end();

	for ( ; curr != end ; ++curr)
		{
		RomEntry* romEntry = curr->second;
		if (romEntry)
			{
			if (romEntry->Type() == RomEntry::ETypeBinary)
				{
				TUint32 romSid = romEntry->SecurityInfo().iSecureId;
				if (romSid != 0 && romSid == aSid)
					{
					aFile = Utf8ToUcs2(romEntry->RomFileName());
					return true;
					}
				}
			}
		}
	return false;
	}

TInt RomManagerLogFiles::ReadSecurityInfo( SBinarySecurityInfo& aInfo, const std::wstring aFileName )
    {
    TInt err = -1;

    // Get filename & convert it to uppercase, since our map key is
    // also in upper case form.
    std::string narrowFileName = Ucs2ToUtf8( aFileName );
    narrowFileName = StringUtils::ToUpper( narrowFileName );

    // Do we have a corresponding entry?
    RomEntry* entry = iEntriesIndexedByRomFileName[ narrowFileName ];
    if ( entry != NULL )
        {
		if ( entry->Type() == RomEntry::ETypeBinary)
			{
			aInfo.iSecureId = entry->SecurityInfo().iSecureId;
			aInfo.iVendorId = entry->SecurityInfo().iVendorId;
			err = 0;
			}		
        }
    //
    return err;
    }


void RomManagerLogFiles::ReadObyFile( const std::string& aFileName )
    {
	std::wstring fileName = Utf8ToUcs2( aFileName );
    if ( FileExists( fileName ) )
        {
    	std::ifstream stream;
	    stream.open( aFileName.c_str(), std::ios::binary );
        //
        if ( !stream.fail() )
            {
            try
                {
                std::string line;
                //
                while( std::getline( stream, line ) )
                    {
                    line = StringUtils::TrimWhiteSpace( line );
                    if ( line.length() )
                        {
                        ProcessObyFileLine( aFileName, line );
                        }
                    }
                //
                stream.close();
                }
            catch( const RomManagerException& e )
                {
                stream.close();
                throw e;
                }
            }
        else
            {
            throw RomManagerException( RomManagerException::ETypeUnableToOpenCorrespondingOBY, aFileName );
            }
        }
    else
        {
        throw RomManagerException( RomManagerException::ETypeUnableToFindCorrespondingOBY, aFileName );
        }
    }


void RomManagerLogFiles::ProcessObyFileLine( const std::string& aFileName, std::string& aLine )
    {
    int breakPos = std::string::npos;
    RomEntry::TType type = RomEntry::ETypeBinary;
    //
    if ( StringUtils::CheckForMatch( "file=", aLine ) )
        {
        breakPos = aLine.find_first_of( "\"" );
        type = RomEntry::ETypeBinary;
        }
    else if ( StringUtils::CheckForMatch( "data=", aLine ) )
        {
        breakPos = aLine.find_first_of( "\"" );
        type = RomEntry::ETypeData;
        }
    else if ( StringUtils::CheckForMatch( "extension[", aLine ) )
        {
        // extension[0x0B080004]=\epoc32\release\ARMV5\urel\esound.ldd 	"Sys\Bin\esound.ldd"
        const std::string KRemainingExtensionDataText = "0xXXXXXXXX]=";

        if ( aLine.length() > KRemainingExtensionDataText.length() )
            {
            aLine = aLine.substr( KRemainingExtensionDataText.length() );
            breakPos = aLine.find_first_of( "\"" );
            }
        }
    else if ( StringUtils::CheckForMatch( "primary[", aLine ) )
        {
        // primary[0x0A080004]=\epoc32\release\ARMV5\urel\_ekern.exe 	"Sys\Bin\ekern.exe"
        const std::string KRemainingPrimaryDataText = "0xXXXXXXXX]=";

        if ( aLine.length() > KRemainingPrimaryDataText.length() )
            {
            aLine = aLine.substr( KRemainingPrimaryDataText.length() );
            breakPos = aLine.find_first_of( "\"" );
            }
        }
    else if ( StringUtils::CheckForMatch( "device[", aLine ) )
        {
        // device[0x0C080004]=\epoc32\release\ARMV5\urel\esound.ldd 	"Sys\Bin\esound.ldd"
        const std::string KRemainingDeviceDataText = "0xXXXXXXXX]=";

        if ( aLine.length() > KRemainingDeviceDataText.length() )
            {
            aLine = aLine.substr( KRemainingDeviceDataText.length() );
            breakPos = aLine.find_first_of( "\"" );
            }
        }

    if ( breakPos != std::string::npos )
        {
        // The env file name must be upper case, since our string indicies
        // require this.
    	std::string envFileName = StringUtils::TrimWhiteSpace( aLine.substr( 0, breakPos ) );
        envFileName = StringUtils::ToUpper( envFileName );

        std::string romFileName = StringUtils::TrimWhiteSpace( aLine.substr( breakPos ) );
        romFileName = KRomManagerRomDrive + romFileName.substr( 1, romFileName.size() - 2 );

        // See if we can locate the ROM entry based upon the environment
        // file name. This should agree with the pre-parsed ROMBUILD/ROFSBUILD
        // log file that we read prior to this method.
        RomEntry* file = iEntriesIndexedByEnvFileName[ envFileName ];

        // Update the entry with the (presumed missing) 'in-place' Symbian OS
        // file name.
        if ( file != NULL )
            {
            romFileName = StringUtils::ToUpper( romFileName );
            file->SetRomFileName( romFileName );

            // Update type info
            file->SetType( type );

            // ... and make an index entry in the index-by-rom-filename-map
            iEntriesIndexedByRomFileName[ romFileName ] = file;
            }
        }
    }


bool RomManagerLogFiles::ReadLogFile( const std::string& aFileName )
    {
    bool obyParsingRequired = true;
    std::ifstream stream;
	stream.open( aFileName.c_str(), std::ios::binary );

    if ( !stream.fail() )
        {
        try
            {
            std::string line;
            RomEntry* currentEntry = NULL;
            //
            while( std::getline( stream, line ) )
                {
                line = StringUtils::TrimWhiteSpace( line );
                if ( line.length() )
                    {
                    ProcessLogFileLine( line, currentEntry, aFileName, obyParsingRequired );
                    }
                }
            //
            stream.close();
            }
        catch( const RomManagerException& e )
            {
            stream.close();
            throw e;
            }
        }
    else
        {
        throw RomManagerException( RomManagerException::ETypeUnableToOpenLogFile, aFileName );
        }
    //
    return obyParsingRequired;
    }


void RomManagerLogFiles::ProcessLogFileLine( std::string& aLine, RomEntry*& aCurrentEntry, const std::string& aFileName, bool& aObyParsingRequired )
    {
    const bool isBinary = StringUtils::CheckForMatch( KRomManagerRomLogProcessingFile, aLine );
    const bool isData = StringUtils::CheckForMatch( KRomManagerRomLogReadingResource, aLine );
    //
    if ( isBinary || isData )
        {
        // Save any existing entry
        if ( aCurrentEntry != NULL )
            {
            // Env file name
            iEntriesIndexedByEnvFileName[ aCurrentEntry->EnvFileName() ] = aCurrentEntry;

            // Rom file name (if set)
            if ( aCurrentEntry->RomFileName().length() )
                {
                iEntriesIndexedByRomFileName[ aCurrentEntry->RomFileName() ] = aCurrentEntry;
                }
            }

        // If this is a data entry, then we must remove everything after the "to rom linear address"
        // text, since this is not part of the filename.
        if ( isData )
            {
            const std::string::size_type linAddrTextPos = aLine.find( KRomManagerRomLogReadingResource2 );
            if ( linAddrTextPos != std::string::npos )
                {
                aLine = aLine.substr( 0, linAddrTextPos );
                }
            }

        // Indicies are upper case file names.
        aLine = StringUtils::ToUpper( aLine );

        // Create a new entry
        aCurrentEntry = new RomEntry( aFileName );
        aCurrentEntry->SetEnvFileName( aLine );

        // Set type depending on whether its data or binary
        if ( isData )
            {
            aCurrentEntry->SetType( RomEntry::ETypeData );
            }
        else if ( isBinary )
            {
            aCurrentEntry->SetType( RomEntry::ETypeBinary );
            }
        }
    else if ( aCurrentEntry && StringUtils::CheckForMatch( KRomManagerRomLogUids, aLine ) )
        {
    	std::istringstream stringStream( aLine );
        //
        TUint32 uid1 = 0;
        stringStream >> std::hex >> uid1;
        aCurrentEntry->SetUid1( uid1 );
        //
        TUint32 uid2 = 0;
        stringStream >> std::hex >> uid2;
        aCurrentEntry->SetUid2( uid2 );
        //
        TUint32 uid3 = 0;
        stringStream >> std::hex >> uid3;
        aCurrentEntry->SetUid3( uid3 );
        }
    else if ( aCurrentEntry && StringUtils::CheckForMatch( KRomManagerRomLogSecureId, aLine ) )
        {
    	std::istringstream stringStream( aLine );
        //
        TUint32 sid = 0;
        stringStream >> std::hex >> sid;
        aCurrentEntry->SecurityInfo().iSecureId = sid;
        }
    else if ( aCurrentEntry && StringUtils::CheckForMatch( KRomManagerRomLogVendorId, aLine ) )
        {
    	std::istringstream stringStream( aLine );
        //
        TUint32 vid = 0;
        stringStream >> std::hex >> vid;
        aCurrentEntry->SecurityInfo().iVendorId = vid;
        }
    else if ( aCurrentEntry && StringUtils::CheckForMatch( KRomManagerRomLogDeviceFileName, aLine ) )
        {
        // Indicies are upper case file names.
        aLine = StringUtils::ToUpper( KRomManagerRomDrive + aLine );
        aCurrentEntry->SetRomFileName( aLine );

        // If we see this marker, then ROMBUILD & ROFSBUILD are supplied with the extended
        // meta data that includes "in-device" file name, hence we don't need to parse
        // the OBY file in order to obtain this information
        aObyParsingRequired = false;
        }
    }


bool RomManagerEmpty::RomFileExists( const std::wstring& aFileName )
	{
	//default return value as there is no Z drive present
	return false;
	}

TInt RomManagerEmpty::ReadSecurityInfo( SBinarySecurityInfo& aInfo, const std::wstring aFileName )
	{
	// return non zero error value  
	return -1;
	}

void RomManagerEmpty::FindAllAdornedVariants(const std::wstring& aSearchNameWild, std::list<std::wstring>& aAdornedFileNamesFound)
	{
	// default return value as there is no Z drive present
	}

bool RomManagerEmpty::SidExistsInRom(std::wstring& aFile, TUint32 aSid)
	{
	// default return value as there is no Z drive present
	return false;
	}

void RomManagerException::Display() const
    {
	std::ostringstream stream;
    stream << "ROM/ROFS OBY/LOG file problem - ";
    //
	switch( iType )
	{
	case ETypeUnableToOpenLogFile:
		stream << "\'" << iFileName << "\'" << " (ROM/ROFSBUILD LOG file) could not be read";
		break;
	case ETypeUnableToOpenCorrespondingOBY:
		stream << "\'" << iFileName << "\'" << " (ROM/ROFSBUILD OBY file) could not be read";
		break;
	case ETypeUnableToFindCorrespondingOBY:
		stream << "\'" << iFileName << "\'" << " (ROM/ROFSBUILD OBY file) could not be found";
		break;
	
	default:
		stream << "Unknown error";
		break;
	}
    //
    stream << std::endl;
    std::wstring finalMessage = Utf8ToUcs2( stream.str() );
    //
	LERROR( finalMessage );
    }
