/*
 * Copyright (C) 2001-2004 Peter J Jones (pjones@pmade.org)
 * All Rights Reserved
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of the Author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/** @file
 * This file contains the definition of the Netxx:Netbuf template class.
**/

#ifndef _netxx_netbuf_h_
#define _netxx_netbuf_h_

// Netxx includes
#include <netxx/streambase.h>

// standard includes
#include <streambuf>
#include <algorithm>
#include <cstring>

namespace 
{
    const std::streamsize PUTBACK_SIZE = 4;
}

namespace Netxx {

/**
 * The Netxx::Netbuf template class is a IOStreams streambuf. After you
 * create an instance of a Netxx::Netbuf class you can pass a pointer to it
 * to a std::iostream, std::ostream or std::istream class. Then you can do
 * basic IOStreams operations on it.
 *
 * This streambuf is buffered so you should call std::flush to make sure it
 * sends the data that you inserted.
**/
template <typename std::streamsize bufsize, class charT=char, class traits=std::char_traits<char> >
class Netbuf : public std::basic_streambuf<charT, traits> {
public:
    /// int type
    typedef typename std::basic_streambuf<charT, traits>::int_type int_type;

    /// char type
    typedef typename std::basic_streambuf<charT, traits>::char_type char_type;

    //####################################################################
    /** 
     * Construct a Netxx::Netbuf object and link it to the given StreamBase
     * object. The StreamBase object will be used for reading and writing to
     * the Netbuf (std::streambuf) object.
     *
     * @param stream The StreamBase object to use for reading and writing.
     * @author Peter Jones
    **/
    //####################################################################
    explicit Netbuf (StreamBase &stream);

    //####################################################################
    /** 
     * Netxx::Netbuf class destructor.
     *
     * @author Peter Jones
    **/
    //####################################################################
    ~Netbuf (void);
protected:
    // TODO streamsize xsputn (const char_type *s, streamsize n);
    int_type overflow (int_type c=traits_type::eof());
    int sync (void);

    int_type underflow (void);
    int_type pbackfail (int_type c);
private:
    StreamBase &stream_;
    charT putbuf_[bufsize];
    charT getbuf_[bufsize];

    int buffer_out (void);
    int buffer_in  (void);

    Netbuf (const Netbuf&);
    Netbuf& operator= (const Netbuf&);

}; // end Netxx::Netbuf class

//#############################################################################
template<std::streamsize bufsize, class charT, class traits>
Netbuf<bufsize, charT, traits>::Netbuf (StreamBase &stream)
    : stream_(stream)
{
    setp(putbuf_, putbuf_ + bufsize);
    setg(getbuf_+PUTBACK_SIZE, getbuf_+PUTBACK_SIZE, getbuf_+PUTBACK_SIZE);
}
//#############################################################################
template<std::streamsize bufsize, class charT, class traits>
Netbuf<bufsize, charT, traits>::~Netbuf (void) {
    sync();
}
//#############################################################################
template<std::streamsize bufsize, class charT, class traits>
typename Netbuf<bufsize, charT, traits>::int_type Netbuf<bufsize, charT, traits>::overflow (int_type c) {
    if (buffer_out() < 0) {
	return traits_type::eof();
    } else if (!traits_type::eq_int_type(c, traits_type::eof())) {
	return sputc(c);
    } else {
	return traits_type::not_eof(c);
    }
}
//#############################################################################
template<std::streamsize bufsize, class charT, class traits>
int Netbuf<bufsize, charT, traits>::sync (void) {
    return buffer_out();
}
//#############################################################################
template<std::streamsize bufsize, class charT, class traits>
int Netbuf<bufsize, charT, traits>::buffer_out (void) {
    int length = pptr() - pbase();
    int rc = stream_.write(putbuf_, length);
    pbump(-length);
    return rc;
}
//#############################################################################
template<std::streamsize bufsize, class charT, class traits>
typename Netbuf<bufsize, charT, traits>::int_type Netbuf<bufsize, charT, traits>::underflow (void) {
    if (gptr() < egptr()) return traits_type::to_int_type(*gptr());
    if (buffer_in() < 0) return traits_type::eof();
    else return traits_type::to_int_type(*gptr());
}
//#############################################################################
template<std::streamsize bufsize, class charT, class traits>
typename Netbuf<bufsize, charT, traits>::int_type Netbuf<bufsize, charT, traits>::pbackfail(int_type c) {
    if (gptr() != eback()) {
	gbump(-1);

	if (!traits_type::eq_int_type(c, traits_type::eof())) {
	    *(gptr()) = traits_type::to_char_type(c);
	}

	return traits_type::not_eof(c);
    } else {
	return traits_type::eof();
    }
}
//#############################################################################
template<std::streamsize bufsize, class charT, class traits>
int Netbuf<bufsize, charT, traits>::buffer_in (void) {
    std::streamsize number_putbacks = std::min(gptr() - eback(), PUTBACK_SIZE);
    std::memcpy(getbuf_ + (PUTBACK_SIZE - number_putbacks) * sizeof(char_type),
	    gptr() - number_putbacks * sizeof(char_type), number_putbacks * sizeof(char_type));

    int rc = stream_.read(getbuf_ + PUTBACK_SIZE * sizeof(char_type), bufsize - PUTBACK_SIZE);
    
    if (rc <= 0) {
	setg(0, 0, 0);
	return -1;
    } else {
	setg(getbuf_ + PUTBACK_SIZE - number_putbacks, getbuf_ + PUTBACK_SIZE, getbuf_ + PUTBACK_SIZE + rc);
	return rc;
    }
}
//#############################################################################

} // end Netxx namespace
#endif
