/*
* Copyright (c) 2004 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:  This class provides the base class for ID3v2 parsers.
*
*/



// INCLUDE FILES
#include	"MetaDataParserID3v2.h"
#include	"MetaDataParserID3v22.h"
#include	"MetaDataParserID3v23.h"
#include	"MetaDataParserID3v24.h"
#include 	"MetaDataSourceDescriptor.h"

#include    <barsc.h>
#include    <barsread.h>
#include	<TopCharacterSet.rsg>
#include 	<data_caging_path_literals.hrh>

// CONSTANTS
// (ID3v2 specification found in www.id3.org)
const TInt KID3v2HeaderLength 			= 10;
const TInt KID3v2TagLength 				= 3;

const TInt KMinimumConfidenceRequired = 90;
const TInt KMinimumJapaneseConfidenceRequired = 75;
const TUint KParseFromMemory	= 1000000; 

//Album Type
const TInt KPictureTypeOther				= 0x00;
const TInt KPictureTypePixel                = 0x01;
const TInt KPictureTypeFileIcon             = 0x02;
const TInt KPictureTypeFrontCover			= 0x03;
const TInt KPictureTypeBackCover            = 0x04;

// Album Type priority
const TInt K1stAlbumArt = KPictureTypeOther;
const TInt K2ndAlbumArt = KPictureTypeFrontCover; 
const TInt K3rdAlbumArt = KPictureTypeBackCover;

_LIT8( KID3v2Tag, "ID3" );

// ============================ MEMBER FUNCTIONS ===============================

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::CMetaDataParserID3v2
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CMetaDataParserID3v2::CMetaDataParserID3v2(
	CMetaDataSource& aSource )
	:	iSource(&aSource),
        iParseFromDescriptor(EFalse),
        iAlbumType(-1),
        iAlbumPtr(NULL)
	{
    }

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::CreateID3v2ParserL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CMetaDataParserID3v2* CMetaDataParserID3v2::CreateID3v2ParserL(
	CMetaDataSource& aSource )
    {
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v2::CreateID3v2ParserL"));
#endif
	TInt version = VersionL(aSource);

	CMetaDataParserID3v2* parser = NULL;
	switch ( version )
		{
		case 2:
			parser = STATIC_CAST( CMetaDataParserID3v2*, CMetaDataParserID3v22::NewL(aSource) );
			break;
		case 3:
			parser = STATIC_CAST( CMetaDataParserID3v2*, CMetaDataParserID3v23::NewL(aSource) );
			break;
		case 4:
			parser = STATIC_CAST( CMetaDataParserID3v2*, CMetaDataParserID3v24::NewL(aSource) );
			break;
		default:	// KErrNotFound
			break;
		}
	return parser;
    }

// Destructor
CMetaDataParserID3v2::~CMetaDataParserID3v2()
	{
	if(iAutoDetectChinese || iAutoDetectRussian || iAutoDetectJapanese)
		{
		delete iCharacterSet;
		delete iTopCharacterSet;
		}
	if(iParseFromDescriptor)
		{
		delete iTag;
		delete iSourceDes;
		}
	if ( iAlbumPtr )
		{
		delete iAlbumPtr;
		}

    }

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::ParseL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v2::ParseL(
	const RArray<TMetaDataFieldId>& aWantedFields,
	CMetaDataFieldContainer& aContainer )
    {
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v2::ParseL"));
#endif
	iContainer = &aContainer;
	PrepareToParseL();
	CRepository *metadataRepository = CRepository::NewL(KCRUidMetadataUtility);
	TInt err = KErrNone;
	err = metadataRepository->Get(KMetadataUtilityAutoDetectChineseChars, iAutoDetectChinese);
	if(err)
	{
		iAutoDetectChinese = EFalse;	
	}
	err = metadataRepository->Get(KMetadataUtilityAutoDetectJapaneseChars, iAutoDetectJapanese);
	if(err)
	{
		iAutoDetectJapanese = EFalse;	
	}
	delete metadataRepository;
	TLanguage lang = User::Language();
	if(lang == ELangRussian || lang == ELangUkrainian)	
		{
		iAutoDetectRussian = ETrue;	
		}
	if(iAutoDetectChinese || iAutoDetectRussian || iAutoDetectJapanese)
		{
		CreateCharacterSetsL();
		}
	// choose if want to parse from descriptor
	if(iFrameDataSize < KParseFromMemory) // 1Mb
		{
		iTag = HBufC8::NewL(iFrameDataSize);
		TPtr8 des( iTag->Des() );
		iSource->ReadL(0, des);
		
		iSourceDes = CMetaDataSourceDescriptor::NewL(des);
		iSource = iSourceDes;
		iParseFromDescriptor = ETrue;
		}
	if ( aWantedFields.Count() == 0 )
		{
		ParseFramesL();
		}
	else
		{
		ParseFramesL(aWantedFields);
		}
    }

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::ParseFramesL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v2::ParseFramesL()
	{
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v2::ParseFramesL"));
#endif
	TMetaDataFieldId fieldId;
	TInt frameSize;
	TInt frameHeaderSize = 0;
	while ( iFrameOffset < ( iFrameDataSize - 1 ) )
		{
		GetNextFieldL(fieldId, frameHeaderSize, frameSize);

		if ( frameHeaderSize == 0 )
			{
			// We have hit the padding -> no more fields to read
            break;
            }

        if ( frameSize < 0 || frameSize > ( iFrameDataSize - iFrameOffset ) )
            {
            // Invalid frame size -> stop
            break;
            }
		TInt err = KErrNone; // ignore err, as some entry may be extracted without exception
		if ( fieldId != EUnknownMetaDataField )
			{
			switch ( fieldId )
				{
				case EMetaDataSongTitle:
				case EMetaDataArtist:
				case EMetaDataAlbum:
				case EMetaDataAlbumTrack:
				case EMetaDataComposer:
				case EMetaDataCopyright:
				case EMetaDataOriginalArtist:
					TRAP(err, GetTextInfoL( fieldId, frameSize ));
					break;

				case EMetaDataGenre:
					TRAP(err, GetGenreL( frameSize ));
					break;

				case EMetaDataYear:
					TRAP(err, GetTextYearL( frameSize ));
					break;

				case EMetaDataComment:
					TRAP(err, GetCommentL( frameSize ));
					break;

				case EMetaDataUrl:
					TRAP(err, GetUrlL( frameSize ));
					break;
				
				case EMetaDataUserUrl:
					TRAP(err, GetUserUrlL( frameSize ));
					break;
					
				case EMetaDataJpeg:
				    // check for 1st album art
				    if (iAlbumType != K1stAlbumArt)
				        {
	                    TRAP(err,GetJpegL( frameSize ));				        
				        }
					break;

				case EMetaDataDuration:
					TRAP(err, GetDurationL( frameSize ));
					break;
				
				case EMetaDataDate:
					TRAP(err, GetTextDateL( frameSize ));
					break;
						
				case EMetaDataRating:
					TRAP(err, GetRatingL( frameSize ));
					break;
							
				default:
					break;
				}
			}
		iFrameOffset += (frameSize + frameHeaderSize);
		}
    
    // retrieve the album art
    TRAPD (error, RetrieveAlbumArtL());
    if (error)
        {
        #ifdef _DEBUG
            RDebug::Print(_L("CMetaDataParserID3v2::ParseFramesL - RetrieveAlbumArt leave"));
        #endif        
        }
  
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::ParseFramesL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v2::ParseFramesL(
	const RArray<TMetaDataFieldId>& aWantedFields )
	{
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v2::ParseFramesL - 2"));
#endif
	TMetaDataFieldId fieldId;
	TInt frameSize;
	TInt frameHeaderSize;
	while ( iFrameOffset < ( iFrameDataSize - 1 ) )
		{
		GetNextFieldL(fieldId, frameHeaderSize, frameSize);

		if ( frameHeaderSize == 0 )
			{
			// We have hit the padding -> no more fields to read
            break;
            }

        if ( frameSize < 0 || frameSize > ( iFrameDataSize - iFrameOffset ) )
            {
            // Invalid frame size -> stop
            break;
            }
		TInt err = KErrNone; 
		if ( fieldId != EUnknownMetaDataField )
			{
            // Look for it in the wanted field array
            TInt count( aWantedFields.Count() );
            for ( TInt i = 0; i < count; i++ )
                {
                if ( aWantedFields[ i ] == fieldId )
                    {
					switch ( fieldId )
						{
						case EMetaDataSongTitle:
						case EMetaDataArtist:
						case EMetaDataAlbum:
						case EMetaDataAlbumTrack:
						case EMetaDataComposer:
						case EMetaDataCopyright:
						case EMetaDataOriginalArtist:
							TRAP(err, GetTextInfoL( fieldId, frameSize ));
							break;

						case EMetaDataGenre:
							TRAP(err, GetGenreL( frameSize ));
							break;

						case EMetaDataYear:
							TRAP(err, GetTextYearL( frameSize ));
							break;

						case EMetaDataComment:
							TRAP(err, GetCommentL( frameSize ));
							break;

						case EMetaDataUrl:
							TRAP(err, GetUrlL( frameSize ));
							break;

						case EMetaDataJpeg:
		                    // check for 1st album art
		                    if (iAlbumType != K1stAlbumArt)
		                        {
		                        TRAP(err,GetJpegL( frameSize ));                        
		                        }
							break;
									
						case EMetaDataUserUrl:
							TRAP(err, GetUserUrlL( frameSize ));
							break;
							
						case EMetaDataDuration:
							TRAP(err, GetDurationL( frameSize ));
							break;
						
						case EMetaDataDate:
							TRAP(err, GetTextDateL( frameSize ));
							break;
							
						case EMetaDataRating:
							TRAP(err, GetRatingL( frameSize ));
							break;
						
						default:
							break;
						}
					}
				}
			}
		iFrameOffset += (frameSize + frameHeaderSize);
		}
	
	// retrieve the album art
    TRAPD (error, RetrieveAlbumArtL());
    if (error)
        {
        #ifdef _DEBUG
            RDebug::Print(_L("CMetaDataParserID3v2::ParseFramesL-2 - RetrieveAlbumArt leave"));
        #endif        
        }
 
	}
	
// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::RetrieveAlbumArtL
// -----------------------------------------------------------------------------
//	
void CMetaDataParserID3v2::RetrieveAlbumArtL()
    {
    // If album art exists, add to container
    if ( iAlbumPtr != NULL )
        {
        TPtr8 des( iAlbumPtr->Des() );       
        TPtrC8 pic = des.Mid(iAlbumOffset);
        TInt length = pic.Length();
        if ( length )
            {
            iContainer->AppendL( EMetaDataJpeg, pic );
            }
         
        // reset the album related variables
        iAlbumType = -1;
        iAlbumOffset = 0;
        delete iAlbumPtr;
        iAlbumPtr = NULL;
        }
    }

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::Handlev2GetGenreL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v2::HandleV2GetGenreL( TInt aSize, TInt aKID3v2FrameHeaderLength, TInt aKID3v2GenreNameLength )
	{
	#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v2::GetGenreL"));
	#endif

	HBufC8* frame = HBufC8::NewLC( aSize );
	TPtr8 des( frame->Des() );

	iSource->ReadL( iFrameOffset + aKID3v2FrameHeaderLength, des, aSize );

	if ( des.Length() < aSize )
		{
		// Partial frame
		User::Leave( KErrCorrupt );
		}

	TInt encoding = (TInt) (des.Left(1))[0];
	TPtrC8 info = StripTrailingZeroes( des.Mid(1), encoding);
	TInt length = info.Length();

	if(length <= 0)
		{
		CleanupStack::PopAndDestroy();  // frame
		return;
		}
	else 
		{
		length = (length > aKID3v2GenreNameLength) ? length : aKID3v2GenreNameLength;
		}

	HBufC8* genreData = HBufC8::NewLC( length );
	TPtr8 temp( genreData->Des() );
	temp.Copy(info);

	if ( encoding == 0 )
		{
		   if ( temp[0] == KID3v2format )
			{
			//temp.Delete(0,1); 
			TLex8 lex(temp.Ptr() + 1);
			TInt genreID = 0;
			TInt err = lex.Val(genreID);	
			if(err != KErrNone)
				{
					CleanupStack::PopAndDestroy(2);  // genreData, frame
					return;
				}
			if(genreID >= 0 && genreID <= 125 || genreID == 199)
				{
				temp.FillZ(0);
				MapID3GenreToStringL(genreID,temp);
				}
			}
			
			if( length )
			 {
			 HBufC* data16 = HBufC::NewLC( length );
		 	 TPtr unicode( data16->Des() );
		 	 if ( ConvertToUnicodeL(encoding, temp, unicode) == KErrNone )
				{
					iContainer->AppendL( EMetaDataGenre, unicode );
				}
			 CleanupStack::PopAndDestroy(data16);  // data16
			 }
		}
	else // non ASCII 
		{
		HBufC* dataGen16 = HBufC::NewLC( length );
		TPtr unicodeGen( dataGen16->Des() );
		if ( ConvertToUnicodeL(encoding, temp, unicodeGen) == KErrNone )
			{
			if (temp.Length() <= 2)
			    {
		        CleanupStack::PopAndDestroy(3);  // dataGen16, genreData, frame
				return;			    
			    }
			temp = temp.Mid(2);
			if(temp[0] == KID3v2format)
				{
				TLex16 lex(unicodeGen.Ptr()+1);
				TInt val=0;
				TInt err = lex.Val(val);
				if(err != KErrNone)
					{
					CleanupStack::PopAndDestroy(3);  // dataGen16, genreData, frame
					return;
					}
					
				if(val >= 0 && val <= 125 || val == 199)
					{
					unicodeGen.FillZ(0);
					MapID3GenreToStringL(val,unicodeGen);
					}
				}
			iContainer->AppendL( EMetaDataGenre, unicodeGen );				
			}
		CleanupStack::PopAndDestroy(dataGen16);  // dataGen16
		}
	CleanupStack::PopAndDestroy(genreData);  // genreData
	CleanupStack::PopAndDestroy(frame);  // frame	
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::VersionL
// -----------------------------------------------------------------------------
//
TInt CMetaDataParserID3v2::VersionL(
	CMetaDataSource& aSource )
	{
	TInt size = 0;
	aSource.Size( size );
	if ( size < KID3v2HeaderLength )
		{
		// This isn't ID3v2
		return KErrNotFound;
		}

	TBuf8<KID3v2HeaderLength> header;
	aSource.ReadL( header );
	// ID3v2 header consists of following parts:
    // - identifier "ID3", 3 bytes
    // - version, 2 bytes
    // - flags, 1 byte
    // - data length, 4 bytes

	if ( header.Left( KID3v2TagLength ).Compare( KID3v2Tag ) != 0 )
		{
		return KErrNotFound;
		}

	TInt frameSize = 0;
	for ( TInt i = 6; i < 10; i++ )
		{
		frameSize <<= 7;
		frameSize |= header[i] & 0x7f;
		}

	if ( size < ( frameSize + KID3v2HeaderLength ) )
		{
		// Partial ID3v2 tag
		User::Leave( KErrCorrupt );
		}

	return (TInt) header[3];
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::CreateCharacterSetsL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v2::CreateCharacterSetsL()
	{
	// Get list of charconv supported character sets
	iCharacterSet = CCnvCharacterSetConverter::CreateArrayOfCharacterSetsAvailableL(iFs);
	iTopCharacterSet = new (ELeave) CArrayFixFlat<CCnvCharacterSetConverter::SCharacterSet>(12);
	GenerateTopCharacterSetsL();
	}
	
// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::DetectCharacterSetL
// -----------------------------------------------------------------------------
//
TInt CMetaDataParserID3v2::DetectCharacterSetL(
	const TDesC8& aDesc)
	{
	TInt confidence = 0;
	TInt highestConfidence = 0;
	TUint charSetId;
	TUint highestConfidencecharSetId = 0;

	CCnvCharacterSetConverter* charSetConv = CCnvCharacterSetConverter::NewLC();
	TInt count = iTopCharacterSet->Count();
	for ( TInt i=0; i < iTopCharacterSet->Count(); i++)
		{
		charSetId = iTopCharacterSet->At(i).Identifier();
		charSetConv->ConvertibleToCharSetL(confidence, charSetId, *iTopCharacterSet, aDesc);
		if ( confidence > highestConfidence )
			{
			highestConfidence = confidence;
			highestConfidencecharSetId = charSetId;
			}
		}
	CleanupStack::PopAndDestroy(charSetConv);
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v1::DetectCharacterSetL :-> Confidence[%d] CharSetId[%x]"),
	        highestConfidence, highestConfidencecharSetId);
#endif

	if ( (highestConfidence >= KMinimumConfidenceRequired) || (iAutoDetectJapanese && highestConfidence >= KMinimumJapaneseConfidenceRequired ))
		{
		iCharacterSetId = highestConfidencecharSetId;
		return KErrNone;
		}
	else
		{
		return KErrNotFound;
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::AutoDetectL
// -----------------------------------------------------------------------------
//
TInt CMetaDataParserID3v2::AutoDetectL(const TDesC8& aDesc,
	TDes16& aUnicode)
{
	if ( iCharacterSetId == 0 )
		{
		if ( DetectCharacterSetL(aDesc) == KErrNotFound )
			{
			return KErrNotFound;
			}
		}

	CCnvCharacterSetConverter* charSetConv = CCnvCharacterSetConverter::NewLC();
	TInt state = CCnvCharacterSetConverter::KStateDefault;
	TInt numOfUnconvertibleChars = 0;

	charSetConv->PrepareToConvertToOrFromL(iCharacterSetId, *iCharacterSet, iFs);
	TInt retVal = charSetConv->ConvertToUnicode(aUnicode, aDesc, state, numOfUnconvertibleChars);
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v1::ConvertToUnicode :-> Tag Size[%d] Unicode Tag Size[%d]Bytes Unconverted[%d] retVal[%d]"),
		aDesc.Length(), aUnicode.Length(), numOfUnconvertibleChars, retVal);
#endif
	if ( retVal < 0 )
		{
		CleanupStack::PopAndDestroy();	// charSetConv
		return retVal;
		//return KErrGeneral;
		}

	if ( retVal > 0 || numOfUnconvertibleChars > 0 )
		{
		// This is different character set. Need to auto detect again
		if ( DetectCharacterSetL(aDesc) == KErrNotFound )
			{
			CleanupStack::PopAndDestroy();	// charSetConv
			return KErrNotFound;
			}
		state = CCnvCharacterSetConverter::KStateDefault;
		numOfUnconvertibleChars = 0;
		charSetConv->PrepareToConvertToOrFromL(iCharacterSetId, *iCharacterSet, iFs);
		retVal = charSetConv->ConvertToUnicode(aUnicode, aDesc, state, numOfUnconvertibleChars);
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v1::ConvertToUnicode :-> Tag Size[%d] Unicode Tag Size[%d]Bytes Unconverted[%d] retVal[%d]"),
		aDesc.Length(), aUnicode.Length(), numOfUnconvertibleChars, retVal);
#endif
        if ( retVal != 0 || numOfUnconvertibleChars > 0 )
			{
	        CleanupStack::PopAndDestroy();	// charSetConv
	        return KErrGeneral;
			}
		}
	CleanupStack::PopAndDestroy();	// charSetConv
	return KErrNone;
}



// -----------------------------------------------------------------------------
// CMetaDataParserID3v2::Version()
// -----------------------------------------------------------------------------
//
TID3Version CMetaDataParserID3v2::ID3Version()
{
	return EID3Version2;
}


//-----------------------------------------------------------------------------
// CMetaDataParserID3v2::NeedRetrieveAlbumArt( TInt aPicType )
//-----------------------------------------------------------------------------
//
TBool CMetaDataParserID3v2::NeedRetrieveAlbumArt( TInt aPicType )
{
    TBool ret = EFalse;
    
    switch (iAlbumType)
        {
        case K1stAlbumArt : 
            // return EFalse, no need to retrieve another album art
            break;
        case K2ndAlbumArt : 
            if (aPicType == K1stAlbumArt)
            {
                ret = ETrue;            
            }
            break;
        case K3rdAlbumArt : 
            if ((aPicType == K1stAlbumArt) ||
                (aPicType == K2ndAlbumArt))
            {
                ret = ETrue;            
            }
            break;
        default:
            // There is no album art, or it is a priority album art
            if ((iAlbumType == -1) ||
                (aPicType == K1stAlbumArt) ||
                (aPicType == K2ndAlbumArt) ||
                (aPicType == K3rdAlbumArt))
            {
                ret = ETrue;            
            }
            // if the old one is bad, and the new one is good, use it
            else
            {
                if ( IgnoreAlbumType(iAlbumType) && !IgnoreAlbumType(aPicType) )
                {   
                    // get the new one
                    ret = ETrue;            
                }
            }

        }
    
    return ret;
}


//-----------------------------------------------------------------------------
// CMetaDataParserID3v2::IgnoreAlbumType( TInt aPicType )
//-----------------------------------------------------------------------------
//
TBool CMetaDataParserID3v2::IgnoreAlbumType (TInt aPicType)
{
    if (aPicType == KPictureTypePixel || aPicType == KPictureTypeFileIcon)
        {
        return ETrue;
        }
   
    return EFalse;
      
}
//  End of File
