/***************************************************************************
 *
 * strstream.cpp - Source for the Standard Library string stream classes
 *
 * $Id: strstream.cpp 550991 2007-06-26 23:58:07Z sebor $
 *
 ***************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  License); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 *
 * Copyright 1994-2006 Rogue Wave Software.
 * 
 **************************************************************************/

#define _RWSTD_LIB_SRC

#if    3 == __GNUG__ && 3 > __GNUC_MINOR__ \
    || 3 == __GNUC_MINOR__ && 1 >__GNUC_PATCHLEVEL__

  // working around a gcc bug (PR #29570)
#  include <rw/_config.h>
#  ifndef _RWSTD_NO_EXTERN_TEMPLATE
#    define _RWSTD_NO_EXTERN_TEMPLATE
#  endif
#endif   // gcc >= 3.0 && gcc < 3.3.1

#include <rw/_defs.h>

#include <limits.h>

#include <ios>
#include <strstream>


_RWSTD_NAMESPACE (std) {


/* virtual */
strstreambuf::~strstreambuf ()
{
    _RWSTD_ASSERT (_C_is_valid ());

    if (   _C_buffer && _C_state & _C_allocated
        && !(_C_state & _C_frozen)) {
        if (_C_pfree)
            _C_pfree (_C_buffer);
        else
            delete[] _C_buffer;
    }
}


/* virtual */ strstreambuf::int_type
strstreambuf::overflow (int_type c)
{
    _RWSTD_ASSERT (_C_is_valid ());

    if (!_C_is_out () || !(_C_state & _C_dynamic))
        return traits_type::eof ();
    
    if (_C_is_eof (c))
        return traits_type::not_eof (c);
    
    // reallocate space if necessary
    if (!(pptr () < epptr ())) {

        const streamsize new_size =
            _C_bufsize ? _C_bufsize * 2 : _C_alsize ? _C_alsize : 128;

        // take care to avoid calling the overridden setbuf(), if any
        if (!strstreambuf::setbuf (0, new_size))
            return traits_type::eof ();
    }

    return sputc (traits_type::to_char_type (c));
}


/* virtual */ strstreambuf::int_type
strstreambuf::pbackfail (int_type c)
{
    _RWSTD_ASSERT (_C_is_valid ());

    typedef traits_type Traits;

    if (eback () < gptr ()) {

        // putback position is available (see 27.5.1, p3, bullet 3)

        if (Traits::eq_int_type (c, Traits::eof ())) {

            // D.7.1.3, p6, bullet 3

            gbump (-1);
            return Traits::not_eof (c);
        }

        if (Traits::eq_int_type (Traits::to_int_type (*(gptr () - 1)), c)) {

            // D.7.1.3, p6, bullet 1

            gbump (-1);
            return c;
        }

        if (!(_C_state & _C_constant)) {

            // D.7.1.3, p6, bullet 2

            gbump (-1);
            Traits::assign (*gptr (), Traits::to_char_type (c));
            return c;
        }
    }

    // D.7.1.3, p7
    return Traits::eof ();
}


/* virtual */ strstreambuf::int_type
strstreambuf::underflow ()
{
    _RWSTD_ASSERT (_C_is_valid ());

    if (gptr () < egptr ()) {
        // read position is available (see 27.5.1, p3, bullet 4)

        // D.7.1.3, p9, bullet 1
        return traits_type::to_int_type (*gptr ());
    }

    if (pptr () > egptr ()) {

        // D.7.1.3, p9, bullet 2
        if (gptr ())
            setg (eback (), gptr (), pptr ());
        else
            setg (pbase (), pbase (), pptr ());

        return traits_type::to_int_type (*gptr ());
    }

    // D.7.1.3, p10
    return traits_type::eof ();
}


/* virtual */ strstreambuf::pos_type
strstreambuf::seekoff (off_type off, ios::seekdir way, ios::openmode which)
{
    _RWSTD_ASSERT (_C_is_valid ());

    // should implicitly hold as long as ios::seekdir is an enum
    _RWSTD_ASSERT (ios::beg == way || ios::cur == way || ios::end == way);
    _RWSTD_ASSERT (_C_is_valid ());

    // determine seekable area - D.7.1, p4
    char* const seeklo = eback ();
    char* const seekhi = epptr () ? epptr () : egptr ();

    const char* const xnext = which & ios::in ? gptr () : pptr ();
    const char* const xbeg  = which & ios::in ? eback () : pbase ();

    // D.7.1.3, p13
    if (!xnext)
        return pos_type (off_type (-1));

    off_type saved_off = off;

    // compute new offset - D.7.1.3, p13, table 105
    if (ios::cur == way)
        off += off_type (xnext - xbeg);
    else if (ios::end == way)
        off += off_type (seekhi - xbeg);

    // test conditions in D.7.1.3, p13, table 105, row 4
    if (off < seeklo - xbeg || off > seekhi - xbeg)
        off = -1;   // failure
    else if (which & ios::in) {   // position input sequence

        // fail if `way' is `cur', otherwise position output sequence first
        if (   which & ios::out
            && (   way == ios::cur
                || pos_type (-1) == seekoff (saved_off, way, ios::out)))
            return pos_type (off_type (-1));

        // adjust input sequence as necessary to maintain invariant
        if (off <= egptr () - eback ())
            setg (eback (), eback () + off, egptr ());
        else if (off <= pptr () - egptr ())   // advance egptr()
            setg (eback (), eback () + off, pptr ());
        else   // advance egptr() even further
            setg (eback (), eback () + off, epptr ());
    }
    else if (which & ios::out) {   // position output sequence
        if (seeklo + off < pbase ()) {
            // adjust beginning of output sequence, then increment pptr()
            setp (seeklo, epptr ());
            pbump (off);
        }
        else {
            // reset pptr() first, then increment it by offset
            setp (pbase (), epptr ());
            pbump (off - (pbase () - seeklo));
        }
    }
    else
        off = -1;   // failure

    _RWSTD_ASSERT (_C_is_valid ());

    return pos_type (off);
}


/* virtual */ strstreambuf::pos_type
strstreambuf::seekpos (pos_type sp, ios::openmode which)
{
    _RWSTD_ASSERT (_C_is_valid ());

    // call seekoff() defined by strstreambuf, and
    // not an overridden() virtual if one exists
    return strstreambuf::seekoff (sp - pos_type (), ios::beg, which);
}


/* virtual */ streambuf*
strstreambuf::setbuf (char_type* buf, streamsize bufsize)
{
    _RWSTD_ASSERT (_C_is_valid ());

    if (   !(_C_state & _C_dynamic) || (_C_state & _C_frozen)
        || !buf && !bufsize) {
        // lwg issue 66
        return 0;
    }

    // determine the existing (possibly disjoint) sequences
    // in the (possibly two distinct) buffer(s) and copy
    // them into the new buffer

    // [.....]  [.....]          or [....[.]...]
    // ^     ^  ^     ^             ^          ^
    // |     |  |     |             |          |
    // |     |  |     +- xend       |          +- xend, gap_beg, gap_end
    // |     |  +------- gap_end    +------------ xbeg
    // |     +---------- gap_beg
    // +---------------- xbeg

    const char_type* const xbeg =
        eback () && eback () < pbase () ? eback () : pbase ();

    const char_type* const xend =
        epptr () && epptr () < egptr () ? egptr () : epptr ();

    const char_type *gap_beg = pptr () < eback ()
        ? pptr () : egptr () < pbase () ? egptr () : pbase ();

    const char_type *gap_end = pptr () < eback ()
        ? eback () : egptr () < pbase () ? egptr () : pbase ();

    // if gap_end is before gap_beg, set both so as to make the gap zero size
    if (gap_end <= gap_beg)
        gap_beg = gap_end = xend;

    // compute the cumulative size of both sequences minus the gap
    const streamsize slen = streamsize ((xend - xbeg) - (gap_end - gap_beg));

    if (bufsize < slen || !_C_is_out ())
        return 0;   // failure

    const bool free_old_buf = _C_own_buf ();
    
    if (!buf) {
        _TRY {
            // allocate a new buffer
            buf = _C_palloc ?
                  _RWSTD_STATIC_CAST (char_type*, _C_palloc (bufsize))
                : new char [bufsize];
        }
        _CATCH (...) {
            // catch all exceptions, indicate failure by returning 0
        }

        if (!buf)
            return 0;

        _C_own_buf (true);
    }
    else
        _C_own_buf (false);

    // copy both sequences from the old buffer to the new buffer
    traits_type::copy (buf, xbeg, gap_beg - xbeg);
    traits_type::copy (buf + (gap_beg - xbeg), gap_end, xend - gap_end);

    if (free_old_buf) {
        if (_C_pfree)
            _C_pfree (_C_buffer);
        else
            delete[] _C_buffer;
    }
    
    _C_buffer  = buf;
    _C_bufsize = bufsize;

    const _RWSTD_PTRDIFF_T seq_1_size = gap_beg - xbeg;

    // restore output sequence
    const streamsize pptr_off = streamsize (pptr () - pbase ());

    if (pbase () < gap_beg)
        setp (_C_buffer, _C_buffer + _C_bufsize);
    else
        setp (_C_buffer + seq_1_size, _C_buffer + _C_bufsize);

    pbump (pptr_off);

    // restore input sequence
    char_type* const gbeg = _C_buffer + (eback () < gap_beg ? 0 : seq_1_size);
    setg (gbeg, gbeg + (gptr () - eback ()), gbeg + (egptr () - eback ()));

    return this;
}


void strstreambuf::
_C_init (streamsize  alsize,
         const void *gnext_v,
         streamsize  n,
         const void *pbeg_v,
         void*     (*palloc)(_RWSTD_SIZE_T),
         void      (*pdealloc)(void*),
         int         state)
{
    _C_alsize = alsize;
    _C_palloc = palloc;
    _C_pfree  = pdealloc;

    char* const gnext =
        _RWSTD_STATIC_CAST (char*, _RWSTD_CONST_CAST (void*, gnext_v));

    char* const pbeg =
        _RWSTD_STATIC_CAST (char*, _RWSTD_CONST_CAST (void*, pbeg_v));

    // D.7.1.1, p4
    if (n < 0)
        n = _RWSTD_INT_MAX;
    else if (!n && gnext)
        n = streamsize (traits_type::length (gnext));

    _C_buffer = gnext;
    _C_state  = state | ios_base::in | ios_base::out;

    if (pbeg) {
        // D.7.1.1, p6
        setg (gnext, gnext, pbeg);
        setp (pbeg, pbeg + n);
    }
    else
        // D.7.1.1, p5
        setg (gnext, gnext, gnext + n);

    _RWSTD_ASSERT (_C_is_valid ());
}


}   // namespace std
