/**
 * @file bufferstream.cpp
 * @brief Implements the BufferStream adapter class
 *
 * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2012, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "bufferstream.h"

#include "bufferarray.h"


namespace LLCore
{

BufferArrayStreamBuf::BufferArrayStreamBuf(BufferArray * array)
    : mBufferArray(array),
      mReadCurPos(0),
      mReadCurBlock(-1),
      mReadBegin(NULL),
      mReadCur(NULL),
      mReadEnd(NULL),
      mWriteCurPos(0)
{
    if (array)
    {
        array->addRef();
        mWriteCurPos = array->mLen;
    }
}


BufferArrayStreamBuf::~BufferArrayStreamBuf()
{
    if (mBufferArray)
    {
        mBufferArray->release();
        mBufferArray = NULL;
    }
}


BufferArrayStreamBuf::int_type BufferArrayStreamBuf::underflow()
{
    if (! mBufferArray)
    {
        return traits_type::eof();
    }

    if (mReadCur == mReadEnd)
    {
        // Find the next block with actual data or leave
        // mCurBlock/mCur/mEnd unchanged if we're at the end
        // of any block chain.
        const char * new_begin(NULL), * new_end(NULL);
        int new_cur_block(mReadCurBlock + 1);

        while (mBufferArray->getBlockStartEnd(new_cur_block, &new_begin, &new_end))
        {
            if (new_begin != new_end)
            {
                break;
            }
            ++new_cur_block;
        }
        if (new_begin == new_end)
        {
            return traits_type::eof();
        }

        mReadCurBlock = new_cur_block;
        mReadBegin = mReadCur = new_begin;
        mReadEnd = new_end;
    }

    return traits_type::to_int_type(*mReadCur);
}


BufferArrayStreamBuf::int_type BufferArrayStreamBuf::uflow()
{
    const int_type ret(underflow());

    if (traits_type::eof() != ret)
    {
        ++mReadCur;
        ++mReadCurPos;
    }
    return ret;
}


BufferArrayStreamBuf::int_type BufferArrayStreamBuf::pbackfail(int_type ch)
{
    if (! mBufferArray)
    {
        return traits_type::eof();
    }

    if (mReadCur == mReadBegin)
    {
        // Find the previous block with actual data or leave
        // mCurBlock/mBegin/mCur/mEnd unchanged if we're at the
        // beginning of any block chain.
        const char * new_begin(NULL), * new_end(NULL);
        int new_cur_block(mReadCurBlock - 1);

        while (mBufferArray->getBlockStartEnd(new_cur_block, &new_begin, &new_end))
        {
            if (new_begin != new_end)
            {
                break;
            }
            --new_cur_block;
        }
        if (new_begin == new_end)
        {
            return traits_type::eof();
        }

        mReadCurBlock = new_cur_block;
        mReadBegin = new_begin;
        mReadEnd = mReadCur = new_end;
    }

    if (traits_type::eof() != ch && mReadCur[-1] != ch)
    {
        return traits_type::eof();
    }
    --mReadCurPos;
    return traits_type::to_int_type(*--mReadCur);
}


std::streamsize BufferArrayStreamBuf::showmanyc()
{
    if (! mBufferArray)
    {
        return -1;
    }
    return mBufferArray->mLen - mReadCurPos;
}


BufferArrayStreamBuf::int_type BufferArrayStreamBuf::overflow(int c)
{
    if (! mBufferArray || mWriteCurPos > mBufferArray->mLen)
    {
        return traits_type::eof();
    }
    const size_t wrote(mBufferArray->write(mWriteCurPos, &c, 1));
    mWriteCurPos += wrote;
    return wrote ? c : traits_type::eof();
}


std::streamsize BufferArrayStreamBuf::xsputn(const char * src, std::streamsize count)
{
    if (! mBufferArray || mWriteCurPos > mBufferArray->mLen)
    {
        return 0;
    }
    const size_t wrote(mBufferArray->write(mWriteCurPos, src, count));
    mWriteCurPos += wrote;
    return wrote;
}


std::streampos BufferArrayStreamBuf::seekoff(std::streamoff off,
                                             std::ios_base::seekdir way,
                                             std::ios_base::openmode which)
{
    std::streampos ret(-1);

    if (! mBufferArray)
    {
        return ret;
    }

    if (std::ios_base::in == which)
    {
        size_t pos(0);

        switch (way)
        {
        case std::ios_base::beg:
            pos = off;
            break;

        case std::ios_base::cur:
            pos = mReadCurPos += off;
            break;

        case std::ios_base::end:
            pos = mBufferArray->mLen - off;
            break;

        default:
            return ret;
        }

        if (pos >= mBufferArray->size())
        {
            pos = (std::max)(size_t(0), mBufferArray->size() - 1);
        }
        size_t ba_offset(0);
        int block(mBufferArray->findBlock(pos, &ba_offset));
        if (block < 0)
            return ret;
        const char * start(NULL), * end(NULL);
        if (! mBufferArray->getBlockStartEnd(block, &start, &end))
            return ret;
        mReadCurBlock = block;
        mReadBegin = start;
        mReadCur = start + ba_offset;
        mReadEnd = end;
        ret = mReadCurPos = pos;
    }
    else if (std::ios_base::out == which)
    {
        size_t pos(0);

        switch (way)
        {
        case std::ios_base::beg:
            pos = off;
            break;

        case std::ios_base::cur:
            pos = mWriteCurPos += off;
            break;

        case std::ios_base::end:
            pos = mBufferArray->mLen - off;
            break;

        default:
            return ret;
        }

        if (pos > mBufferArray->size())
        {
            pos = mBufferArray->size();
        }
        ret = mWriteCurPos = pos;
    }

    return ret;
}


BufferArrayStream::BufferArrayStream(BufferArray * ba)
    : std::iostream(&mStreamBuf),
      mStreamBuf(ba)
{}


BufferArrayStream::~BufferArrayStream()
{}


}  // end namespace LLCore