/*
* 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 implements an ID3v1 and v1.1 parser as specified in
*                www.id3.org.
*
*/



// INCLUDE FILES
#include	"MetaDataParserID3v1.h"
#include <centralrepository.h>
#include "MetadataUtilityCRKeys.h"
#include    <barsc.h>
#include    <barsread.h>
#include 	<data_caging_path_literals.hrh>
#include	<TopCharacterSet.rsg>
#ifdef _DEBUG
#include	<e32svr.h>
#endif

// CONSTANTS
// (ID3v1 specification found in www.id3.org)
_LIT8( KID3v1TagHeader, "TAG" );
const TInt KID3v1TagHeaderLength = 3;

const TInt KOffsetTitle 		= 3;
const TInt KOffsetArtist		= 33;
const TInt KOffsetAlbum 		= 63;
const TInt KOffsetYear 			= 93;
const TInt KOffsetComment 		= 97;
const TInt KOffsetAlbumTrack 	= 125;
const TInt KOffsetGenre 		= 127;

const TInt KLengthTitle 		= 30;
const TInt KLengthArtist		= 30;
const TInt KLengthAlbum 		= 30;
const TInt KLengthYear 			= 4;
const TInt KLengthComment 		= 30;
const TInt KLengthAlbumTrack 	= 1;
const TInt KLengthGenre 		= 1;

const TInt KMinimumConfidenceRequired = 90;


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

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::CMetaDataParserID3v1
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CMetaDataParserID3v1::CMetaDataParserID3v1(
	CMetaDataSource& aSource )
	:	iSource(aSource),
		iCharacterSetId(0)
	{
    }

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::ConstructL()
    {
	iTagData.FillZ( KID3v1TagLength );
	if ( ValidateL() )
		{
		User::LeaveIfError(iFs.Connect());
		// Get list of charconv supported character sets
		iCharacterSet = CCnvCharacterSetConverter::CreateArrayOfCharacterSetsAvailableL(iFs);
		iTopCharacterSet = new (ELeave) CArrayFixFlat<CCnvCharacterSetConverter::SCharacterSet>(12);
		GenerateTopCharacterSetsL();
    	}
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v1::ConstructL - Done"));
#endif
    }

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CMetaDataParserID3v1* CMetaDataParserID3v1::NewL(
	CMetaDataSource& aSource )
    {
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v1::NewL"));
#endif
	CMetaDataParserID3v1* self = new( ELeave ) CMetaDataParserID3v1(aSource);
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop();
    return self;
    }

// Destructor
CMetaDataParserID3v1::~CMetaDataParserID3v1()
	{
	if ( iExists )
		{
		delete iCharacterSet;
		delete iTopCharacterSet;
		iFs.Close();
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::ParseL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::ParseL(
	const RArray<TMetaDataFieldId>& aWantedFields,
	CMetaDataFieldContainer& aContainer )
    {
#ifdef _DEBUG
	RDebug::Print(_L("CMetaDataParserID3v1::ParseL"));
#endif
	iContainer = &aContainer;
	TInt err = KErrNone;
	if ( aWantedFields.Count() == 0 )
		{
		TRAP(err, GetArtistL()); // ignore errors, coz other entries may be 
		TRAP(err, GetTitleL()); // extracted wihout exceptions.
		TRAP(err, GetAlbumL());
		TRAP(err, GetYearL());
		TRAP(err, GetCommentL());
		TRAP(err, GetAlbumTrackL());
		TRAP(err, GetGenreL());
		}
	else
		{
		// Look for it in the wanted field array
		TInt count( aWantedFields.Count() );
		for ( TInt i = 0; i < count; i++ )
			{
			switch ( aWantedFields[ i ] )
				{
				case EMetaDataSongTitle:
					TRAP(err, GetTitleL());
					break;
				case EMetaDataArtist:
					TRAP(err, GetArtistL());
					break;
				case EMetaDataAlbum:
					TRAP(err, GetAlbumL());
					break;
				case EMetaDataYear:
					TRAP(err, GetYearL());
					break;
				case EMetaDataComment:
					TRAP(err, GetCommentL());
					break;
				case EMetaDataAlbumTrack:
					TRAP(err, GetAlbumTrackL());
					break;
				case EMetaDataGenre:
					TRAP(err, GetGenreL());
					break;
				default:
					break;
				}
			}
		}
    }

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::ValidateL
// -----------------------------------------------------------------------------
//
TBool CMetaDataParserID3v1::ValidateL()
	{
	// ID3v1 tags are always 128 bytes long
	TInt size;
	User::LeaveIfError( iSource.Size( size ) );
	if ( size < KID3v1TagLength )
		{
		// This isn't ID3v1
		return EFalse;
		}

	iSource.ReadL( size - KID3v1TagLength, iTagData );
	// Search for the ID3v1 header in the tag data
	if ( iTagData.Left( KID3v1TagHeaderLength ).Compare( KID3v1TagHeader ) != 0 )
		{
		return EFalse;
		}

	iExists = ETrue;
	return ETrue;
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::GetTitleL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::GetTitleL()
	{
	TPtrC8 title = StripTrailingZeroes( iTagData.Mid( KOffsetTitle, KLengthTitle ) );
	if ( title.Length() )
		{
		TBuf<KLengthTitle> titleUnicode;
		if ( ConvertToUnicodeL(title, titleUnicode) == KErrNone )
			{
			iContainer->AppendL( EMetaDataSongTitle, titleUnicode );
			}
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::GetArtistL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::GetArtistL()
	{
	TPtrC8 artist = StripTrailingZeroes( iTagData.Mid( KOffsetArtist, KLengthArtist ) );
	if ( artist.Length() )
		{
		TBuf<KLengthArtist> artistUnicode;
		if ( ConvertToUnicodeL(artist, artistUnicode) == KErrNone )
			{
			iContainer->AppendL( EMetaDataArtist, artistUnicode );
			}
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::GetAlbumL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::GetAlbumL()
	{
	TPtrC8 album = StripTrailingZeroes( iTagData.Mid( KOffsetAlbum, KLengthAlbum ) );
	if ( album.Length() )
		{
		TBuf<KLengthAlbum> albumUnicode;
		if ( ConvertToUnicodeL(album, albumUnicode) == KErrNone )
			{
			iContainer->AppendL( EMetaDataAlbum, albumUnicode );
			}
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::GetYearL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::GetYearL()
	{
	TBuf<KLengthYear> year;
	year.Copy( StripTrailingZeroes( iTagData.Mid( KOffsetYear, KLengthYear ) ) );
	if ( year.Length() )
		{
		iContainer->AppendL( EMetaDataYear, year );
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::GetCommentL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::GetCommentL()
	{
	TInt commentLength = 0;
	// To be compliant with ID3 v1.1
	if ( iTagData[KOffsetAlbumTrack] == 0 && iTagData[KOffsetAlbumTrack + 1] != 0 )
		{
		commentLength = 28;
		}
	else
		{
		commentLength = KLengthComment;
		}
	TPtrC8 comment = StripTrailingZeroes( iTagData.Mid( KOffsetComment, commentLength ) );
	if ( comment.Length() )
		{
		TBuf<KLengthComment> commentUnicode;
		if ( ConvertToUnicodeL(comment, commentUnicode) == KErrNone )
			{
			iContainer->AppendL( EMetaDataComment, commentUnicode );
			}
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::GetAlbumTrackL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::GetAlbumTrackL()
	{
	// To be compliant with ID3 v1.1
	if ( iTagData[KOffsetAlbumTrack] == 0 && iTagData[KOffsetAlbumTrack + 1] != 0 )
		{
		TBuf<3> track;	// 1 byte, but can be upto 3 bytes in when converted to ascii
		TUint num = iTagData.Mid( KOffsetAlbumTrack + 1, KLengthAlbumTrack )[0];
		track.NumUC( num );
		iContainer->AppendL( EMetaDataAlbumTrack, track );
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::GetGenreL
// -----------------------------------------------------------------------------
//
void CMetaDataParserID3v1::GetGenreL()
	{
		TUint num = iTagData.Mid( KOffsetGenre, KLengthGenre )[0];
		TBufC<KMaxGenreSize> genre;
		TPtr genrePtr = genre.Des();
		if(num <= 125 || num == 199)
			{
			MapID3GenreToStringL(num, genrePtr);
			iContainer->AppendL( EMetaDataGenre, genre );
		}
	}

// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::ConvertToUnicodeL
// -----------------------------------------------------------------------------
//
TInt CMetaDataParserID3v1::ConvertToUnicodeL(
	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;
	}


// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::DetectCharacterSetL
// -----------------------------------------------------------------------------
//
TInt CMetaDataParserID3v1::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]"),
   		confidence, iCharacterSetId);
#endif
	if ( highestConfidence < KMinimumConfidenceRequired )
		{
		return KErrNotFound;
		}
	else
		{
		iCharacterSetId = highestConfidencecharSetId;
		return KErrNone;
		}
	}
	
// -----------------------------------------------------------------------------
// CMetaDataParserID3v1::Version()
// -----------------------------------------------------------------------------
//
TID3Version CMetaDataParserID3v1::ID3Version()
{
	return EID3Version1;
}

//  End of File
