/*
* Copyright (c) 1999-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 "TmText.h"
#include <txtfrmat.h>
#include <txtetext.h>
#include "TMSTD.H"

/**
 * Creates a CTmText object using the specified device map and formatting parameters.
 */
EXPORT_C CTmText* CTmText::NewL(MGraphicsDeviceMap& aDevice,const TTmFormatParamBase& aFormatParam)
	{
	CTmText* t = new(ELeave) CTmText;
	CleanupStack::PushL(t);
	t->iImp = new(ELeave) CTmTextImp(aDevice,aFormatParam);
	CleanupStack::Pop();
	return t;
	}

/**
 * Creates a CTmText object using the specified device map and formatting
 * parameters that have default values (as in the default constructor for
 * TTmFormatParamBase) apart from the wrap width and flags. The values of the
 * flags are those used in for TTmFormatParamBase::iFlags.
 */
EXPORT_C CTmText* CTmText::NewL(MGraphicsDeviceMap& aDevice,TInt aWrapWidth,TInt aFlags)
	{
	TTmFormatParamBase param;
	param.iWrapWidth = aWrapWidth;
	param.iFlags = aFlags;
	return NewL(aDevice,param);
	}

CTmText::CTmText()
	{
	}
/**
 * Destroys a CTmText object.
 */
EXPORT_C CTmText::~CTmText()
	{
	delete iImp;
	}

/**
 * Inserts the text aText at the document position aPos. The other arguments
 * are all pointers which have default values of null. The first two,
 * aCharFormat and aParFormat, specify the format for the inserted text. If
 * either is null the format immediately before the insertion point is used.
 *
 * Put the coordinates of the area that has been reformatted and needs to be
 * redrawn, relative to the origin of the CText object, in aRedrawRect. Put the
 * amount by which the text after the redrawn text needs to be scrolled up or
 * down in aScroll: this is the amount by which the reformatted text has
 * changed in height, and thus is positive if the text after the redrawn text
 * has increased in height, which is usually, but not always, the case.
 */
EXPORT_C void CTmText::InsertL(TInt aPos,const TDesC& aText,
	const TTmCharFormat* aCharFormat,const RTmParFormat* aParFormat,
	TRect* aRedrawRect,TInt* aScroll)
	{
	iImp->InsertL(aPos,aText,aCharFormat,aParFormat,aRedrawRect,aScroll);
	}

/**
 * Retrieves the current formatting parameters.
 */
EXPORT_C void CTmText::GetFormat(TTmFormatParamBase& aFormatParam) const
	{
	iImp->GetFormat(aFormatParam);
	}

/**
 * Sets the wrap width and reformat the text.
 */
EXPORT_C void CTmText::SetWrapWidthL(TInt aWrapWidth)
	{
	iImp->SetWrapWidthL(aWrapWidth);
	}

/**
 * Set the formatting parameters and reformat the text.
 */
EXPORT_C void CTmText::ChangeFormatL(const TTmFormatParamBase& aFormatParam)
	{
	iImp->ChangeFormatL(aFormatParam);
	}

/**
 * Deletes all the text.
 */
EXPORT_C void CTmText::Clear()
	{
	iImp->Clear();
	}

/**
 * Specifies custom drawing or layout for the object, or if aCustom is NULL,
 * removes any customization. Reformats the object according to the new
 * customized layout. See the MTmCustom class for details. Custom drawing
 * allows background graphics to be drawn, and custom layout allows, among
 * other things, control over the way line height is calculated and special
 * characters are displayed.
 */
EXPORT_C void CTmText::CustomizeL(const MTmCustom* aCustom)
	{
	iImp->CustomizeL(aCustom);
	}

/**
 * Returns the number of bytes of memory used by a CTmText object, allowing 8
 * bytes overhead per heap allocation.
*/
EXPORT_C TInt CTmText::MemoryUsed() const
	{
	return sizeof(*this) + iImp->MemoryUsed() + 8;
	}

/**
 * Returns a const reference to the CTmTextLayout object owned by this object.
 * This function implements MTmTextLayoutForwarder::TextLayout.
 */
const CTmTextLayout& CTmText::TextLayout() const
	{
	return iImp->TextLayout();
	}

template<class T> TInt RRefCountedArray<T>::Insert(const T& aEntry,TInt& aIndex)
	{
	//+ replace this linear search with a binary search
	int n = iArray.Count();
	for (int i = 0; i < n; i++)
		if (aEntry == iArray[i].iEntry)
			{
			iArray[i].iRefCount++;
			aIndex = i;
			return KErrNone;
			}
	aIndex = n;
	TCountedEntry new_entry;
	new_entry.iRefCount = 1;
	int error = iArray.Append(new_entry);
	if (!error)
		error = iArray[iArray.Count() - 1].iEntry.Copy(aEntry);
	return error;
	}

template<class T> void RRefCountedArray<T>::Remove(TInt aIndex)
	{
	if (--iArray[aIndex].iRefCount == 0)
		iArray.Remove(aIndex);
	}

template<class T> TInt RRefCountedArray<T>::MemoryUsed() const
	{
	TInt bytes = sizeof(*this);
	if (iArray.Count())
		bytes += User::AllocLen(&iArray[0]) + 8;
	return bytes;
	} 

CTmTextImp::~CTmTextImp()
	{
	iCharFormatRun.Close();
	iParFormatRun.Close();
	iCharFormat.Close();
	for (int i = 0; i < iParFormat.Count(); i++)
		iParFormat[i].Close();
	iParFormat.Close();
	}

void CTmTextImp::InsertL(TInt aPos,const TDesC& aText,
						 const TTmCharFormat* aCharFormat,const RTmParFormat* aParFormat,
						 TRect* aRedrawRect,TInt* aScroll)
	{
	__ASSERT_DEBUG(0 <= aPos, TmPanic(EBadArg));

	// Don't insert zero-length text.
	if (aText.Length() == 0)
		return;

	// Clamp aPos to the end of the document.
	if (iTextLayout.EndChar() <= aPos)
		aPos = Max(0,iTextLayout.EndChar() - 1);

	// If the document is empty and character or paragraph format is unspecified, use a default format.
	TTmCharFormat default_char_format;
	default_char_format.iFontSpec.SetHeight(12 * 20);	// set height, which must be in twips, to 12pt
	RTmParFormat default_par_format;
	if (iText.Length() == 0)
		{
		if (aCharFormat == NULL)
			aCharFormat = &default_char_format;
		if (aParFormat == NULL)
			aParFormat = &default_par_format;
		}

	// Insert the character format.
	int error = 0;
	int char_format_index = -1;
	if (aCharFormat)
		error = iCharFormat.Insert((TMyCharFormat&)*aCharFormat,char_format_index);
	
	// Insert the paragraph format.
	int par_format_index = -1;
	if (!error && aParFormat)
		error = iParFormat.Insert((RMyParFormat&)*aParFormat,par_format_index);

	// Insert the character format run.
	if (!error)
		error = iCharFormatRun.Insert(aPos,aText.Length(),char_format_index);

	// If the paragraph format has changed extend it to the paragraph bounds.
	int old_par_format_index = -1;
	if (!error && iText.Length() > 0)
		{
		if (par_format_index != -1)
			old_par_format_index = iParFormatRun.Index(aPos);
		if (par_format_index != old_par_format_index)
			{
			int par_start = ParagraphStart(aPos);
			int par_end = ParagraphEnd(aPos);
			if (iText.Length() == par_end - 1)
				--par_end;
			error = iParFormatRun.Set(par_start, par_end - par_start,
				par_format_index);
			}
		}

	// Insert the paragraph format run.
	if (!error)
		error = iParFormatRun.Insert(aPos,aText.Length(),par_format_index);

	// Insert the text.
	if (!error)
		{
		TRAP(error,iText.InsertL(aPos,aText));
		}

	default_par_format.Close();

	User::LeaveIfError(error);

	if (iTextLayout.EndChar() > 0)
		{
		TTmReformatParam reformat_param;
		reformat_param.iStartChar = aPos;
		reformat_param.iOldLength = 0;
		reformat_param.iNewLength = aText.Length();
		reformat_param.iParFormatChanged = par_format_index != old_par_format_index;
		TTmReformatResult result;
		iTextLayout.FormatL(iFormatParam,reformat_param,result);
		if (aRedrawRect)
			*aRedrawRect = result.iRedrawRect;
		if (aScroll)
			*aScroll = result.iHeightChange;
		}
	else
		{
		iTextLayout.SetTextL(*this,iFormatParam);
		if (aRedrawRect)
			*aRedrawRect = TRect(0,0,iTextLayout.LayoutWidth(),iTextLayout.LayoutHeight());
		if (aScroll)
			*aScroll = 0;
		}
	}

void CTmTextImp::ChangeFormatL(const TTmFormatParamBase& aFormatParam)
	{
	iFormatParam = aFormatParam;
	ReformatL();
	}

void CTmTextImp::ReformatL()
	{
	if (iText.Length() > 0)
		iTextLayout.SetTextL(*this,iFormatParam);
	}

void CTmTextImp::Clear()
	{
	iText.Reset();
	iCharFormatRun.Reset();
	iParFormatRun.Reset();
	iCharFormat.Reset();
	for (int i = 0; i < iParFormat.Count(); i++)
		iParFormat[i].Close();
	iParFormat.Reset();
	iTextLayout.Clear();
	}

void CTmTextImp::CustomizeL(const MTmCustom* aCustom)
	{
	iCustom = aCustom;
	ReformatL();
	}

TInt CTmTextImp::MemoryUsed() const
	{
	return sizeof(*this) +
		   iText.MemoryUsed() - sizeof(iText) +
		   iCharFormatRun.MemoryUsed() - sizeof(iCharFormatRun) +
		   iParFormatRun.MemoryUsed() - sizeof(iParFormatRun) +
		   iCharFormat.MemoryUsed() - sizeof(iCharFormat) +
		   iParFormat.MemoryUsed() - sizeof(iParFormat) +
		   iTextLayout.MemoryUsed() - sizeof(iTextLayout);
	}

MGraphicsDeviceMap& CTmTextImp::FormatDevice() const
	{
	return iDevice;
	}

MGraphicsDeviceMap& CTmTextImp::InterpretDevice() const
	{
	return iDevice;
	}

TInt CTmTextImp::DocumentLength() const
	{
	return iText.Length();
	}

void CTmTextImp::GetText(TInt aPos,TPtrC& aText,TTmCharFormat& aFormat) const
	{
	static const TText end_par = CEditableText::EParagraphDelimiter;
	TInt index = 0;
	TInt textLength = iText.Length();
	__ASSERT_DEBUG(aPos <= textLength + 1, TmPanic(EIndexOutOfRange));
	if (aPos < textLength)
		{
		TPtrC p = iText.PtrC(aPos);
		TInt pLength = p.Length();
		TRun charRun;
		TInt charRunStart = iCharFormatRun.RunAndStartPos(aPos, charRun);
		TInt remainingLength = charRunStart + charRun.iLength - aPos;
		aText.Set(p.Ptr(), remainingLength < pLength? remainingLength : pLength);
		index = charRun.iIndex;
		}
	else if (aPos == textLength)
		{
		aText.Set(&end_par,1);
		if (0 < aPos)
			index = iCharFormatRun.Index(aPos - 1);
		else
 			{
			TTmCharFormat d;
			aFormat = d;
			return;
			}
		}
	else
		{
		aText.Set(0, 0);
		return;
		}
	aFormat = iCharFormat[index];
	}

void CTmTextImp::GetParagraphFormatL(TInt aPos,RTmParFormat& aFormat) const
	{
	__ASSERT_DEBUG(0 <= aPos && aPos <= iText.Length(), TmPanic(EBadArg));
	TInt textLength = iText.Length();
	if (0 == textLength)
		{
		RTmParFormat d;
		CleanupClosePushL(d);
		aFormat.CopyL(d);
		CleanupStack::PopAndDestroy(&d);
		return;
		}
	if (aPos != textLength)
		++aPos;
	aFormat.CopyL(iParFormat[iParFormatRun.Index(aPos)]);
	}

TInt CTmTextImp::ParagraphStart(TInt aPos) const
	{
	while (aPos > 0)
		{
		TPtrC text = iText.BackPtrC(aPos);
		const TText *p = text.Ptr();
		const TText *q = p + text.Length();
		while (p < q)
			{
			q--;
			aPos--;
			if (*q == CEditableText::EParagraphDelimiter)
				return aPos + 1;
			}
		}
	return 0;
	}

TRgb CTmTextImp::SystemColor(TUint aColorIndex,TRgb aDefaultColor) const
	{
	if (iCustom)
		return iCustom->SystemColor(aColorIndex,aDefaultColor);
	else
		return MTmSource::SystemColor(aColorIndex,aDefaultColor);
	}

TInt CTmTextImp::Stretch(TUint aChar) const
	{
	if (iCustom)
		return iCustom->Stretch(aChar);
	else
		return MTmSource::Stretch(aChar);
	}

TUint CTmTextImp::Map(TUint aChar) const
	{
	if (iCustom)
		return iCustom->Map(aChar);
	else
		return MTmSource::Map(aChar);
	}

void CTmTextImp::SetLineHeight(const TLineHeightParam& aParam,TInt& aAscent,TInt& aDescent) const
	{
	if (iCustom)
		iCustom->SetLineHeight(aParam,aAscent,aDescent);
	else
		MTmSource::SetLineHeight(aParam,aAscent,aDescent);
	}

void CTmTextImp::DrawBackground(CGraphicsContext& aGc,const TPoint& aTextLayoutTopLeft,const TRect& aRect,
								const TLogicalRgb& aBackground,TRect& aRectDrawn) const
	{
	if (iCustom)
		iCustom->DrawBackground(aGc,aTextLayoutTopLeft,aRect,aBackground,aRectDrawn);
	else
		MTmSource::DrawBackground(aGc,aTextLayoutTopLeft,aRect,aBackground,aRectDrawn);
	}

void CTmTextImp::DrawLineGraphics(CGraphicsContext& aGc,const TPoint& aTextLayoutTopLeft,const TRect& aRect,
								  const TTmLineInfo& aLineInfo) const
	{
	if (iCustom)
		iCustom->DrawLineGraphics(aGc,aTextLayoutTopLeft,aRect,aLineInfo);
	else
		MTmSource::DrawLineGraphics(aGc,aTextLayoutTopLeft,aRect,aLineInfo);
	}

void CTmTextImp::DrawText(CGraphicsContext& aGc,const TPoint& aTextLayoutTopLeft,const TRect& aRect,
						  const TTmLineInfo& aLineInfo,const TTmCharFormat& aFormat,
						  const TDesC& aText,const TPoint& aTextOrigin,TInt aExtraPixels) const
	{
	if (iCustom)
		iCustom->DrawText(aGc,aTextLayoutTopLeft,aRect,aLineInfo,aFormat,aText,aTextOrigin,aExtraPixels);
	else
		MTmSource::DrawText(aGc,aTextLayoutTopLeft,aRect,aLineInfo,aFormat,aText,aTextOrigin,aExtraPixels);
	}

TInt CTmTextImp::RRunArray::SplitRun(TInt aRunIndex,TInt aOffset)
	{
	TRun& run = iRun[aRunIndex];
	TRun new_run;
	new_run.iIndex = run.iIndex;
	new_run.iLength = run.iLength - aOffset;
	int error = iRun.Insert(new_run, aRunIndex + 1);
	if (!error)
		run.iLength = aOffset;
	return error;
	}

/*
Find the run containing aPos. The position at the end of a run is in the run.
The position at the start of a run is NOT in the run, but in the preceding run, if any.
*/
void CTmTextImp::RRunArray::FindRun(TInt aPos,TInt& aRunIndex,TInt& aOffset) const
	{
#ifdef _DEBUG
	TInt error =
#endif
	FindRunNonstrict(aPos, aRunIndex, aOffset);
	__ASSERT_DEBUG(!error, TmPanic(ETextRunNotFound));
	}

TInt CTmTextImp::RRunArray::FindRunNonstrict(TInt aPos,TInt& aRunIndex,TInt& aOffset) const
	{
	int n = iRun.Count();
	int start = 0;
	int end = 0;
	for (int i = 0; i < n; i++)
		{
		__ASSERT_DEBUG(0 <= iRun[i].iLength, TmPanic(EInvalidTextRunLength));
		__ASSERT_DEBUG(0 <= iRun[i].iIndex, TmPanic(EInvalidTextRunIndex));
		end += iRun[i].iLength;
		if (end >= aPos)
			{
			aRunIndex = i;
			aOffset = aPos - start;
			return KErrNone;
			}
		start = end;
		}

	aRunIndex = aOffset = -1;
	return KErrNotFound;
	}

/* Find the latest run that starts less than or equal to aPos. Return it
 * in aRunOut, and return its start position.
 */
TInt CTmTextImp::RRunArray::RunAndStartPos(TInt aPos, TRun& aRunOut) const
	{
	TInt runs = iRun.Count();
	__ASSERT_DEBUG(0 < runs, TmPanic(ETextRunNotFound));
	TInt start = 0;
	TInt end = 0;
	for (TInt i = 0; i != runs && end <= aPos; ++i)
		{
		aRunOut = iRun[i];
		start = end;
		end = start + aRunOut.iLength;
		}
	return start;
	}

// Insert a run of length aLength and attribute aIndex at aPos; if aIndex is < 0, use the index at aPos.
TInt CTmTextImp::RRunArray::Insert(TInt aPos,TInt aLength,TInt aIndex)
	{
	__ASSERT_DEBUG(0 < aLength, TmPanic(EInvalidTextRunLength));
	__ASSERT_DEBUG(0 <= aIndex || 0 < iRun.Count(), TmPanic(EParagraphFormatRequired));

	int error = 0;
	TRun new_run;
	if (iRun.Count() == 0)
		{
		new_run.iLength = 0;
		new_run.iIndex = aIndex;
		error = iRun.Append(new_run);
		if (error)
			return error;
		}

	// Find the run; see if the index matches; if on a join between runs, check both.
	int run_index, offset;
	FindRun(aPos,run_index,offset);
	if (offset == iRun[run_index].iLength && iRun[run_index].iIndex != aIndex && run_index < iRun.Count() - 1)
		{
		run_index++;
		offset = 0;
		}
	TRun& run = iRun[run_index];

	__ASSERT_DEBUG(0 <= offset && offset <= run.iLength, TmPanic(EInvariant));

	// If aIndex is negative, it means don't change the attribute.
	if (aIndex < 0)
		aIndex = run.iIndex;

	// No need to create a new run; just extend the existing one
	if (run.iIndex == aIndex)
		run.iLength += aLength;

	// Split the run if necessary, then add a new run.
	else
		{
		if (run.iLength == offset)
			++run_index;
		else if (offset != 0)
			{
			error = SplitRun(run_index,offset);
			run_index++;
			}
		if (!error)
			{
			new_run.iIndex = aIndex;
			new_run.iLength = aLength;
			error = iRun.Insert(new_run, run_index);
			}
		}

	return error;
	}

TInt CTmTextImp::RRunArray::Set(TInt aPos,TInt aLength,TInt aIndex)
	{
	__ASSERT_DEBUG(0 <= aLength, TmPanic(EInvalidTextRunLength));
	__ASSERT_DEBUG(0 <= aIndex, TmPanic(EInvalidTextRunIndex));

	if (aLength == 0)
		return KErrNone;

	// Find the run start.
	int start_run_index, start_run_offset;
	FindRun(aPos,start_run_index,start_run_offset);

	// Adjust the run start if there is no change to the attribute; return if this eliminates the range to be set.
	if (iRun[start_run_index].iIndex == aIndex)
		{
		int n = iRun[start_run_index].iLength - start_run_offset;
		aPos += n;
		aLength -= n;
		if (aLength <= 0)
			return KErrNone;
		}

	// Find the run end.
	int end_run_index, end_run_offset;
	FindRun(aPos + aLength,end_run_index,end_run_offset);

	// Adjust the run end if there is no change to the attribute; return if this eliminates the range to be set.
	if (iRun[end_run_index].iIndex == aIndex)
		{
		aLength -= end_run_offset;
		if (aLength <= 0)
			return KErrNone;
		}

	// Determine the change in the number of runs. The maximum increase is 2.
	int runs_increase = start_run_index - end_run_index + 2;

	// Insert extra runs if needed.
	int error = KErrNone;
	if (runs_increase > 0)
		{
		error = SplitRun(start_run_index,start_run_offset);
		runs_increase--;
		if (start_run_index == end_run_index)
			end_run_offset -= start_run_offset;
		end_run_index++;
		}
	if (!error && runs_increase > 0)
		{
		error = SplitRun(end_run_index,end_run_offset);
		end_run_index++;
		end_run_offset = 0;
		runs_increase--;
		}
	if (!error)
		{
		// Delete unneeded runs.
		while (runs_increase < 0)
			{
			iRun.Remove(start_run_index + 2);
			end_run_index--;
			++runs_increase;
			}

		// Adjust the size of the start run.
		iRun[start_run_index].iLength = start_run_offset;

		// Set the run after the start run to the new attribute.
		TRun& run = iRun[start_run_index + 1];
		run.iLength = aLength;
		run.iIndex = aIndex;

		// Adjust the size of the end run.
		iRun[end_run_index].iLength -= end_run_offset;
		}

	return error;
	}

void CTmTextImp::RRunArray::Delete(TInt aPos,TInt aLength)
	{
	__ASSERT_DEBUG(0 <= aLength, TmPanic(EInvalidTextRunLength));

	if (aLength == 0)
		return;

	// Find the start.
	int start_run_index, start_run_offset;
	FindRun(aPos,start_run_index,start_run_offset);

	// Find the end.
	int end_run_index, end_run_offset;
	FindRun(aPos + aLength,end_run_index,end_run_offset);

	// If the runs are the same it's trivial.
	if (start_run_index == end_run_index)
		{
		iRun[start_run_index].iLength -= (end_run_offset - start_run_offset);
		return;
		}

	// Shorten the start and end runs and determine the range of runs to delete.
	iRun[start_run_index].iLength = start_run_offset;
	int first_deleted_run = start_run_index + 1;
	if (start_run_offset == 0)
		first_deleted_run--;
	iRun[end_run_index].iLength -= end_run_offset;
	int last_deleted_run = end_run_index - 1;
	if (iRun[end_run_index].iLength == 0)
		last_deleted_run++;

	// Delete runs.
	for (int i = first_deleted_run; i <= last_deleted_run; i++)
		iRun.Remove(first_deleted_run);
	}

TInt CTmTextImp::RRunArray::Index(TInt aPos) const
	{
	int run_index, offset;
	TInt error = FindRunNonstrict(aPos,run_index,offset);
	return error? error : iRun[run_index].iIndex;
	}

TInt CTmTextImp::RRunArray::MemoryUsed() const
	{
	TInt bytes = sizeof(*this);
	if (iRun.Count())
		bytes += User::AllocLen(&iRun[0]) + 8;
	return bytes;
	} 

EXPORT_C void CTmText::Spare1()
	{
	TmPanic(EUnimplemented);
	}
