#ifndef __STREAMBUFIMPL_H_INCLUDED__
#define __STREAMBUFIMPL_H_INCLUDED__

#include <streambuf>

// Abstract class representing the physical device.
// It is important that all methods do not throw exceptions
template <typename Trait>
class device_wrapper
{
public:
	typedef typename Trait::char_type		char_type;
	typedef Trait							traits_type;
	typedef typename Trait::int_type		int_type;
	typedef typename Trait::pos_type		pos_type;
	typedef typename Trait::off_type		off_type;

	// return number og characters read. -1 for failure
	virtual int read( char_type* s, std::streamsize n ) = 0;

	// return number of characters written. -1 for failure
	virtual int write( const char_type* s, std::streamsize n ) = 0;

	// return true for success and false for failure
	virtual bool sync() = 0;

	// return true for success and false for failure
	virtual bool disconnect() { return sync(); };

	// return estimate of characters remaining in input stream. -1 for failure
	virtual streamsize showmanyc() = 0;

	// handle seekoff for device
	virtual pos_type seekoff( off_type off, std::ios_base::seekdir way, std::ios_base::openmode which )
	{ return pos_type( -1 ); }

	// handle seekpos for device
	virtual pos_type seekpos( pos_type sp, std::ios_base::openmode which )
	{ return pos_type( -1 ); }

	virtual ~device_wrapper() { }
};

// T: Character type
// BufSize: Size of get/put area. Minimum value is pbSize+1
// pbSize: part of get area reserved for putback. Minimum value is 1
template <typename T, int BufSize=64, int pbSize=1>
class shared_buffer
{
public:
	shared_buffer()
		: buffer_( internal_buffer_ ), buffer_size_( BufSize ), put_back_size_( pbSize )
	{ }

	// use external buffer pointed to by s.
	bool setbuf( T* s, std::streamsize n )
	{
		if( s==0 && n==0 )
		{
			buffer_ = 0;
			buffer_size_ = 0;
		}
		else if( s != 0 || n > 1 )
		{
			buffer_ = s;
			buffer_size_ = n;
			put_back_size_ = n > pbSize ? pbSize : n-1;
		}
		else
		{
			return false;
		}

		return true;
	}


	bool is_buffered()		const	{ return buffer_ != 0; }
	bool areas_overlap()	const	{ return true; }

	// put area
	T* put_area()					{ return buffer_; }
	int put_area_size()		const	{ return buffer_size_; }

	// get area
	T* get_area()					{ return buffer_; }
	int get_area_size()		const	{ return buffer_size_; }
	int put_back_size()		const	{ return put_back_size_; }

private:
	// internal get/put area
	T internal_buffer_[ BufSize ];

	// stuff to handle both internal and external buffers
	T* buffer_;
	std::streamsize buffer_size_;
	int put_back_size_;
};

// T: Character type
// BufSize: Size of get/put area. Minimum value is pbSize+1
// pbSize: part of get area reserved for putback. Minimum value is 1
template <typename T, int BufSize=64, int pbSize=1>
class dual_buffer
{
public:
	dual_buffer()
		: unbuffered_( false )
	{ }

	bool setbuf( T* s, std::streamsize n )
	{
		if( s==0 && n==0 )
		{
			unbuffered_ = true;
		}
		else if( s != 0 || n > 1 )
		{
			unbuffered_ = false;
		}
		else
		{
			return false;
		}
		return true;
	}

	bool is_buffered()		const	{ return !unbuffered_; }
	bool areas_overlap()	const	{ return false; }

	// put area
	T* put_area()					{ return internal_put_buffer_; }
	int put_area_size()		const	{ return BufSize; }

	// get area
	T* get_area()					{ return internal_get_buffer_; }
	int get_area_size()		const	{ return BufSize; }
	int put_back_size()		const	{ return pbSize; }

private:
	// internal put area
	T internal_put_buffer_[ BufSize ];
	// internal get area
	T internal_get_buffer_[ BufSize ];

	// stuff to handle both internal and external buffers
	bool unbuffered_;
};

/*
Virtual methods in basic_streambuf. See 27.5.4.2
Local
imbue:		used to notify the buffer of changes in the used local
Buffer managment
setbuf:		user defined! setbuf(0,0) means unbuffered. See 27.5.4.2.4
seekoff:		alter the stream position in a user defined way! See 27.5.4.2.4
seekpos:		alter the stream position in a user defined way! See 27.5.4.2.4
sync:		syncronice buffers with stream. On failure return -1
Get area
showmanyc:	estimate number of character available. If -1 overflow will fail
xsgetn:		read up to n character from stream. Return number of character read
underflow:	called if buffer is empty. Returns eof to indicate failure
uflow:		call underflow, the return next character in stream. Returns eof to indicate failure
Putback
pbackfail:	push a read character back into the stream. Returns eof to indicate failure
Put area
xsputn:		write n character to the stream. Return number of character written or eof for failure
overflow:	called to flush buffer. Returns eof to indicate failure

transitions between input and output state
	in -> out: seekpos(out), seekoff(out), write after read to eof
	out -> in: sync, seekpos(in), seekoff(in)
*/


// TODO handle text and binary input/output
// TODO investigate requiretment on exception
// TODO handle wide character to multi byte character

// T: Character trait type
// BufSize: Size of get/put area. Minimum value is pbSize+1
// pbSize: part of get area reserved for putback. Minimum value is 1
template <typename T, typename buffer_trait>
class streambuf_impl : public std::basic_streambuf< typename T::char_type, T>
{
public:
	typedef typename T::char_type	char_type;
	typedef T						traits_type;
	typedef typename T::int_type	int_type;
	typedef typename T::pos_type	pos_type;
	typedef typename T::off_type	off_type;

	streambuf_impl( device_wrapper<T>* d );
	~streambuf_impl();

protected:
	// buffer managment and position
	std::basic_streambuf< typename T::char_type, T>* setbuf( char_type* s, std::streamsize n );
	pos_type seekoff( off_type off, std::ios_base::seekdir way, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out );
	pos_type seekpos( pos_type sp, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out );
	int sync();

	// get area
	streamsize showmanyc();
	std::streamsize xsgetn( char_type* s, std::streamsize n );
	int_type underflow();
	int_type uflow();

	// putback
	int_type pbackfail( int_type c );

	// put area
	std::streamsize xsputn( const char_type* s, std::streamsize n );
	int_type overflow( int_type c = traits_type::eof() );

private:
	// prohibit copying
	streambuf_impl( const streambuf_impl& );
	streambuf_impl& operator= ( const streambuf_impl& );

private:
	int read( char_type* s, std::streamsize n );
	int write( const char_type* s, std::streamsize n );

private:
	// get/put area
	buffer_trait buffer_;

	// place to put back character in unbuffered mode
	int_type putback;

	// handle text/binary transfer
	std::ios_base::openmode mode;

protected:
	// pointer to device specific class
	device_wrapper<T>* const dw;
};


template <typename T, typename buffer_trait>
streambuf_impl<T, buffer_trait>::streambuf_impl( device_wrapper<T>* d )
	: putback( traits_type::eof() ),
	mode( std::ios_base::in | std::ios_base::out | std::ios_base::binary ),
	dw( d )
{
	this->setp( 0, 0 );
	this->setg( 0, 0, 0 );
}

template <typename T, typename buffer_trait>
streambuf_impl<T, buffer_trait>::~streambuf_impl()
{
	overflow( traits_type::eof() );
	dw->disconnect();

	delete dw;
}


///////////////////////////////////////////
// buffer managment and position

template <typename T, typename buffer_trait>
std::basic_streambuf< typename T::char_type, T>*
streambuf_impl<T, buffer_trait>::setbuf( char_type* s, std::streamsize n )
{	// setbuf( 0, 0 ) makes I/O unbuffered. Otherwise uses s (having length n) as the new get/put area
	if( buffer_.setbuf( s, n ) )
	{
		this->setp( 0, 0 );
		this->setg( 0, 0, 0 );

		putback = traits_type::eof();

		return this;
	}

	return 0;
}

template <typename T, typename buffer_trait>
typename streambuf_impl<T, buffer_trait>::pos_type
streambuf_impl<T, buffer_trait>::seekoff( off_type off, std::ios_base::seekdir way, std::ios_base::openmode which )
{
	return ( sync() == -1 ) ? pos_type( -1 ) : dw->seekoff( off, way, which );
}

template <typename T, typename buffer_trait>
typename streambuf_impl<T, buffer_trait>::pos_type
streambuf_impl<T, buffer_trait>::seekpos( pos_type sp, std::ios_base::openmode which )
{
	return ( sync() == -1 ) ? pos_type( -1 ) : dw->seekpos( sp, which );
}

// return -1 on failure. Default behaviour: return 0.
template <typename T, typename buffer_trait>
int streambuf_impl<T, buffer_trait>::sync()
{
	const int res = ( traits_type::eq_int_type( traits_type::eof(), overflow( traits_type::eof() ) ) || dw->sync() == false ) ? -1 : 0;

	this->setp( 0, 0 );

	if( buffer_.areas_overlap() )
	{
		putback = traits_type::eof();
		this->setg( 0, 0, 0 );
	}

	return res;
}


///////////////////////////////////////////
// get area

template <typename T, typename buffer_trait>
streamsize streambuf_impl<T, buffer_trait>::showmanyc()
{
	int unread = traits_type::eq_int_type( putback, traits_type::eof() ) ? 0 : 1;
	return dw->showmanyc() + unread;
}


template <typename T, typename buffer_trait>
std::streamsize streambuf_impl<T, buffer_trait>::xsgetn( char_type* s, std::streamsize n )
{
	std::streamsize ns = 0;
	while( 0 < n )
	{
		if( this->gptr() && this->gptr() < this->egptr() )
		{
			std::streamsize m = this->egptr() - this->gptr();
			if( n < m )
				m = n;
			traits_type::copy( s, this->gptr(), m );
			s += m;
			ns += m;
			n -= m;
			this->gbump( m );
		}
		else
		{
			const int_type c = this->sbumpc();
			if( !traits_type::eq_int_type( traits_type::eof(), c ) )
			{
				*s = traits_type::to_char_type( c );
				++ns;
				++s;
				--n;
			}
			else
				break;
			}
	}

	return ns;
}

template <typename T, typename buffer_trait>
typename streambuf_impl<T, buffer_trait>::int_type
streambuf_impl<T, buffer_trait>::underflow()
{
	if( buffer_.is_buffered() )
	{
		if( this->gptr() != 0 )
		{
			if( this->gptr() < this->egptr() )
				return traits_type::to_int_type( *this->gptr() );

			const int n = this->gptr()-this->eback();

			std::streamsize numPutbacks = n < buffer_.put_back_size() ? n : buffer_.put_back_size();

			traits_type::move( buffer_.get_area() + (buffer_.put_back_size()-numPutbacks), this->gptr()-numPutbacks, numPutbacks );

			char_type* s = buffer_.get_area() + buffer_.put_back_size();

			setg( s-numPutbacks, s, s );
		}
		else
		{	// switch to get state
			if( buffer_.areas_overlap() && this->pptr() != 0 ) return traits_type::eof();

			char_type* s = buffer_.get_area() + buffer_.put_back_size();
			setg( s, s, s );
		}

		const int retval = read( buffer_.get_area() + buffer_.put_back_size(), buffer_.get_area_size() - buffer_.put_back_size() );
		if( retval <= 0 )
		{
			this->setg( 0, 0, 0 );
			return traits_type::eof();
		}

		setg( this->eback(), this->gptr(), buffer_.get_area() + buffer_.put_back_size() + retval );

		return traits_type::to_int_type( *this->gptr() );
	}
	else
	{
		return pbackfail( uflow() );
	}
}

template <typename T, typename buffer_trait>
typename streambuf_impl<T, buffer_trait>::int_type
streambuf_impl<T, buffer_trait>::uflow()
{
	if( buffer_.is_buffered() )
	{
		if( traits_type::eq_int_type( traits_type::eof(), underflow() ) )
			return traits_type::eof();

		return this->sbumpc();
	}
	else
	{
		if( traits_type::eq_int_type( putback, traits_type::eof() ) )
		{
			char_type c;
			if( read( &c, 1 ) > 0 )
				return traits_type::to_int_type( c );
			return traits_type::eof();
		}
		else
		{
			const int_type c = putback;
			putback = traits_type::eof();
			return c;
		}
	}
}


///////////////////////////////////////////
// putback

template <typename T, typename buffer_trait>
typename streambuf_impl<T, buffer_trait>::int_type
streambuf_impl<T, buffer_trait>::pbackfail( int_type c )
{
	if( this->gptr() != 0 )
	{
		if( this->gptr() != this->eback() )
		{
			this->gbump( -1 );
			if( !traits_type::eq_int_type( c, traits_type::eof() ) )
				*(this->gptr()) = traits_type::to_char_type( c );
			return traits_type::not_eof( c );
		}
	}
	else if( !buffer_.is_buffered() )
	{
		if( traits_type::eq_int_type( putback, traits_type::eof() ) )
		{
			putback = c;
			return c;
		}
	}

	return traits_type::eof();
}


///////////////////////////////////////////
// put area

template <typename T, typename buffer_trait>
std::streamsize streambuf_impl<T, buffer_trait>::xsputn( const char_type* s, std::streamsize n )
{
	std::streamsize ns = 0;
	while( 0 < n )
	{
		std::streamsize m = this->epptr() - this->pptr();
		if( this->pptr() != 0 && 0 < m )
		{
			if( n < m )
				m = n;
			traits_type::copy( this->pptr(), s, m );
			s += m;
			ns += m;
			n -= m;
			this->pbump( m );
		}
		else if( !traits_type::eq_int_type( traits_type::eof(), overflow( traits_type::to_int_type( *s ) ) ) )
		{
			++s;
			++ns;
			--n;
		}
		else
			break;
	}
	return ns;
}

template <typename T, typename buffer_trait>
typename streambuf_impl<T, buffer_trait>::int_type
streambuf_impl<T, buffer_trait>::overflow( int_type c )
{
	if( buffer_.is_buffered() )
	{
		if( this->pptr() != 0 )
		{
			int n = this->pptr() - this->pbase();
			if(  write( buffer_.put_area(), n ) < 0 )
				return traits_type::eof();

			this->pbump( -n );
		}
		else
		{	// switch to put state
			if( buffer_.areas_overlap() && this->gptr() != 0 ) return traits_type::eof();

			setp( buffer_.put_area(), buffer_.put_area() + buffer_.put_area_size() );
		}

		if( !traits_type::eq_int_type( c, traits_type::eof() ) )
		{
			*this->pptr() = traits_type::to_char_type( c );
			this->pbump( 1 );
		}
	}
	else
	{
		if( !traits_type::eq_int_type( c, traits_type::eof() ) )
		{
			const char_type ch = traits_type::to_char_type( c );
			if( write( &ch, 1 ) < 0 )
				return traits_type::eof();
		}
	}

	return traits_type::not_eof( c );
}

///////////////////////////////////////////
// private functions

template <typename T, typename buffer_trait>
int streambuf_impl<T, buffer_trait>::read( char_type* s, std::streamsize n )
{
	if( mode & std::ios_base::binary )
	{	// binary mode
		return dw->read( s, n );
	}
	else
	{	// text mode

		return dw->read( s, n );
	}
}

template <typename T, typename buffer_trait>
int streambuf_impl<T, buffer_trait>::write( const char_type* s, std::streamsize n )
{
	if( mode & std::ios_base::binary )
	{	// binary mode
		return dw->write( s, n );
	}
	else
	{	// text mode
		return dw->write( s, n );
	}
}

#endif //__STREAMBUFIMPL_H_INCLUDED__
