/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#include "bu/textbuilder.h"

#include "bu/exceptionbase.h"
#include "bu/text.h"

#define PAGE_SIZE       16

Bu::TextBuilderCore::Chunk::Chunk( const CodePoint *pSrc, int32_t iLength ) :
    iLength( iLength ),
    pData( 0 ),
    pNext( 0 )
{
    if( iLength < PAGE_SIZE )
    {
        pData = new CodePoint[PAGE_SIZE];
    }
    else
    {
        pData = new CodePoint[iLength];
    }
    memcpy( pData, pSrc, iLength*sizeof(CodePoint) );
}

Bu::TextBuilderCore::Chunk::~Chunk()
{
    delete[] pData;
    pData = 0;
    pNext = 0;
}

void Bu::TextBuilderCore::Chunk::append( const Bu::CodePoint *&pSrc,
        int32_t &iLength )
{
    if( this->iLength >= PAGE_SIZE )
    {
        // This chink is full, just return.
        return;
    }
    int32_t iCopy = PAGE_SIZE-this->iLength;
    if( iCopy > iLength )
    {
        iCopy = iLength;
    }
    memcpy( pData+this->iLength, pSrc, iCopy*sizeof(Bu::CodePoint) );
    this->iLength += iCopy;
    pSrc += iCopy;
    iLength -= iCopy;
}

Bu::TextBuilderCore::Chunk *Bu::TextBuilderCore::Chunk::split( int32_t iIndex )
{
    if( iIndex == 0 )
        return NULL;

    if( iIndex >= iLength )
        return NULL;

    Chunk *pNew = new Chunk( pData+iIndex, iLength-iIndex );
    iLength -= iIndex;
    pNew->pNext = pNext;
    pNext = pNew;

    return pNew;
}


//////
// TextBuilderCore
//
Bu::TextBuilderCore::TextBuilderCore() :
    pFirst( 0 ),
    pLast( 0 ),
    iLength( 0 )
{
}

Bu::TextBuilderCore::TextBuilderCore( const TextBuilderCore &rSrc ) :
    pFirst( 0 ),
    pLast( 0 ),
    iLength( rSrc.iLength )
{
    throw Bu::ExceptionBase("Not yet implemented.");
}

Bu::TextBuilderCore::~TextBuilderCore()
{
    clear();
}

void Bu::TextBuilderCore::clear()
{
    Chunk *pCur = pFirst;
    while( pCur )
    {
        Chunk *pNext = pCur->pNext;
        delete pCur;
        pCur = pNext;
    }
    pFirst = pLast = 0;
    iLength = 0;
}

void Bu::TextBuilderCore::append( const CodePoint *pSrc, int32_t iLength )
{
    this->iLength += iLength;
    if( pFirst == 0 )
    {
        // Nothing in the list, just add a chunk.
        pFirst = pLast = new Chunk( pSrc, iLength );
        return;
    }
    else if( pLast->iLength < PAGE_SIZE )
    {
        // Append to the last chunk first, this will modify pSrc & iLength.
        pLast->append( pSrc, iLength );
    }

    // If there's unused data at the end, append it now.
    if( iLength > 0 )
    {
        pLast->pNext = new Chunk( pSrc, iLength );
        pLast = pLast->pNext;
    }
}

void Bu::TextBuilderCore::prepend( const CodePoint *pSrc, int32_t iLength )
{
    if( pFirst == 0 )
    {
        pFirst = pLast = new Chunk( pSrc, iLength );
    }
    else
    {
        Chunk *pNew = new Chunk( pSrc, iLength );
        pNew->pNext = pFirst;
        pFirst = pNew;
    }
    this->iLength += iLength;
}

void Bu::TextBuilderCore::insert( int32_t iBefore, const CodePoint *pSrc, int32_t iLength )
{
    if( iBefore <= 0 )
    {
        prepend( pSrc, iLength );
        return;
    }
    if( iBefore >= this->iLength )
    {
        append( pSrc, iLength );
        return;
    }

    Chunk *pCur = pFirst;
    while( pCur )
    {
        if( iBefore == 0 )
        {
            // Insert between chunks, no splitting required.
            Chunk *pNew = new Chunk( pSrc, iLength );
            pNew->pNext = pCur->pNext;
            pCur->pNext = pNew;
            if( pLast == pCur )
                pLast = pNew;
        }
        if( iBefore < pCur->iLength )
        {
            // This is the chunk we need to split.
            Chunk *pNew = pCur->split( iBefore );
            if( pLast == pCur )
                pLast = pNew;
            continue;
        }
        pCur = pCur->pNext;
    }
    this->iLength = iLength;
}

void Bu::TextBuilderCore::set( const CodePoint *pSrc, int32_t iLength )
{
    clear();
    append( pSrc, iLength );
}

void Bu::TextBuilderCore::copyTo( void *pDestRaw, int32_t iLength )
{
    CodePoint *pDest = reinterpret_cast<CodePoint *>( pDestRaw );

    Chunk *pCur = pFirst;
    while( pCur && iLength )
    {
        int32_t iChunkLen = pCur->iLength;
        if( iChunkLen > iLength )
            iChunkLen = iLength;

        memcpy( pDest, pCur->pData, iChunkLen*sizeof(CodePoint) );
        pDest += iChunkLen;
        iLength -= iChunkLen;

        pCur = pCur->pNext;
    }
}

Bu::CodePoint Bu::TextBuilderCore::getAt( int32_t iIndex ) const
{
    if( iIndex < 0 || iIndex >= iLength )
        throw Bu::ExceptionBase("Requested index is out of range.");

    Chunk *pCur = pFirst;
    while( iIndex >= pCur->iLength )
    {
        iIndex -= pCur->iLength;
        pCur = pCur->pNext;
    }
    return pCur->pData[iIndex];
}

/////
// TextBuilder
//

Bu::TextBuilder::TextBuilder()
{
}

Bu::TextBuilder::TextBuilder( const Text &rSrc )
{
    core->append( rSrc.getData(), rSrc.getSize() );
}

Bu::TextBuilder::TextBuilder( const TextBuilder &rSrc ) :
    Bu::SharedCore<Bu::TextBuilder, Bu::TextBuilderCore>( rSrc )
{
}

Bu::TextBuilder::~TextBuilder()
{
}

void Bu::TextBuilder::set( const Text &rSrc )
{
    _hardCopy();
    core->set( rSrc.getData(), rSrc.getSize() );
}

void Bu::TextBuilder::append( const Text &rSrc )
{
    _hardCopy();
    core->append( rSrc.getData(), rSrc.getSize() );
}

void Bu::TextBuilder::append( const CodePoint *pSrc, int32_t iLength )
{
    _hardCopy();
    core->append( pSrc, iLength );
}

void Bu::TextBuilder::prepend( const Text &rSrc )
{
    _hardCopy();
    core->prepend( rSrc.getData(), rSrc.getSize() );
}

void Bu::TextBuilder::insert( int32_t iBefore, const Text &rSrc )
{
    _hardCopy();
    core->insert( iBefore, rSrc.getData(), rSrc.getSize() );
}

void Bu::TextBuilder::clear()
{
    _hardCopy();
    core->clear();
}

int32_t Bu::TextBuilder::getSize() const
{
    return core->iLength;
}

Bu::Text Bu::TextBuilder::getText() const
{
    return Text( *this );
}

void Bu::TextBuilder::copyTo( void *pDestRaw, int32_t iDestSize ) const
{
    core->copyTo( pDestRaw, iDestSize );
}

Bu::CodePoint Bu::TextBuilder::operator[]( int32_t iIndex ) const
{
    return core->getAt( iIndex );
}

Bu::TextBuilder &Bu::TextBuilder::operator=( const Text &rSrc )
{
    set( rSrc );
    return *this;
}

Bu::TextBuilder &Bu::TextBuilder::operator==( const Text &rSrc )
{

    return *this;
}

