// UB_BUF.CPP
//
// Copyright (c) 1994-1999 Symbian Ltd.  All rights reserved.
//

#include "ub_std.h"

class TBufSegLink : public TDblQueLink
	{
public:
	inline TBufSegLink() : iLen(0) {}
	inline TBufSegLink *Next() const {return((TBufSegLink *)iNext);}
	inline TBufSegLink *Prev() const {return((TBufSegLink *)iPrev);}
public:
	TInt iLen;
	};

EXPORT_C CBufBase::CBufBase(TInt anExpandSize)
//
// Constructor
//
	{

	__ASSERT_ALWAYS(anExpandSize>=0,Panic(EBufExpandSizeNegative));
//	iSize=0;
	iExpandSize=anExpandSize;
	__DECLARE_NAME(_S("CBufBase"));
	}

EXPORT_C CBufBase::~CBufBase()
//
// Destructor
//
	{
	}

EXPORT_C void CBufBase::Reset()
//
// Clear the whole buffer.
//
	{

	if (iSize)
		Delete(0,iSize);
	Compress();
	}

EXPORT_C void CBufBase::Read(TInt aPos,TDes8 &aDes) const
//
// Read up to aDes.MaxLength() bytes.
//
	{

	Read(aPos,aDes,aDes.MaxLength());
	}

EXPORT_C void CBufBase::Read(TInt aPos,TDes8 &aDes,TInt aLength) const
//
// Read from the buffer.
//
	{

	aDes.SetLength(aLength);
	Read(aPos,(TAny *)aDes.Ptr(),aLength);
	}

EXPORT_C void CBufBase::Read(TInt aPos,TAny *aPtr,TInt aLength) const
//
// Read from the buffer.
//
	{

	if (aLength==0)
		return;
	__ASSERT_ALWAYS(aLength>0,Panic(EBufReadLengthNegative));
	__ASSERT_ALWAYS((aPos+aLength)<=iSize,Panic(EBufReadBeyondEnd));
	TUint8 *pT=(TUint8 *)aPtr;
	while (aLength)
		{
		TPtr8 p=((CBufBase *)this)->Ptr(aPos);
		TInt s=Min(p.Length(),aLength);
		pT=Mem::Copy(pT,p.Ptr(),s);
		aLength-=s;
		aPos+=s;
		}
	}

EXPORT_C void CBufBase::Write(TInt aPos,const TDesC8 &aDes)
//
// Write aDes.Length() characters to the buffer. Does not cause any expansion.
//
	{

	Write(aPos,aDes.Ptr(),aDes.Length());
	}

EXPORT_C void CBufBase::Write(TInt aPos,const TDesC8 &aDes,TInt aLength)
//
// Write aDes.Length() characters to the buffer. Does not cause any expansion.
//
	{

	Write(aPos,aDes.Ptr(),aLength);
	}

EXPORT_C void CBufBase::Write(TInt aPos,const TAny *aPtr,TInt aLength)
//
// Write to the buffer. Does not cause any expansion.
//
	{

	if (aLength==0)
		return;
	__ASSERT_ALWAYS(aLength>0,Panic(EBufWriteLengthNegative));
	__ASSERT_ALWAYS((aPos+aLength)<=iSize,Panic(EBufWriteBeyondEnd));
	const TUint8 *pS=(const TUint8 *)aPtr;
	while (aLength)
		{
		TPtr8 p=Ptr(aPos);
		TInt s=Min(p.Length(),aLength);
		Mem::Copy((TAny *)p.Ptr(),pS,s);
		pS+=s;
		aLength-=s;
		aPos+=s;
		}
	}

EXPORT_C void CBufBase::InsertL(TInt aPos,const TDesC8 &aDes)
//
// Insert aDes.Length() bytes into the buffer.
//
	{

	InsertL(aPos,aDes.Ptr(),aDes.Length());
	}

EXPORT_C void CBufBase::InsertL(TInt aPos,const TDesC8 &aDes,TInt aLength)
//
// Insert aLength bytes into the buffer.
//
	{

	InsertL(aPos,aDes.Ptr(),aLength);
	}

EXPORT_C void CBufBase::InsertL(TInt aPos,const TAny *aPtr,TInt aLength)
//
// Insert aLength bytes into the buffer.
//
	{

	if (aLength==0)
		return;
	__ASSERT_ALWAYS(aLength>0,Panic(EBufInsertLengthNegative));
	__ASSERT_ALWAYS(aPtr,Panic(EBufInsertBadPtr));
	DoInsertL(aPos,aPtr,aLength);
	}

EXPORT_C void CBufBase::ExpandL(TInt aPos,TInt aLength)
//
// Expand the buffer at aPos by aLength bytes.
//
	{

	if (aLength==0)
		return;
	__ASSERT_ALWAYS(aLength>0,Panic(EBufInsertLengthNegative));
	DoInsertL(aPos,NULL,aLength);
	}

EXPORT_C void CBufBase::ResizeL(TInt aSize)
//
// Resize the buffer.
//
	{

	TInt excess=iSize-aSize;
	if (excess>0)
		Delete(aSize,excess);
	else
		ExpandL(iSize,-excess);
	}

EXPORT_C CBufFlat *CBufFlat::NewL(TInt anExpandSize)
//
// Create a new instance of this class.
//
	{

	return(new(ELeave) CBufFlat(anExpandSize));
	}

EXPORT_C CBufFlat::CBufFlat(TInt anExpandSize)
//
// Constructor
//
	: CBufBase(anExpandSize)
	{

//	iMaxSize=0;
//	iPtr=NULL;
	__DECLARE_NAME(_S("CBufFlat"));
	}

EXPORT_C CBufFlat::~CBufFlat()
//
// Destructor
//
	{

	User::Free(iPtr);
	}

EXPORT_C void CBufFlat::Compress()
//
// Compress the buffer.
//
	{

	SetReserveL(iSize);
	}

EXPORT_C void CBufFlat::SetReserveL(TInt aSize)
//
// Set the SetReserve of this buffer.
//
	{

	__ASSERT_ALWAYS(aSize>=0,Panic(EBufFlatReserveNegative));
	__ASSERT_ALWAYS(aSize>=iSize,Panic(EBufFlatReserveSetTooSmall));
    if (!aSize)
        {
        User::Free(iPtr);
        iPtr=NULL;
        }
    else
        iPtr=(TUint8 *)User::ReAllocL(iPtr,aSize);
    iMaxSize=aSize;
	}

EXPORT_C void CBufFlat::DoInsertL(TInt aPos,const TAny *aPtr,TInt aLength)
//
// Insert into the buffer. Can cause expansion.
//
	{

	__ASSERT_ALWAYS(aPos>=0 && aPos<=iSize,Panic(EBufFlatPosOutOfRange));
	TInt len=iSize+aLength;
	if (len>iMaxSize)
		{
		TInt r=len-iMaxSize;
		r=((r/iExpandSize)+1)*iExpandSize;
		SetReserveL(iMaxSize+r);
		}
	Mem::Copy(iPtr+aPos+aLength,iPtr+aPos,iSize-aPos);
	if (aPtr)
		Mem::Copy(iPtr+aPos,aPtr,aLength);
	iSize+=aLength;
	}

EXPORT_C void CBufFlat::Delete(TInt aPos,TInt aLength)
//
// Delete from the buffer.
//
	{

	__ASSERT_ALWAYS(aPos>=0 && aPos<=iSize,Panic(EBufFlatPosOutOfRange));
	__ASSERT_ALWAYS((aPos+aLength)<=iSize,Panic(EBufFlatDeleteBeyondEnd));
	Mem::Copy(iPtr+aPos,iPtr+aPos+aLength,iSize-aLength-aPos);
	iSize-=aLength;
	}

EXPORT_C TPtr8 CBufFlat::Ptr(TInt aPos)
//
// Return a pointer to the buffer, and the amount of data remaining.
//
	{

	__ASSERT_ALWAYS(aPos>=0 && aPos<=iSize,Panic(EBufFlatPosOutOfRange));
	TInt len=iSize-aPos;
	return(TPtr8(iPtr+aPos,len,len));
	}

EXPORT_C TPtr8 CBufFlat::BackPtr(TInt aPos)
//
// Return a pointer to the buffer which has the maximum amount of data
// before aPos, and the amount of data remaining.  
//
	{

	__ASSERT_ALWAYS(aPos>=0 && aPos<=iSize,Panic(EBufFlatPosOutOfRange));
	return(TPtr8(iPtr,aPos,aPos));
	}

void CBufSeg::InsertIntoSegment(TBufSegLink *aSeg,TInt anOffset,const TAny *aPtr,TInt aLength)
//
// Insert into the segment.
//
	{

    if (aLength)
        {
        TUint8 *pS=((TUint8 *)(aSeg+1))+anOffset;
        Mem::Copy(pS+aLength,pS,aSeg->iLen-anOffset);
		if (aPtr)
			Mem::Copy(pS,aPtr,aLength);
        aSeg->iLen+=aLength;
        }
	}

void CBufSeg::DeleteFromSegment(TBufSegLink *aSeg,TInt anOffset,TInt aLength)
//
// Delete from the segment.
//
	{

    if (aLength)
        {
        TUint8 *pS=((TUint8 *)(aSeg+1))+anOffset;
        Mem::Copy(pS,pS+aLength,aSeg->iLen-anOffset-aLength);
        aSeg->iLen-=aLength;
        }
	}

void CBufSeg::FreeSegment(TBufSegLink *aSeg)
//
// Free an entire segment.
//
	{

    aSeg->Deque();
    User::Free(aSeg);
	}

void CBufSeg::SetSBO(TInt aPos)
//
// Set a segment-base-offset struct (SBO) to a new pos.
// If the initial psbo->seg is not NULL, it assumes that it is a valid
// SBO for a different position and counts relative to the initial SBO
// to set the desired position. If the initial psbo->seg is NULL, it starts
// scanning from the beginning ie pos=0.
// When the position is between segments A and B, there are two equivalent
// positions: (1) at the beginning of B and (2) at the end of A.
// Option (1) is suitable for referencing the data and deleting.
// Option (2) is best for insertion when A is not full.
// This function uses option (1) and will always set the SBO to the
// beginning of the next segment. It does however set to the end of the
// last segment when pos is equal to the number of bytes in the buffer.
//
	{

    __ASSERT_ALWAYS(aPos>=0 && aPos<=iSize,Panic(EBufSegPosOutOfRange));
    if (aPos==iSize)
        { // Positioning to end is treated as a special case
        iSeg=0;
        if (iSize)
            iBase=aPos-(iOffset=(iSeg=iQue.Last())->iLen);
        return;
        }
    TInt base=iBase;
	TBufSegLink *next;
    if ((next=iSeg)==NULL)
        { // anSbo is not valid - set to pos=0
        next=iQue.First();
        base=0;
        }
    if (aPos<base)
        { // Look to the left
        do
            {
            next=next->Prev();
            base-=next->iLen;
            } while (aPos<base);
        }
    else
        { // Look to the right
		TBufSegLink *nn;
        while (aPos>=(base+next->iLen) && !iQue.IsHead(nn=next->Next()))
            {
            base+=next->iLen;
            next=nn;
            }
        }
    iSeg=next;
    iBase=base;
    iOffset=aPos-base;
	__ASSERT_DEBUG(iOffset<=iExpandSize,Panic(EBufSegSetSBO));
	}

void CBufSeg::AllocSegL(TBufSegLink *aSeg,TInt aNumber)
//
// Allocate a number of segments.
//
	{

	for (TInt i=0;i<aNumber;i++)
		{
		TBufSegLink *pL=(TBufSegLink *)User::Alloc(sizeof(TBufSegLink)+iExpandSize);
		if (pL==NULL)
			{ // alloc failed - tidy up
			while (i--)
				FreeSegment(aSeg->Next());
			User::Leave(KErrNoMemory);
			}
		new(pL) TBufSegLink;
		pL->Enque(aSeg);
		}
	}

EXPORT_C CBufSeg *CBufSeg::NewL(TInt anExpandSize)
//
// Create a new instance of this class.
//
	{

	return(new(ELeave) CBufSeg(anExpandSize));
	}

EXPORT_C CBufSeg::CBufSeg(TInt anExpandSize)
//
// Constructor
//
	: CBufBase(anExpandSize)
	{

//	iSeg=NULL;
	__DECLARE_NAME(_S("CBufSeg"));
	}

EXPORT_C CBufSeg::~CBufSeg()
//
// Destructor
//
	{

	Delete(0,iSize);
	}

EXPORT_C void CBufSeg::Compress()
//
// Compress the buffer.
//
	{

    if (!iSize)
        return;
    iSeg=NULL; // Invalidate current position
    TBufSegLink *p1=iQue.First();
    TBufSegLink *p2;
    while (!iQue.IsHead(p2=p1->Next()))
        {
        TInt rem=iExpandSize-p1->iLen;
        if (rem==0)
            {
            p1=p2;
            continue; // Full
            }
        if (rem>p2->iLen)
            { // Zap the next segment
            InsertIntoSegment(p1,p1->iLen,p2+1,p2->iLen);
            FreeSegment(p2);
            continue;
            }
        InsertIntoSegment(p1,p1->iLen,p2+1,rem);  // Make full
        DeleteFromSegment(p2,0,rem);
        p1=p2;
        }
	}

EXPORT_C void CBufSeg::DoInsertL(TInt aPos,const TAny *aPtr,TInt aLength)
//
// Insert data at the specified position. This is quite tricky.
// In general, the data to be copied may be broken down into the
// following elements:
//     s1 bytes into the current segment (p1)
//     nseg-1 segments of self->sgbuf.hd.len (ie full segments)
//     s2 bytes into segment nseg
//     s3 bytes into the next segment (p2)
// where p2 is the next segment before the insertion of nseg new segments.
// In addition, any remaining data to the right of the insertion point must
// be moved appropriately. In general, r1 bytes must be moved into segment
// nseg (r2 bytes) and segment p2 (r3 bytes) where r1=r2+r3.
//
	{

    SetSBO(aPos);
    TInt slen=iExpandSize;
    TInt ofs=iOffset;	
	TInt ll=0;
    TInt s1=0;
    TInt r1=0;
    TBufSegLink *p1=(TBufSegLink *)(&iQue); 
    TBufSegLink *p2=p1->Next(); 
	TUint8 *pR=NULL;
    if (iSize)	
        {
        p1=iSeg;	
     	if (!iQue.IsHead(p2=p1->Prev()) && ofs==0 && p2->iLen<slen)
        	{  
        	iSeg=p1=p2;     
        	iOffset=ofs=p1->iLen;
        	iBase-=ofs;     
        	}
        s1=slen-ofs; 
        if (s1>aLength)
            s1=aLength; 
		TInt r2=slen-p1->iLen; 
        if (aLength>r2)	
            { 
            pR=((TUint8 *)(p1+1))+ofs; 
            r1=aLength-r2; 
			r2=p1->iLen-ofs; 
            if (r1>r2) 
                r1=r2; 
            else
                pR+=(r2-r1); 
            }
		p2=p1->Next();
        ll=slen-p1->iLen;
		if (!iQue.IsHead(p2))
		  	ll+=slen-p2->iLen;
        }
    TUint8 *pB=((TUint8 *)aPtr)+s1; 
    TInt lrem=aLength-s1;
    TBufSegLink *pP=p1;
    if (aLength>ll)
        {// Need some more segments
		TInt nseg=(slen-1+aLength-ll)/slen;
        AllocSegL(p1,nseg); // Could leave
        while (nseg--)
            { // Copy into allocated segments
            pP=pP->Next();
            TInt gap=slen;
            if (lrem<gap)
                gap=lrem;
			InsertIntoSegment(pP,0,aPtr==NULL ? NULL : pB,gap);
            pB+=gap;
            lrem-=gap;
            }
        }
    if (lrem) 
        {	
		InsertIntoSegment(p2,0,aPtr==NULL ? NULL : pB,lrem); 
        InsertIntoSegment(p2,lrem,pR,r1); 
        }
    else 
        { 
        TInt r2=0;
        if (pP!=p1)
            {
            r2=slen-pP->iLen; 
            if (r2>r1)
                r2=r1;	
            }
        InsertIntoSegment(pP,pP->iLen,pR,r2); // Moved from p1 
        InsertIntoSegment(p2,0,pR+r2,r1-r2); // Also moved from p1
        }
    p1->iLen-=r1;
	InsertIntoSegment(p1,ofs,aPtr,s1);
    iSize+=aLength;
	}

EXPORT_C void CBufSeg::Delete(TInt aPos,TInt aLength)
//
// Delete from the buffer.
//
	{

    if (aLength==0)
        return;
    SetSBO(aPos);
    TInt ofs=iOffset;
    __ASSERT_ALWAYS((iBase+ofs+aLength)<=iSize,Panic(EBufSegDeleteBeyondEnd));
    iSize-=aLength;
    TBufSegLink *p1=iSeg;
	TBufSegLink *p2;
    TInt rem=p1->iLen-ofs;
    FOREVER
        {
        p2=p1->Next();
        TInt gap=aLength;
        if (gap>rem)
            gap=rem;
        DeleteFromSegment(p1,ofs,gap);
        if (p1->iLen==0)
            {
            iSeg=NULL;
            FreeSegment(p1);
            }
        p1=p2;
        if ((aLength-=gap)==0)
            break;
        rem=p1->iLen;
        ofs=0;
        }
    if (iSize)
        {
        p1=p2->Prev();
        if (!iQue.IsHead(p1) && !iQue.IsHead(p2))
            {
            if ((p1->iLen+p2->iLen)<=iExpandSize)
                { // Join to the right
                InsertIntoSegment(p1,p1->iLen,p2+1,p2->iLen);
                FreeSegment(p2);
                }
            }
        }
    SetSBO(aPos);
	}

EXPORT_C TPtr8 CBufSeg::Ptr(TInt aPos)
//
// Return a pointer to the buffer, and the amount of data remaining.
//
	{

    if (iSize==0)
		return(TPtr8(NULL,0,0));
    SetSBO(aPos);
	TInt len=iSeg->iLen-iOffset;
    return(TPtr8(((TUint8 *)(iSeg+1))+iOffset,len,len));
	}

EXPORT_C TPtr8 CBufSeg::BackPtr(TInt aPos)
//
// Return a pointer to the buffer which has the maximum amount of data
// before aPos, and the amount of data remaining.  
//
	{

    if (aPos==0)
		return(TPtr8(NULL,0,0));
    SetSBO(aPos);
    if (iOffset)
        return(TPtr8((TUint8 *)(iSeg+1),iOffset,iOffset));
    TBufSegLink *pL=iSeg->Prev();
	TInt len=pL->iLen;
	return(TPtr8((TUint8 *)(pL+1),len,len));
	}

