/************************************************************************
 *
 * rw_streambuf.h - definition of the MyStreambuf class template
 *
 * $Id$
 *
 ***************************************************************************
 *
 * 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 2004-2006 Rogue Wave Software.
 * 
 **************************************************************************/

#ifndef RW_STREAMBUF_H_INCLUDED
#define RW_STREAMBUF_H_INCLUDED


#include <cstring>          // for memset()
#include <streambuf>        // for basic_streambuf

#include <rw_testdefs.h>
#include <rw_char.h>        // for make_char()
#include <rw_exception.h>   // for rw_throw()


enum MemFun {
    // bitmask with a bit for each virtual member function
    None      = 0,
    Setbuf    = 0x0001,
    Seekoff   = 0x0002,
    Seekpos   = 0x0004,
    Showmanyc = 0x0008,
    Xsgetn    = 0x0010,
    Underflow = 0x0020,
    Uflow     = 0x0040,
    Overflow  = 0x0080,
    Pbackfail = 0x0100,
    Xsputn    = 0x0200,
    Sync      = 0x0400,
    // bit OR-ed with MemFun bits
    Throw     = 0x1000,
    Failure   = 0x2000
};

static const char* const streambuf_func_names[] = {
    "setbuf", "seekoff", "seekpos", "showmanyc", "xsgetn", "underflow",
    "uflow", "overflow", "pbackfail", "xsputn", "sync"
};

template <class charT, class Traits>
struct MyStreambuf: std::basic_streambuf<charT, Traits>
{
    typedef charT                                        char_type;
    typedef Traits                                       traits_type;
    typedef std::basic_streambuf<char_type, traits_type> Base;
    typedef typename Base::int_type                      int_type;
    typedef typename Base::off_type                      off_type;
    typedef typename Base::pos_type                      pos_type;

    MyStreambuf (std::streamsize, int, int);

    MyStreambuf (const char*, std::streamsize, int, int);

    ~MyStreambuf () {
        delete[] buf_;
    }

    // public interface to base class protected members
    char_type* pubeback () const {
        return this->eback ();
    }

    char_type* pubgptr () const {
        return this->gptr ();
    }

    char_type* pubegptr () const {
        return this->egptr ();
    }

    char_type* pubpbase () const {
        return this->pbase ();
    }

    char_type* pubpptr () const {
        return this->pptr ();
    }

    char_type* pubepptr () const {
        return this->epptr ();
    }

    void pubsetg (charT *beg, charT *cur, charT *end) {
        this->setg (beg, cur, end);
    }

    void pubsetp (charT *beg, charT *cur, charT *end) {
        this->setp (beg, end);
        this->pbump (cur - beg);
    }

private:

    // overridden protected virtual functions
    virtual Base* setbuf (char_type*, std::streamsize) {
        return test (Setbuf) ? this : 0;
    }

    virtual pos_type
    seekoff (off_type, std::ios_base::seekdir, std::ios_base::openmode) {
        test (Seekoff);
        return pos_type (off_type (-1));
    }

    virtual pos_type
    seekpos (pos_type, std::ios_base::openmode) {
        test (Seekpos);
        return pos_type (off_type (-1));
    }

    virtual std::streamsize showmanyc () {
        test (Showmanyc);
        return 0;
    }

    virtual std::streamsize xsgetn (char_type*, std::streamsize) {
        test (Xsgetn);
        return 0;
    }

    virtual int_type underflow ();

    virtual int_type uflow ();

    virtual int_type
    overflow (int_type = traits_type::eof ());

    virtual int_type
    pbackfail (int_type = traits_type::eof ());

    virtual std::streamsize
    xsputn (const char_type *buf, std::streamsize bufsize) {
        if (!test (Xsputn))
            return 0;
        return Base::xsputn (buf, bufsize);
    }

    virtual int sync () {
        test (Sync);
        return 0;
    }

public:

    int ncalls (MemFun) const;
    int memfun_inx (MemFun) const;

    char_type       *buf_;
    std::streamsize  bufsize_;

    int        throw_set_;       // functions that should throw
    int        fail_set_;        // functions that should fail
    MemFun     threw_;           // which function threw
    MemFun     failed_;          // which function failed

    int        fail_when_;       // call number on which to fail

    int        throw_when_ [11]; // call number on which to throw for each func
    int        allthrows_;       // total number of thrown exceptions

    // max size of the pending input sequence
    static std::streamsize in_pending_;

    // max size of the pending output sequence
    static std::streamsize out_pending_;

private:

    bool test (MemFun) const;

    int ncalls_ [11];  // number of calls made to each function

    int allcalls_;     // total number of calls
};


template <class charT, class Traits>
std::streamsize MyStreambuf<charT, Traits>::
in_pending_ = 1;


template <class charT, class Traits>
std::streamsize MyStreambuf<charT, Traits>::
out_pending_ = 1;


template <class charT, class Traits>
MyStreambuf<charT, Traits>::
MyStreambuf (std::streamsize bufsize, int fail_set, int when)
    : Base (), buf_ (0), bufsize_ (bufsize),
      throw_set_ (0), fail_set_ (0), threw_ (None), failed_ (None),
      fail_when_ (when), allthrows_ (0), allcalls_ (0)
{
    // reset the member function call counters
    std::memset (ncalls_, 0, sizeof ncalls_);

    // reset the member function throw counters
    std::memset (throw_when_, 0, sizeof throw_when_);

    // allocate a (possibly wide) character buffer for output
    buf_ = new charT [bufsize_];

    // invalidate the contents of the buffer
    traits_type::assign (buf_, bufsize_, make_char ('\xfe', buf_));

    // set the put area to 0 size to force a call to overflow()
    // on the first write attempt to the buffer 
    this->setp (buf_, buf_);

    // set the fail and throw flags
    if (fail_set & Throw) {
        throw_set_ = fail_set & ~Throw;

        for (unsigned i = 0; i < 11; ++i)
            if (throw_set_ & (1U << i))
                throw_when_ [i] = when;
    }
    else {
        fail_set_ = fail_set;
    }
}


template <class charT, class Traits>
MyStreambuf<charT, Traits>::
MyStreambuf (const char *buf, std::streamsize bufsize, int fail_set, int when)
    : Base (), buf_ (0), bufsize_ (bufsize),
      throw_set_ (0), fail_set_ (0), threw_ (None), failed_ (None),
      fail_when_ (when), allthrows_ (0), allcalls_ (0)
{
    // reset the member function call counters
    std::memset (ncalls_, 0, sizeof ncalls_);

    // reset the member function throw counters
    std::memset (throw_when_, 0, sizeof throw_when_);

    // as a convenience, if `bufsize == -1' compute the size
    // from the length of `buf'
    if (std::streamsize (-1) == bufsize_)
        bufsize_ = std::streamsize (std::char_traits<char>::length (buf)) + 1;

    // allocate a (possibly wide) character buffer to copy
    // (and widen) the contents of `buf' into
    buf_ = new charT [bufsize_ + 1];

    for (std::streamsize inx = 0; inx != bufsize_; ++inx) {
        typedef unsigned char UChar;

        buf_ [inx] = make_char (buf [inx], buf_);
    }

    // zero out the (non-dereferenceable) element just past the end
    // so that the buffer can be printed out as an ordinary string
    buf_ [bufsize_] = charT ();

    // set the get area to 0 size to force a call to underflow()
    // on the first read attempt from the buffer
    this->setg (buf_, buf_, buf_);

    // set the fail and throw flags
    if (fail_set & Throw) {
        throw_set_ = fail_set & ~Throw;

        for (unsigned i = 0; i < 11; ++i)
            if (throw_set_ & (1U << i))
                throw_when_ [i] = when;
    }
    else {
        fail_set_ = fail_set;
    }
}


template <class charT, class Traits>
typename MyStreambuf<charT, Traits>::int_type
MyStreambuf<charT, Traits>::
underflow ()
{
    if (!test (Underflow))
        return traits_type::eof ();

    if (this->egptr () - this->gptr () > 0) {
        this->gbump (1);
        return traits_type::to_int_type (*this->gptr ());
    }
        
    if (this->egptr () < buf_ + bufsize_) {

        // increase the pending input sequence by no more than
        // the lesser of the available buffer space and `in_pending_'
        std::streamsize pending = buf_ + bufsize_ - this->egptr ();

        if (pending > in_pending_)
            pending = in_pending_;

        this->setg (this->eback (), this->gptr (), this->egptr () + pending);
        return traits_type::to_int_type (*this->gptr ());
    }

    failed_ = Underflow;
    return traits_type::eof ();        
}


template <class charT, class Traits>
typename MyStreambuf<charT, Traits>::int_type
MyStreambuf<charT, Traits>::
overflow (int_type c /* = traits_type::eof () */)
{
    if (!test (Overflow))
        return traits_type::eof ();

    if (this->epptr () - this->pptr () > 0) {
        traits_type::assign (*this->pptr (), traits_type::to_char_type (c));
        this->pbump (1);
        return traits_type::not_eof (c);
    }
        
    if (this->epptr () < buf_ + bufsize_) {

        // increase the pending output sequence by no more than
        // the lesser of the available buffer space and `out_pending_'
        std::streamsize pending = buf_ + bufsize_ - this->epptr ();

        if (pending > out_pending_)
            pending = out_pending_;

        const std::streamsize pptr_off = this->pptr () - this->pbase ();

        this->setp (this->pbase (), this->epptr () + pending);

        this->pbump (pptr_off);

        traits_type::assign (*this->pptr (), traits_type::to_char_type (c));

        this->pbump (1);

        return traits_type::not_eof (c);
    }

    failed_ = Overflow;
    return traits_type::eof ();        
}


template <class charT, class Traits>
typename MyStreambuf<charT, Traits>::int_type
MyStreambuf<charT, Traits>::
pbackfail (int_type c /* = traits_type::eof () */)
{
    if (!test (Pbackfail))
        return traits_type::eof ();

    if (this->gptr () == buf_) {
        failed_ = Pbackfail;
        return traits_type::eof ();
    }

    this->setg (this->gptr () - 1, this->gptr () - 1, this->gptr ());

    const int_type last = traits_type::to_int_type (*this->gptr ());

    if (!traits_type::eq_int_type (c, traits_type::eof ()))
        traits_type::assign (*this->gptr (), traits_type::to_char_type (c));

    return last;
}


template <class charT, class Traits>
typename MyStreambuf<charT, Traits>::int_type
MyStreambuf<charT, Traits>::
uflow ()
{
    if (!test (Uflow))
        return traits_type::eof ();

    if (this->egptr () - this->gptr () > 0) {
        this->gbump (1);
        return traits_type::to_int_type (*this->gptr ());
    }
        
    if (this->egptr () < buf_ + bufsize_) {

        // increase the pending input sequence by no more than
        // the lesser of the available buffer space and `in_pending_'
        std::streamsize pending = buf_ + bufsize_ - this->egptr ();

        if (pending > in_pending_)
            pending = in_pending_;

        this->setg (this->eback (), this->gptr (), this->egptr () + pending);
        return traits_type::to_int_type (*this->gptr ());
    }

    failed_ = Uflow;
    return traits_type::eof ();        
}

template <class charT, class Traits>
int
MyStreambuf<charT, Traits>::
memfun_inx (MemFun which) const
{
    int inx = -1;

    for (unsigned i = 0; i < sizeof (which) * _RWSTD_CHAR_BIT; ++i) {
        if (which & (1U << i)) {
            if (inx < 0)
                inx = i;
            else
                return -1;
        }
    }

    return inx;
}

template <class charT, class Traits>
int
MyStreambuf<charT, Traits>::
ncalls (MemFun which) const
{
    int inx = memfun_inx (which);
    if (0 <= inx)
        return ncalls_ [inx];
    
    return -1;
}


template <class charT, class Traits>
bool
MyStreambuf<charT, Traits>::
test (MemFun which) const
{
    MyStreambuf* const self = _RWSTD_CONST_CAST (MyStreambuf*, this);

    const int inx = memfun_inx (which);
    if (inx < 0)
        return true;

    // assert precondition to silence HP aCC 6/cadvise warning
    // #20206-D: Out of bound access 
    RW_ASSERT ((_RWSTD_SIZE_T)inx < sizeof ncalls_ / sizeof *ncalls_);

    // increment the counter tracking the number of calls made
    // to each member function; do so regardless of whether
    // an exception will be thrown below
    self->ncalls_ [inx] ++;
    self->allcalls_ ++;
    const int callno = self->ncalls_ [inx];

#ifndef _RWSTD_NO_EXCEPTIONS

    // if the call counter is equal to the `fail_when_' watermark
    // and `shich' is set in the `throw_set_' bitmask, throw an
    // exception with the value of the member id
    if (callno == throw_when_ [inx] && throw_set_ & which) {
        self->threw_ = which;
        self->allthrows_++;

        rw_throw (ex_stream, __FILE__, __LINE__, 
                  streambuf_func_names [inx], 
                  "%s", "test exception");
    }

#else   // if defined (_RWSTD_NO_EXCEPTIONS)

    if (callno == throw_when_ [inx] && throw_set_ & which) {
        self->threw_ = which;
        return false;
    }

#endif   // _RWSTD_NO_EXCEPTIONS

    // ...otherwise check if the member should succeed or fail
    // and return true or false, respectively
    const bool success = !(fail_set_ & which) || callno != fail_when_;

    if (!success)
        self->failed_ = which;

    return success;
}


#endif   // RW_STREAMBUF_H_INCLUDED
