/*
* Copyright (c) 1997-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 <e32std.h>
#include <e32base.h>

#include <gdi.h>
#include <s32file.h>
#include <e32test.h>

#include <txtrich.h>
#include <txtfmlyr.h>
#include <txtfrmat.h>
#include <txtstyle.h>

#define UNUSED_VAR(a) a = a

const TInt KTestCleanupStack=0x40;

LOCAL_D RTest test(_L("Testing Paragraph Styles"));
LOCAL_D CTrapCleanup* TheTrapCleanup;

LOCAL_D CRichText* TheText;
LOCAL_D CStyleList* TheStyleList;
LOCAL_D CParaFormatLayer* TheNormalParaLayer;
LOCAL_D CCharFormatLayer* TheNormalCharLayer;
LOCAL_D CParagraphStyle* TheStyleOne;
LOCAL_D CParagraphStyle* TheStyleTwo;


_LIT(KOutputFile, "c:\\etext\\t_style.tst");
template <class T>
void testStoreRestoreL(T& aCopy,const T& aOriginal)
// Test document persistance.
//
    {
	// set up the store
	RFs	theFs;
	theFs.Connect();
	//
	theFs.Delete(KOutputFile);
	theFs.MkDirAll(KOutputFile);
	CFileStore* theStore=CDirectFileStore::CreateL(theFs,KOutputFile,EFileRead|EFileWrite);
	CleanupStack::PushL(theStore);
	theStore->SetTypeL(KDirectFileStoreLayoutUid);
	//
	// store the original
	TStreamId id(0);
	TRAPD(ret,id=aOriginal.StoreL(*theStore));
		test(ret==KErrNone);
	//
	// restore into the copy
	TRAP(ret,aCopy.RestoreL(*theStore,id));
		test(ret==KErrNone);
	//
	// tidy up
	CleanupStack::PopAndDestroy();  // theStore
	theFs.Close();
    }


LOCAL_C TInt IsEqual(const CRichText* aCopy,const CRichText* aOriginal)
//
// Returns true if aCopy contents matches aOriginal contents.
// Takes account of multiple segments of a segmented text component.
//
	{
	TInt lengthOfOriginal=aOriginal->DocumentLength();
	TInt lengthOfCopy=aCopy->DocumentLength();
	test(lengthOfOriginal==lengthOfCopy);
//
	TPtrC copy,orig;
//
	TInt lengthRead=0;
	while(lengthRead<=lengthOfOriginal)
		{
		copy.Set((aCopy->Read(lengthRead)));
		orig.Set((aOriginal->Read(lengthRead)));
		for (TInt offset=0; offset<orig.Length(); offset++)
			test(copy[offset]==orig[offset]);
		lengthRead+=orig.Length();
		}
	test(lengthRead==lengthOfOriginal+1);
//
	CStyleList* origStyle=aOriginal->StyleList();
	CStyleList* copyStyle=aCopy->StyleList();
	TInt origStyleCount=origStyle->Count();
	TInt copyStyleCount=copyStyle->Count();
	test(origStyleCount==copyStyleCount);
	for (TInt ii=0;ii<origStyleCount;ii++)
		{
		RParagraphStyleInfo oInfo=origStyle->At(ii);
		RParagraphStyleInfo cInfo=copyStyle->At(ii);
		test(oInfo.iStyle->iName==cInfo.iStyle->iName);
		if (oInfo.iStyleForNextPara==NULL)
			test(cInfo.iStyleForNextPara==NULL);
		}

	return 1;
	}


LOCAL_C void ConstructEnvWithNullParaFormat()
	{
	// Create global layers
	CParaFormat* normalPara=CParaFormat::NewLC();
	TParaFormatMask paraFormatMask;
	normalPara->iHorizontalAlignment=CParaFormat::ELeftAlign;
	paraFormatMask.SetAttrib(EAttAlignment);
	TheNormalParaLayer=CParaFormatLayer::NewL(normalPara,paraFormatMask);
	CleanupStack::PopAndDestroy();  // normalPara
	TCharFormat charFormat;
	TCharFormatMask charFormatMask;
	TheNormalCharLayer=CCharFormatLayer::NewL(charFormat,charFormatMask);
	//
	// Create some paragraph styles
	TheStyleOne=CParagraphStyle::NewL(*TheNormalParaLayer,*TheNormalCharLayer);
	TheStyleOne->iName=_L("Style1");
	
	//	Style two is based on style one
	TheStyleTwo=CParagraphStyle::NewL( *TheStyleOne, *(TheStyleOne->CharFormatLayer()));
	TheStyleTwo->iName=_L("Style2");
	//
	CParaFormat* styleFormat=CParaFormat::NewLC();
	TParaFormatMask styleMask;
	styleFormat->iHorizontalAlignment=CParaFormat::ECenterAlign;
	styleMask.SetAttrib(EAttAlignment);
	TheStyleOne->SetL(styleFormat,styleMask);
	//
	styleFormat->iHorizontalAlignment=CParaFormat::ERightAlign;
	//	Set paragraph format to NULL
	TheStyleTwo->SetL( NULL,styleMask);	
	CleanupStack::PopAndDestroy();  // styleFormat
	//
	// Create style table and insert styles.
	TheStyleList=CStyleList::NewL();	
	RParagraphStyleInfo info(TheStyleOne);
	TInt error=TheStyleList->AppendL(&info);
	test(error==KErrNone);
	RParagraphStyleInfo info1=TheStyleList->At(0);
	CParagraphStyle* style=info1.iStyle;
	style=NULL;

	RParagraphStyleInfo info2(TheStyleTwo,TheStyleOne);
	error=TheStyleList->AppendL(&info2);
	test(error==KErrNone);
	
	error=TheStyleList->AppendL(&info2);
	test(error==KErrAlreadyExists);
	test(TheStyleList->Count()==2);
	
	style=TheStyleList->At(1).iStyle;
	test(style->iName==_L("Style2"));
	//
	// Create the rich text with styles.
	TheText=CRichText::NewL(TheNormalParaLayer,TheNormalCharLayer,*TheStyleList);
}

LOCAL_C void ConstructEnvironment()
// Create some styles.
//
	{
	// Create global layers
	CParaFormat* normalPara=CParaFormat::NewLC();
	TParaFormatMask paraFormatMask;
	normalPara->iHorizontalAlignment=CParaFormat::ELeftAlign;
	paraFormatMask.SetAttrib(EAttAlignment);
	TheNormalParaLayer=CParaFormatLayer::NewL(normalPara,paraFormatMask);
	CleanupStack::PopAndDestroy();  // normalPara
	TCharFormat charFormat;
	TCharFormatMask charFormatMask;
	TheNormalCharLayer=CCharFormatLayer::NewL(charFormat,charFormatMask);
	//
	// Create some paragraph styles
	TheStyleOne=CParagraphStyle::NewL(*TheNormalParaLayer,*TheNormalCharLayer);
	TheStyleOne->iName=_L("Style1");
	TheStyleTwo=CParagraphStyle::NewL(*TheNormalParaLayer,*TheNormalCharLayer);
	TheStyleTwo->iName=_L("Style2");
	//
	CParaFormat* styleFormat=CParaFormat::NewLC();
	TParaFormatMask styleMask;
	styleFormat->iHorizontalAlignment=CParaFormat::ECenterAlign;
	styleMask.SetAttrib(EAttAlignment);
	TheStyleOne->SetL(styleFormat,styleMask);
	//
	styleFormat->iHorizontalAlignment=CParaFormat::ERightAlign;
	TheStyleTwo->SetL(styleFormat,styleMask);
	CleanupStack::PopAndDestroy();  // styleFormat
	//
	// Create style table and insert styles.
	TheStyleList=CStyleList::NewL();	
	RParagraphStyleInfo info(TheStyleOne);
	TInt error=TheStyleList->AppendL(&info);
	test(error==KErrNone);
	RParagraphStyleInfo info1=TheStyleList->At(0);
	CParagraphStyle* style=info1.iStyle;
	style=NULL;

	RParagraphStyleInfo info2(TheStyleTwo,TheStyleOne);
	error=TheStyleList->AppendL(&info2);
	test(error==KErrNone);
	
	error=TheStyleList->AppendL(&info2);
	test(error==KErrAlreadyExists);
	test(TheStyleList->Count()==2);
	
	style=TheStyleList->At(1).iStyle;
	test(style->iName==_L("Style2"));
	//
	// Create the rich text with styles.
	TheText=CRichText::NewL(TheNormalParaLayer,TheNormalCharLayer,*TheStyleList);
	}


LOCAL_C void KillEnvironment()
// Kill everything
//
	{
	delete TheText;
	// the style table is owned by the rich text, and is destroyed there.
	delete TheNormalParaLayer;
	delete TheNormalCharLayer;
	}


LOCAL_C void TestConstruction()
// Test the construction/destruction of rich text with styles
//
	{
	__UHEAP_MARK;

	ConstructEnvironment();
	KillEnvironment();

	__UHEAP_MARKEND;
	}


LOCAL_C void TestParaWithNullParaFormat()
	{
	CParagraphStyle::TApplyParaStyleMode applyMode=CParagraphStyle::ERetainNoSpecificFormats;

	test.Next(_L("Apply style to paragraph with NULL para format"));
	ConstructEnvWithNullParaFormat();
	TheText->InsertL(0,_L("HEADINGBODYTEXT"));
	//
	TheText->ApplyParagraphStyleL(*TheStyleList->At(1).iStyle,0,1,applyMode);
	CParaFormat* paraFormat=CParaFormat::NewLC();
	TheText->GetParagraphFormatL(paraFormat,0);
	test(paraFormat->iHorizontalAlignment==CParaFormat::ECenterAlign);
	//
	TChar delimiter=CEditableText::EParagraphDelimiter;
	TheText->InsertL(7,delimiter);
	TheText->GetParagraphFormatL(paraFormat,6);
	test(paraFormat->iHorizontalAlignment==CParaFormat::ECenterAlign);
	TheText->GetParagraphFormatL(paraFormat,8);
	test(paraFormat->iHorizontalAlignment==CParaFormat::ECenterAlign);
	CleanupStack::PopAndDestroy();
	KillEnvironment();	
	}

LOCAL_C void TestSharedPara()
// Test
//
	{
	CParagraphStyle::TApplyParaStyleMode applyMode=CParagraphStyle::ERetainNoSpecificFormats;

	test.Next(_L("Apply style to shared paragraph"));
	ConstructEnvironment();
	TheText->InsertL(0,_L("HEADINGBODYTEXT"));
	//
	TheText->ApplyParagraphStyleL(*TheStyleList->At(0).iStyle,0,1,applyMode);
	CParaFormat* paraFormat=CParaFormat::NewLC();
	TheText->GetParagraphFormatL(paraFormat,0);
	test(paraFormat->iHorizontalAlignment==CParaFormat::ECenterAlign);
	//
	TChar delimiter=CEditableText::EParagraphDelimiter;
	TheText->InsertL(7,delimiter);
	TheText->GetParagraphFormatL(paraFormat,6);
	test(paraFormat->iHorizontalAlignment==CParaFormat::ECenterAlign);
	TheText->GetParagraphFormatL(paraFormat,8);
	test(paraFormat->iHorizontalAlignment==CParaFormat::ECenterAlign);
	CleanupStack::PopAndDestroy();
	KillEnvironment();
	}


LOCAL_C void TestNonSharedPara()
//
	{
	CParagraphStyle::TApplyParaStyleMode applyMode=CParagraphStyle::ERetainNoSpecificFormats;

	test.Next(_L("Apply style to non-shared paragraph"));
	ConstructEnvironment();
	//
	TheText->InsertL(0,_L("This is paragraph one.This is paragraph number two."));
	TChar delimiter=CEditableText::EParagraphDelimiter;
	TheText->InsertL(22,delimiter);
	//
	TCharFormat charFormat;
	TCharFormatMask charFormatMask;
	charFormat.iFontPresentation.iStrikethrough=EStrikethroughOn;
	charFormatMask.SetAttrib(EAttFontStrikethrough);
	TheText->ApplyCharFormatL(charFormat,charFormatMask,0,4);
	//
	TheText->ApplyParagraphStyleL(*(TheStyleList->At(1).iStyle),0,TheText->DocumentLength(),applyMode);
	//
	CParaFormat* paraFormat=CParaFormat::NewLC();
	TheText->GetParagraphFormatL(paraFormat,0);
	test(paraFormat->iHorizontalAlignment=CParaFormat::ERightAlign);
	//
	TheText->GetParagraphFormatL(paraFormat,10);
	test(paraFormat->iHorizontalAlignment=CParaFormat::ERightAlign);
	//
	TheText->GetParagraphFormatL(paraFormat,30);
	test(paraFormat->iHorizontalAlignment=CParaFormat::ERightAlign);
	//
	CleanupStack::PopAndDestroy();  // para format
	//
	/*TEtextComponentInfo info=*/TheText->ComponentInfo();
	CRichText* theCopy=CRichText::NewL(TheNormalParaLayer,TheNormalCharLayer);
	testStoreRestoreL(*theCopy,*TheText);
	test(IsEqual(theCopy,TheText));
	//
	theCopy->ApplyParagraphStyleL(*(TheStyleList->At(0).iStyle),25,1,applyMode);
	CParagraphStyle* tempStyle = CParagraphStyle::NewL(*TheNormalParaLayer,*TheNormalCharLayer);
	theCopy->InsertL(28,delimiter);
	theCopy->InsertL(31,delimiter);
	charFormat.iFontSpec.iFontStyle.SetStrokeWeight(EStrokeWeightBold);
	charFormatMask.ClearAll();
	charFormatMask.SetAttrib(EAttFontStrokeWeight);
	theCopy->ApplyCharFormatL(charFormat, charFormatMask, 33, 1);
	theCopy->NotifyStyleChangedL(tempStyle, TheStyleList->At(0).iStyle);
	//
	delete theCopy;
	delete tempStyle;
	KillEnvironment();
	}


LOCAL_C void TestStyles()
// Perform tests
//
	{
	TestSharedPara();
	TestNonSharedPara();
	}

LOCAL_C void TestStyleWithNullParaFormat()
	{
	TestParaWithNullParaFormat();
	}

LOCAL_C void TestStyleList()
	{
	__UHEAP_MARK;
	// Test 1
	// Construction under OOM
	test.Start(_L("Construction under OOM"));
	CStyleList* list=NULL;
	TInt nn;
	for (nn=0; ;nn++)
		{
		__UHEAP_RESET;
		__UHEAP_SETFAIL(RHeap::EDeterministic,nn);
		__UHEAP_MARK;
		TRAPD(ret,
			list=CStyleList::NewL());
		if (ret!=KErrNone)
			{
			__UHEAP_MARKEND;
			test(list==NULL);
			}
		else
			{
			test(list!=NULL);
			delete list;
			list=NULL;
			__UHEAP_MARKEND;
			break;
			}
		}
	__UHEAP_RESET;
	TBuf<36> answer;
	answer.Format(_L("        #allocs for full c'tion: %d\n"),nn-1);
	test.Printf(answer);


	// Test 2
	// Populated style list, Append under OOM;
	test.Next(_L("AppendL() under OOM"));
	CParaFormatLayer* paraLayer=CParaFormatLayer::NewL();
	CCharFormatLayer* charLayer=CCharFormatLayer::NewL();
	__UHEAP_MARK;
	list=CStyleList::NewL();
	CParagraphStyle* style=NULL;
	for (TInt mm=0;mm<KMaxStyleListGranularity;mm++)
		{
		style=CParagraphStyle::NewL(*paraLayer,*charLayer);
		RParagraphStyleInfo info(style,NULL);
		TInt r=list->AppendL(&info);
		test(r==KErrNone);
		}
	test(list->Count()==KMaxStyleListGranularity);
	
	for (TInt oo=0; ;oo++)
		{
		style=CParagraphStyle::NewL(*paraLayer,*charLayer);
		RParagraphStyleInfo info(style);
		__UHEAP_RESET;
		__UHEAP_SETFAIL(RHeap::EDeterministic,oo);
		TInt r=KErrNone;
		TRAPD(ret,
				r=list->AppendL(&info));
		if (ret!=KErrNone)
			{
			test(r!=KErrAlreadyExists);
			test(list->Count()==KMaxStyleListGranularity);
			}
		else
			{
			test(r==KErrNone);
			test(list->Count()==KMaxStyleListGranularity+1);
			break;
			}
		__UHEAP_RESET;
		}
	delete list;
	list=NULL;
	style=NULL;
	__UHEAP_MARKEND;
	__UHEAP_RESET;


	// Test 3
	// Inserting a duplicate
	test.Next(_L("AppendL() a duplicate"));
	list=CStyleList::NewL();
	style=NULL;
	for (TInt pp=0;pp<KMaxStyleListGranularity;pp++)
		{
		style=CParagraphStyle::NewL(*paraLayer,*charLayer);
		RParagraphStyleInfo info(style,NULL);
		list->AppendL(&info);
		}
	test(list->Count()==KMaxStyleListGranularity);
	RParagraphStyleInfo info=list->At(0);
	TInt r=list->AppendL(&info);
	test(r==KErrAlreadyExists);
	test(list->Count()==KMaxStyleListGranularity);
	test(info.iStyle->CharFormatLayer()!=NULL);  // the duplicate style has not been deleted.
	delete list;


	// Test 4
	// Looking for a style by name that does not exist.
	test.Next(_L("IndexByName() where style not present"));
	list=CStyleList::NewL();
	style=NULL;
	TUint name='A';
	for (TInt qq=0;qq<KMaxStyleListGranularity;qq++)
		{
		style=CParagraphStyle::NewL(*paraLayer,*charLayer);
		style->iName.Append(name);
		name++;
		RParagraphStyleInfo info(style,NULL);
		list->AppendL(&info);
		}
	test(list->Count()==KMaxStyleListGranularity);
	TParagraphStyleName search=_L("not present");
	/*TInt index=*/list->IndexByName(search);

	delete list;

	delete paraLayer;
	delete charLayer;

	__UHEAP_MARKEND;
	test.End();
	}


LOCAL_C void TestHarness()
// Test rich text style usage.
//
    {
	test.Start(_L(" @SYMTestCaseID:SYSLIB-TTEXT-LEGACY-T_STYLE-0001 RichText Styles "));
	// Do the tests.
	TestConstruction();
	TestStyles();
	TestStyleWithNullParaFormat();
	test.Next(_L("CStyleList"));
	TestStyleList();
	//
    }


LOCAL_C void setupCleanup()
//
// Initialise the cleanup stack.
//
    {

	TheTrapCleanup=CTrapCleanup::New();
	TRAPD(r,\
		{\
		for (TInt i=KTestCleanupStack;i>0;i--)\
			CleanupStack::PushL((TAny*)1);\
		test(r==KErrNone);\
		CleanupStack::Pop(KTestCleanupStack);\
		});
	}


LOCAL_C void DeleteDataFile(const TDesC& aFullName)
	{
	RFs fsSession;
	TInt err = fsSession.Connect();
	if(err == KErrNone)
		{
		TEntry entry;
		if(fsSession.Entry(aFullName, entry) == KErrNone)
			{
			RDebug::Print(_L("Deleting \"%S\" file.\n"), &aFullName);
			err = fsSession.SetAtt(aFullName, 0, KEntryAttReadOnly);
			if(err != KErrNone) 
				{
				RDebug::Print(_L("Error %d changing \"%S\" file attributes.\n"), err, &aFullName);
				}
			err = fsSession.Delete(aFullName);
			if(err != KErrNone) 
				{
				RDebug::Print(_L("Error %d deleting \"%S\" file.\n"), err, &aFullName);
				}
			}
		fsSession.Close();
		}
	else
		{
		RDebug::Print(_L("Error %d connecting file session. File: %S.\n"), err, &aFullName);
		}
	}

GLDEF_C TInt E32Main()
//
// Test the streaming framework.
//
    {

	test.Title();	
	__UHEAP_MARK;
	setupCleanup();
	TRAPD(r,TestHarness());
    test(r == KErrNone);

	delete TheTrapCleanup;
	
	__UHEAP_MARKEND;
	
	::DeleteDataFile(KOutputFile);		//deletion of data files must be before call to End() - DEF047652
	
	test.End();
	test.Close();

	return 0;
    }
