// Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "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 <barsread.h>    // TResourceReader
#include <bautils.h>     // BaflUtils
#include "textresolver.h"
#include <errorres.rsg>
#include <uikon/eikdefconst.h>


const TInt KUnknownError( 0 );
const TInt KBlankError( -2 );

const TInt KRIMask(0x00000fff);
_LIT(KErrorResResourceFileName, "z:\\resource\\errors\\ERRORRES.RSC"); 
_LIT(KRDSupport, "c:\\resource\\ErrRD");
_LIT(KTextResolverPanic,   "TextResolver");


GLDEF_C void Panic(TInt aPanic)
	{
    User::Panic(KTextResolverPanic, aPanic);
	}


CTextResolver::CTextResolver()
    {
    }

CTextResolver::CTextResolver(CCoeEnv& aEnv)
    :iCoe(&aEnv)
    {
    }
/**
 * Two-phase constructor method that is used to create a new instance
 * of the CTextResolver class. The implementation uses the passed 
 * CCoeEnv instance to access the resource files.
 *
 * @param aEnv the CCoeEnv instance to be used to access the resource
 * files.
 * @return a pointer to a new instance of the CTextResolver class.
 */
EXPORT_C CTextResolver* CTextResolver::NewL(CCoeEnv& aEnv)
    {
    CTextResolver* self = CTextResolver::NewLC(aEnv);
    CleanupStack::Pop(self);
    return self;
    }

 /**
 * Constructor creates a new instance of CTextResolver. The 
 * implementation uses the passed CCoeEnv instance to access the 
 * resource files. Leaves the object on the cleanup stack.
 *
 * @param aEnv the CCoeEnv instance to be used to access the resource
 * files.
 * @return a pointer to a new instance of the CTextResolver class.
 */
EXPORT_C CTextResolver* CTextResolver::NewLC(CCoeEnv& aEnv)
    {
    CTextResolver* self = new (ELeave) CTextResolver(aEnv);
    CleanupStack::PushL(self);
    self->ConstructL();
    return self;
    }

 /**
 * Constructor creates a new instance of CTextResolver. Resource files 
 * are accessed through the RResourceFile API.
 *
 * @return a pointer to a new instance of the CTextResolver class.
 */
EXPORT_C CTextResolver* CTextResolver::NewL()
    {
    CTextResolver* self = CTextResolver::NewLC();
    CleanupStack::Pop(self);
    return self;
    }

 /**
 * Constructor creates a new instance of CTextResolver.Resource files 
 * are accessed through the RResourceFile API. Leaves the object on 
 * the cleanup stack.
 *
 * @return a pointer to a new instance of the CTextResolver class.
 */
EXPORT_C CTextResolver* CTextResolver::NewLC()
    {
    CTextResolver* self = new (ELeave) CTextResolver();
    CleanupStack::PushL(self);
    self->ConstructL();
    return self;
    }

//
// ---------------------------------------------------------
// CTextResolver::ConstructL
//
// Symbian OS default constructor, initializes variables and cache 
// ---------------------------------------------------------
//
void CTextResolver::ConstructL()
    {
    // if no coe is present, connect new filesession.
    if (!iCoe)
    	{
        User::LeaveIfError(iFs.Connect());    

		TFileName resFile(KErrorResResourceFileName);
		BaflUtils::NearestLanguageFile(iFs, resFile);
		iResFile.OpenL(iFs, resFile);
    	}

    iRDSupport = BaflUtils::FileExists(iCoe ? iCoe->FsSession() : iFs, KRDSupport);     
    }

/** 
* Destructor 
*/
EXPORT_C CTextResolver::~CTextResolver()
    {
    Reset();
    iResFile.Close();

    // if no coe is present, close filesession.
    if (!iCoe)
        iFs.Close();   
    
    delete iTitleText;
    delete iTextBuffer;
    delete iContextSeparator;
    }

/**
* Resolves the given error code and returns the error text for the
* resolved error. Resolved text can be of any length. This version 
* is for "normal" use.
*
* @param aError The error code to be resolved.
* @param aContext Optional context for error numbers. If the aContext 
* parameter is not passed to the function, it uses the default value
* ECtxAutomatic. 
* @return the error text for the resolved error. "System error" (in 
* English localisation) is returned when error code is not known. For 
* unknown errors blank error flag (flags are defined in 
* textresolver.hrh) is also set to hide errors without proper 
* explanation. There is no limit on how long the resolved string
* can be.
*/
EXPORT_C const TDesC& CTextResolver::ResolveErrorString(TInt aError, TErrorContext aContext)
    {
    TUint flags(0);
    TInt id(0);
	return ResolveErrorString(aError,id,flags,aContext);
	}

/**
* Resolves the given error code and returns the error text for the
* resolved error. Resolved text can be of any length. This version
* is for advanced use
*
* @param aError The error code to be resolved.
* @param aTextId ID of the returned text.
* @param aFlags The priority of the returned error. The priority is 
* defined by the this module! Flags are defined in textresolver.hrh.
* @param aContext Optional context for error numbers. If the aContext 
* parameter is not passed to the function, it uses the default value
* ECtxAutomatic. 
* @return the error text for the resolved error. "System error" (in 
* English localisation) is returned when error code is not known. For 
* unknown errors blank error flag (flags are defined in 
* textresolver.hrh) is also set to hide errors without proper 
* explanation. There is no limit on how long the resolved string 
* can be.
*/
EXPORT_C const TDesC& CTextResolver::ResolveErrorString(TInt aError, TInt& aTextId, TUint& aFlags, TErrorContext aContext)
    {
    aFlags = 0;
    if (iCoe)	// Use the resource loaded using the CCoeEnv to read the resource file
    	{
	    TRAPD(err, DoResolveErrorStringL(aError, aTextId, aFlags));
		if (err)
			{
			if ((!AddContextAndSeparator(aContext)))
				{
				// Even the resource file opening failed, return OOM flag
				aFlags |= EErrorResOOMFlag;
				return KNullDesC;
				}
			return *iTextBuffer;
			}            

		if(aFlags&ETextResolverUnknownErrorFlag)	// Error code not supported. No error text found.
	        {
	        // Error is not recognised 
	        aTextId = R_ERROR_RES_GENERAL;	// Read and return an "unknown error" text from the
	        								// Nokia-specific version of the errorres.rss file
	      	TPtr appTextPtr = iTitleText->Des();
	      	TPtr textPtr = iTextBuffer->Des();
	      	iCoe->ReadResource(appTextPtr, R_BASE);
	      	iCoe->ReadResource(textPtr, R_ERROR_RES_GENERAL);
	        }
		}
	else	// No CCoeEnv available
		{
		aTextId = 0;
        TRAPD(err, DoRawReadOfSystemErrorResourcesToArraysL(aError, aTextId));
        if(err)
            {
            // Cleaning needed since object not destroyed
            Reset();
            
            if (aTextId != R_ERROR_RES_NO_MEMORY || !AddContextAndSeparator(aContext))
                {
                // Even the resource file opening failed, return OOM flag
                aFlags |= EErrorResOOMFlag;
                return KNullDesC;
                }

            return *iTextBuffer;
            }            
        
        aTextId = ResourceForError(aError);
        switch (aTextId)
            {
            case KBlankError:
                {
                aFlags |= ETextResolverBlankErrorFlag;
                break;
                }
            case KUnknownError:
                {
                // Error is not recognised 
                aTextId = R_ERROR_RES_GENERAL;

				delete iTitleText;
                iTitleText = AllocReadUnicodeString(iResFile, R_BASE);
                aFlags |= ETextResolverUnknownErrorFlag;
                // No break here; Fall through to default case to resolve text for the general error
                }
            default:    
                {
                // --- Error recognised by the resolver, so get the text ---
				delete iTextBuffer;
                iTextBuffer = AllocReadUnicodeString(iResFile, aTextId);
                break;
                }
            };       
        }       

	if(aFlags&ETextResolverBlankErrorFlag)	// Error code supported, but no error text found.
		{
		delete iTextBuffer;
		iTextBuffer = NULL;				// Return no error text at all	
		}

    if (!AddContextAndSeparator(aContext))
		{
		aFlags |= EErrorResOOMFlag;
		return KNullDesC;
		}

    if (iRDSupport && iTextBuffer) 
        {
        HBufC* tempBuffer = iTextBuffer->ReAlloc(iTextBuffer->Length()+14);

        // if allocation succeeds, append error id. Otherwise iTextBuffer stays as it is
        if (tempBuffer)
            {
            iTextBuffer = tempBuffer;
            iTextBuffer->Des().Append(' ');
            iTextBuffer->Des().Append('(');
            iTextBuffer->Des().AppendNum(aError);
            iTextBuffer->Des().Append(')');
            }
        }
	else if (aFlags & ETextResolverUnknownErrorFlag)
		{ // We hide the errors we cannot give proper explanation, 
		  //i.e. "System Error" will not be shown.
		aFlags |= ETextResolverBlankErrorFlag;	
        }

    if (iTextBuffer)
        return *iTextBuffer;
    else
        return KNullDesC;
    }

TInt CTextResolver::ResourceForError(TInt aError)
    {
    ASSERT(!iCoe);
    const TInt errorCount = iStartError->Count();
            
    delete iTitleText;
    iTitleText = NULL;
    
    for (TInt cc = 0; cc < errorCount; ++cc)
        {
        const TInt starterror = (*iStartError)[cc];
        const TInt endError = starterror-(*iErrorTexts)[cc]->Count()+1;
        
        if (aError >= endError && aError <= starterror)
        	{
	        ASSERT(!iTitleText);
	        iTitleText = AllocReadUnicodeString(iResFile, (*iAppTexts)[cc]);

	        const TInt errorIndex = -(aError-starterror);
	        const TInt flags = (*iFlags)[cc]->At(errorIndex);
	        
	        if (flags & ETextResolverPanicErrorFlag)
	            {
	            // --- Some errors are never meant to get to the UI level ---
	            // --- Those of them that do, result in a panic ---
	            Panic(aError);
	            }
	        
	        else if (flags & ETextResolverBlankErrorFlag)
	            {
	            // --- Error code can be ignored from the users' perspective ---
	            // --- Return blank error text ---
	            return KBlankError;
	            }

	        else if (flags & ETextResolverUnknownErrorFlag)
	            {
	            // --- Error not recognised by TextResolver ---
	            // --- Will be passed on to UIKON for interpretation ---
	            return KUnknownError;
	            }
	            
	        return (*iErrorTexts)[cc]->At(errorIndex); 
        	}
        }
        
   return KUnknownError;     
   }
    

/**
Read the system error texts from errorres.rss only, without using the Uikon CEikErrorResolver.
This will not pick up any appliation specific errors.
*/
void CTextResolver::DoRawReadOfSystemErrorResourcesToArraysL(TInt& aError, TInt& aTextId)
    {
    ASSERT(!iCoe);
    TResourceReader reader;
    ReadLocalizedSeparatorCharacterFromResourceAndPrepareResourceReaderLC(reader);

    // Lets handle KErrNoMemory as a special case, this this way we can maybe avoid extra "Resolving Errors"
    if (aError == KErrNoMemory) 
        {
        ASSERT(!iTextBuffer && !iTitleText);
		aTextId = R_ERROR_RES_NO_MEMORY;
		iTextBuffer = AllocReadUnicodeString(iResFile, aTextId);
		iTitleText = AllocReadUnicodeString(iResFile, R_BASE);
        User::Leave(KErrNoMemory);
        }
   
	if(iStartError && iErrorTexts && iFlags && iAppTexts)
		{
		CleanupStack::PopAndDestroy(); // rBuffer
		return;	// Already all done
		}
        
	// The resource data is used differenciate between the old and new resource formats 
	// (i.e, resources using title text and one's without using title text)
	// This API only support the new format, so just check that it is on the new format
    const TInt dummy = reader.ReadInt16();
    const TInt version = reader.ReadInt16();
    __ASSERT_ALWAYS(dummy == 0 && version == 2, User::Leave(KErrNotSupported));

    // Read into the error arrays
    const TInt arrayCount = reader.ReadInt16();
    
    ASSERT(!iStartError && !iErrorTexts && !iFlags && !iAppTexts);
    iStartError = new(ELeave) CArrayFixFlat<TInt>(arrayCount);
    iErrorTexts = new(ELeave) CArrayPtrSeg<CErrorResourceIdArray>(arrayCount);
    iFlags = new(ELeave) CArrayPtrSeg<CErrorResourceFlagArray>(arrayCount);
    iAppTexts = new(ELeave) CArrayFixFlat<TInt>(arrayCount);
    
    for (TInt cc = 0; cc < arrayCount; ++cc)
        {
        // Read in error array
        iAppTexts->AppendL(reader.ReadInt32()); // Application indicators  
        iStartError->AppendL(reader.ReadInt32());

        const TInt listId = reader.ReadInt32();
        
        TResourceReader reader2;
        HBufC8* rBuffer = iResFile.AllocReadL(listId & KRIMask); // Remove offset from id
        reader2.SetBuffer(rBuffer);
        CleanupStack::PushL(rBuffer);

        const TInt count = reader2.ReadInt16();
        CArrayFixFlat<TInt>* array = new(ELeave) CErrorResourceIdArray(count);
        CleanupStack::PushL(array);
        
        CArrayFixFlat<TInt>* flagarray = new(ELeave) CErrorResourceFlagArray(count);
        CleanupStack::PushL(flagarray);
    
        for (TInt dd = 0; dd < count; ++dd)
            {                   
            const TInt flags = reader2.ReadInt8();
            flagarray->AppendL(flags);
            // Append resource id for this error text
            array->AppendL(reader2.ReadInt32());
            }
    
        iFlags->AppendL(flagarray);
        CleanupStack::Pop(flagarray); 

        iErrorTexts->AppendL(array);
        CleanupStack::Pop(array);

        CleanupStack::PopAndDestroy(rBuffer);
        }
    
    CleanupStack::PopAndDestroy(); // reader's rBuffer
    }

// ---------------------------------------------------------
// CTextResolver::Reset()
//
// Reset the member variables
// ---------------------------------------------------------
//
void CTextResolver::Reset()
    {
    if (iBaseResourceFileOffset)
        {
        iCoe->DeleteResourceFile(iBaseResourceFileOffset);
        iBaseResourceFileOffset = 0;
        }
        
    if (iErrorTexts)
        {
        iErrorTexts->ResetAndDestroy();
        delete iErrorTexts;
        iErrorTexts = NULL;
        }
    
    if (iFlags)
        {
        iFlags->ResetAndDestroy();
        delete iFlags;
        iFlags = NULL;
        }
                
    delete iStartError;
    iStartError = NULL;
    
    delete iAppTexts;   
    iAppTexts = NULL;
    }

/**
ErrorResolver resource files in the ?:/resource/errors/ folder must always begin with
an array of error texts. The Nokia-specific version of the errorres.rss file also 
contains the localized titel/text separator character at the end of the file.
This method reads the separator character from file using the CCoeEnv provided.
*/
void CTextResolver::ReadLocalizedSeparatorCharacterFromResourceL(CCoeEnv& aCoeEnv)
	{
	ASSERT(&aCoeEnv);
	
	if (!iBaseResourceFileOffset)
    	{
    	TFileName filename(KErrorResResourceFileName);
    	BaflUtils::NearestLanguageFile(aCoeEnv.FsSession(), filename);
    
    	iBaseResourceFileOffset = aCoeEnv.AddResourceFileL(filename);
		
		ASSERT(!iContextSeparator);
		iContextSeparator = aCoeEnv.AllocReadResourceL(R_LOCALIZED_CONTEXT_SEPARATOR);
		}
	}

/**
ErrorResolver resource files in the ?:/resource/errors/ folder must always begin with
an array of error texts. The Nokia-specific version of the errorres.rss file also 
contains the localized titel/text separator character at the end of the file.
This method reads the separator character from file WITHOUT using a CCoeEnv.
*/
void CTextResolver::ReadLocalizedSeparatorCharacterFromResourceAndPrepareResourceReaderLC(TResourceReader& aResReader)
    {
    ASSERT(!iCoe);
    ASSERT(!iBaseResourceFileOffset);

    HBufC8* rBuffer = iResFile.AllocReadL(R_ERROR_RES_BASE_LIST & KRIMask); // Remove offset from id
    aResReader.SetBuffer(rBuffer);        
    CleanupStack::PushL(rBuffer);
    
    if(!iContextSeparator)
    	iContextSeparator = AllocReadUnicodeString(iResFile, R_LOCALIZED_CONTEXT_SEPARATOR);
    
    // Leave rBuffer on CleanupStack
    }

// returns NULL if out of memory
HBufC* CTextResolver::AllocReadUnicodeString(RResourceFile& aResFile, TInt aTextId)
    { 
    // Reading Unicode string w/o CCoe
    HBufC8* tempBuffer = NULL;
    HBufC* retBuffer = NULL;

    TRAPD(err, tempBuffer = aResFile.AllocReadL((KRIMask & aTextId))); // Remove offset from id
    if (!err)
        {
        __ASSERT_DEBUG((tempBuffer->Length()%2)==0,Panic(aTextId));

        // do bitwise shift to halve the length for mapping the tempBuffer to unicode.
        TInt newLen = tempBuffer->Length()>>1;
        TPtrC tempPointer((TText*)tempBuffer->Ptr(), newLen);

        retBuffer = tempPointer.Alloc();
        delete tempBuffer;
        }

    return retBuffer;
    }

TBool CTextResolver::AddContextAndSeparator(TErrorContext aContext)
    {
    TBool retval(EFalse); 
    
    if (iTextBuffer && iTitleText && iContextSeparator) // If we got all the parts we need...
        {
        //... then put them together according to the aContext argument
        
        // Context checked after validity of string buffers is checked, 
        // because EFalse return value is required if any of those is NULL.
        if (aContext == ECtxNoCtxNoSeparator)
            retval = ETrue;
        else
            {
            HBufC* tempBuffer = iTextBuffer->ReAlloc(iTextBuffer->Length()+iContextSeparator->Length()+iTitleText->Length()+1);

            // If allocation succeeds, insert context. Otherwise iTextBuffer stays as it is.
            if (tempBuffer)
                {
                iTextBuffer = tempBuffer;
				
				// Add the title ("context") text and separator before the error text

               	_LIT(KNewLineChar, "\n");
                iTextBuffer->Des().Insert(0, KNewLineChar);            
                iTextBuffer->Des().Insert(0, *iContextSeparator);

                if (aContext != ECtxNoCtx)
                    iTextBuffer->Des().Insert(0,*iTitleText);

                retval = ETrue;
                }
            }
        }
    return retval;
    }

void CTextResolver::AllocBuffersL()
	{
	delete iTitleText;
	iTitleText = NULL;
	delete iTextBuffer;
	iTextBuffer = NULL;

	iTitleText = HBufC::NewL(KEikErrorResolverMaxTextLength);
    iTextBuffer = HBufC::NewL(KEikErrorResolverMaxTextLength);
	}
	
void CTextResolver::DoResolveErrorStringL(TInt aError, TInt& aTextId, TUint& aFlags)
	{
	ASSERT(iCoe);
	AllocBuffersL();
    
   	TPtr errTextPtr = iTextBuffer->Des();
	TPtr errTitlePtr = iTitleText->Des();
    
    CEikonEnv::TErrorValidity errValidity = static_cast<CEikonEnv*>(iCoe)->DoGetErrorTextAndTitle(errTextPtr, aError, aTextId, aFlags, errTitlePtr, ETrue);

    if (errValidity == CEikonEnv::EErrorNumInvalid)
    	Panic(aError);

	// If either the iTextBuffer or iTitleText buffers were too small, 
	// they will contain the required length coded as digits into the buffer
	TLex sizeRealloc(*iTextBuffer);
    TInt sizeRequiredForTextBuffer = 0;
    TInt sizeRequiredForTitleBuffer = 0;
    
    // Try reading the size required, if any
    sizeRealloc.Val(sizeRequiredForTextBuffer);
    sizeRealloc = *iTitleText;
    sizeRealloc.Val(sizeRequiredForTitleBuffer);
    
    // If sizes were found, realloc and get the error text again
    if (sizeRequiredForTextBuffer || sizeRequiredForTitleBuffer)	
    	{	
		if (sizeRequiredForTextBuffer)
			{
			delete iTextBuffer;
			iTextBuffer = NULL;
			iTextBuffer = HBufC::NewL(sizeRequiredForTextBuffer);
			}
			
		if (sizeRequiredForTitleBuffer)
			{
			delete iTitleText;
			iTitleText = NULL;
			iTitleText = HBufC::NewL(sizeRequiredForTitleBuffer);
			}

	   	TPtr errTextPtr(iTextBuffer->Des());
		TPtr errTitlePtr(iTitleText->Des());
		errValidity = static_cast<CEikonEnv*>(iCoe)->DoGetErrorTextAndTitle(errTextPtr, aError, aTextId, aFlags, errTitlePtr, ETrue);
		if (!iTextBuffer->Length())
			{
			delete iTextBuffer;
			iTextBuffer = NULL;
			}
    	}
    	
    ReadLocalizedSeparatorCharacterFromResourceL(*iCoe);
	}

//  End of File


