/* Copyright (C) 2010 to 2013 Chris Vine

The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:

   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; either version 2.1 of
   the License, or (at your option) any later version.

   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, version 2.1, along with this library (see the file LGPL.TXT
   which came with this source code package in the c++-gtk-utils
   sub-directory); if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

However, it is not intended that the object code of a program whose
source code instantiates a template from this file or uses macros or
inline functions (of any length) should by reason only of that
instantiation or use be subject to the restrictions of use in the GNU
Lesser General Public License.  With that in mind, the words "and
macros, inline functions and instantiations of templates (of any
length)" shall be treated as substituted for the words "and small
macros and small inline functions (ten lines or less in length)" in
the fourth paragraph of section 5 of that licence.  This does not
affect any other reason why object code may be subject to the
restrictions in that licence (nor for the avoidance of doubt does it
affect the application of section 2 of that licence to modifications
of the source code in this file).

*/

#ifndef CGU_GSTREAM_TPP
#define CGU_GSTREAM_TPP

namespace Cgu {

// provide the gstreambuf class methods

template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::set_input_error(GError* err) {
  g_object_set_data_full((GObject*)input_stream.get(),
			 "CGU-ERROR-KEY",
			 err,
			 (GDestroyNotify)&g_error_free);
}

template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::set_output_error(GError* err) {
  g_object_set_data_full((GObject*)output_stream.get(),
			 "CGU-ERROR-KEY",
			 err,
			 (GDestroyNotify)&g_error_free);
}

template <class charT , class Traits>
GError* basic_gstreambuf<charT , Traits>::is_input_error() {
  return input_stream.get() ? static_cast<GError*>(g_object_get_data((GObject*)input_stream.get(),
								     "CGU-ERROR-KEY"))
                            : 0;
}

template <class charT , class Traits>
GError* basic_gstreambuf<charT , Traits>::is_output_error() {
  return output_stream.get() ? static_cast<GError*>(g_object_get_data((GObject*)output_stream.get(),
								      "CGU-ERROR-KEY"))
                             : 0;
}

// static function to pass to std::for_each() to carry out
// the wchar_t byte swapping
template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::swap_element(char_type& c) {
  // this is by no means the most efficient implementation (a bit
  // shifting implementation is likely to be more efficient) but
  // it is the most portable. The underlying type of wchar_t can be
  // signed (and is in libc++-3), and the problem we have is that until
  // swapped, the sign bit is in fact just bit 1 in the incoming text
  // and is entirely random.  So to avoid left shifting a negative number
  // (which is undefined behaviour) we would have to cast to uint32, but
  // that cast is bit altering on non-2s complement systems if the sign
  // bit is set.  So avoid a bit shifting implementation.
  unsigned char* p = reinterpret_cast<unsigned char*>(&c);
  unsigned char tmp;
  if (sizeof(char_type) == 4) {  // let the optimiser elide whichever block
                                 // cannot be reached
    tmp = p[0]; // swap bytes 1 and 4
    p[0] = p[3];
    p[3] = tmp;
    tmp = p[1]; // swap bytes 2 and 3
    p[1] = p[2];
    p[2] = tmp;
  }
  else if (sizeof(char_type) == 2) {
    tmp = p[0];
    p[0] = p[1];
    p[1] = tmp;
  }
}

template <class charT , class Traits>
bool basic_gstreambuf<charT , Traits>::is_output_stored() {
  return (this->pbase() != this->pptr());
}

template <class charT , class Traits>
bool basic_gstreambuf<charT , Traits>::is_input_stored() {
  return (this->eback() != this->gptr() || this->gptr() != this->egptr());
}

template <class charT , class Traits>
GobjHandle<GInputStream>
basic_gstreambuf<charT , Traits>::find_base_input_stream(const GobjHandle<GInputStream>& input_stream) {

  GInputStream* current = input_stream.get();
  while (G_IS_FILTER_INPUT_STREAM(current)) {
    GInputStream* parent = current;
    current = g_filter_input_stream_get_base_stream((GFilterInputStream*)current);
    g_filter_input_stream_set_close_base_stream((GFilterInputStream*)parent, false);
    g_input_stream_close(parent, 0, 0);  // there is no point in reporting errors here
  }

  g_object_ref(current);
  return GobjHandle<GInputStream>(current);
}

template <class charT , class Traits>
GobjHandle<GOutputStream>
basic_gstreambuf<charT , Traits>::find_base_output_stream(const GobjHandle<GOutputStream>& output_stream) {

  GOutputStream* current = output_stream.get();
  while (G_IS_FILTER_OUTPUT_STREAM(current)) {
    GOutputStream* parent = current;
    current = g_filter_output_stream_get_base_stream((GFilterOutputStream*)current);
    g_filter_output_stream_set_close_base_stream((GFilterOutputStream*)parent, false);
    g_output_stream_close(parent, 0, 0);  // there is no point in reporting errors here
  }

  g_object_ref(current);
  return GobjHandle<GOutputStream>(current);
}

template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::reset_input_buffer_pointers() {
  this->setg(input_buffer.get() + putback_size,     // beginning of putback area
	     input_buffer.get() + putback_size,     // read position
	     input_buffer.get() + putback_size);    // end position
}

template <class charT , class Traits>
basic_gstreambuf<charT , Traits>::basic_gstreambuf():
				  manage(false),
				  byteswap(false),
				  seek_mismatch(false),
				  seekable(false) {

  // leave input_stream, output_stream, io_stream,
  // input_buffer and output_buffer smart handles as empty
  // and leave the output and input buffer pointers as NULL
}

template <class charT , class Traits>
basic_gstreambuf<charT , Traits>::basic_gstreambuf(const GobjHandle<GOutputStream>& output_stream_,
		                                   bool manage_,
						   const GobjHandle<GConverter>& converter_):
				  manage(manage_),
				  byteswap(false),
				  seek_mismatch(false) {

  if (!output_stream_.get()) {
    manage = false;
    seekable = false;
    return;
  }

  output_stream = find_base_output_stream(output_stream_);
  if (converter_.get()) {
    output_stream.reset(g_converter_output_stream_new(output_stream, converter_));
    seekable = false;
  }
  else {
    // as we are attaching a GOutputStream object, we do not need to test
    // for seek_mismatch
    seekable = (G_IS_SEEKABLE(output_stream.get())
                && g_seekable_can_seek((GSeekable*)output_stream.get()));
  }

#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
  output_buffer.reset(static_cast<char_type*>(g_slice_alloc(output_buf_size * sizeof(char_type))));
#else
  output_buffer.reset(new char_type[output_buf_size]);
#endif

  this->setp(output_buffer.get(), output_buffer.get() + output_buf_size);
  // leave the input buffer pointers as NULL
}

template <class charT , class Traits>
basic_gstreambuf<charT , Traits>::basic_gstreambuf(const GobjHandle<GInputStream>& input_stream_,
		                                   bool manage_,
						   const GobjHandle<GConverter>& converter_):
				  manage(manage_),
				  byteswap(false),
				  seek_mismatch(false) {

  if (!input_stream_.get()) {
    manage = false;
    seekable = false;
    return;
  }

  input_stream = find_base_input_stream(input_stream_);
  if (converter_.get()) {
    input_stream.reset(g_converter_input_stream_new(input_stream, converter_));
    seekable = false;
  }
  else {
    // as we are attaching a GInputStream object, we do not need to test
    // for seek_mismatch
    seekable = (G_IS_SEEKABLE(input_stream.get())
                && g_seekable_can_seek((GSeekable*)input_stream.get()));
  }

#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
  input_buffer.reset(static_cast<char_type*>(g_slice_alloc((input_buf_size + putback_size)
                                                           * sizeof(char_type))));
#else
  input_buffer.reset(new char_type[input_buf_size + putback_size]);
#endif

  reset_input_buffer_pointers();
  // leave the put buffer pointers as NULL.
}

template <class charT , class Traits>
basic_gstreambuf<charT , Traits>::basic_gstreambuf(const GobjHandle<GIOStream>& io_stream_,
		                                   bool manage_,
		   				   const GobjHandle<GConverter>& input_converter_,
		   				   const GobjHandle<GConverter>& output_converter_):
                                  input_stream(io_stream_.get() ?
                                               g_io_stream_get_input_stream(io_stream_) :
					       0),
                                  output_stream(io_stream_.get() ?
				                g_io_stream_get_output_stream(io_stream_) :
						0),
				  io_stream(io_stream_),
				  manage(manage_),
				  byteswap(false),
				  seek_mismatch(false) {

  if (!io_stream_.get()) {
    manage = false;
    seekable = false;
    return;
  }

  // acquire an additional reference to input_stream and output_stream
  // as the GIOStream object owns them
  g_object_ref(input_stream.get());
  g_object_ref(output_stream.get());

  // can these ever be filter streams?  I don't think so but do the test anyway
  input_stream = find_base_input_stream(input_stream);
  if (input_converter_.get()) {
    // check for seek mismatch
    if (G_IS_SEEKABLE(input_stream.get())
        && g_seekable_can_seek((GSeekable*)input_stream.get()))
      seek_mismatch = true;
    input_stream.reset(g_converter_input_stream_new(input_stream, input_converter_));
    seekable = false;
  }

  output_stream = find_base_output_stream(output_stream);
  if (output_converter_.get()) {
    // check for seek mismatch
    if (!seek_mismatch
        && G_IS_SEEKABLE(output_stream.get())
        && g_seekable_can_seek((GSeekable*)output_stream.get()))
      seek_mismatch = true;
    output_stream.reset(g_converter_output_stream_new(output_stream, output_converter_));
    seekable = false;
  }
  else if (!input_converter_.get()) {
    seekable = (G_IS_SEEKABLE(output_stream.get())
                && g_seekable_can_seek((GSeekable*)output_stream.get()));
  }

  // have a general rule that any GFileIOStream must be seekable or a seek
  // mismatch arises, even if there is no converter.  This duplicates
  // some of the above and is highly defensive: we need to catch any file
  // system which permits read-write opening of files and reports itself
  // as not supporting random access, but which in fact maintains a file
  // position pointer which is shared for reading and writing.  This is
  // a broad brush way of doing that which is subject to refinement
  if (!seekable && G_IS_FILE_IO_STREAM(io_stream_.get()))
    seek_mismatch = true;

#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
  output_buffer.reset(static_cast<char_type*>(g_slice_alloc(output_buf_size * sizeof(char_type))));
#else
  output_buffer.reset(new char_type[output_buf_size]);
#endif

#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
  input_buffer.reset(static_cast<char_type*>(g_slice_alloc((input_buf_size + putback_size)
                                                           * sizeof(char_type))));
#else
  input_buffer.reset(new char_type[input_buf_size + putback_size]);
#endif

  this->setp(output_buffer.get(), output_buffer.get() + output_buf_size);
  reset_input_buffer_pointers();
}

template <class charT , class Traits>
basic_gstreambuf<charT , Traits>::~basic_gstreambuf() {
  if (manage) close_stream(); // this will also call sync()
  else {
    basic_gstreambuf<charT , Traits>::sync(); // checks whether there is an output stream
    if (!is_output_stored())  // sync() succeeded
      wind_back_input_buffer(); // since we are not closing the stream, wind back the file
                                // pointer on an input stream to the logical position.
				// wind_back_input_buffer() checks whether there is an
				// input stream
  }
}

// sync returns 0 for success and -1 for error
template <class charT , class Traits>
int basic_gstreambuf<charT , Traits>::sync() {

  // if the rules for managing GIOStream objects explained
  // in the documentation are followed, there should never be
  // anything pending in both the output buffers and input buffers
  // on anything other than an intrinsically unseekable device
  // such as socket: if there is, the stream is in an inconsistent
  // state and flushing will make matters worse. The best test for
  // a socket with a GIOStream attached is whether (!seekable && !seek_mismatch)
  if (is_output_stored() && is_input_stored() && (seekable || seek_mismatch))
    return -1;
  return flush_buffer();  // flush even if is_output_stored() == 0: we might have
                          // unbuffered output and be using a converter
}

template <class charT , class Traits>
bool basic_gstreambuf<charT , Traits>::close_stream() {
  bool ret;
  if (!output_stream.get()
      && !input_stream.get()
      && !io_stream.get())
    ret = false; // no stream attached
  else
    ret = true;

  if (output_stream.get()) {
    if (basic_gstreambuf<charT , Traits>::sync() == -1)
      ret = false;
    GError* error = 0;
    g_output_stream_close(output_stream, 0, &error);
    if (error) {
      GerrorScopedHandle handle_h(error);
      g_warning("Cgu::basic_gstreambuf<charT , Traits>::close_stream(): %s\n", error->message);
      ret = false;
    }
    output_stream.reset();
    output_buffer.reset();
    this->setp(0, 0);
  }

  if (input_stream.get()) {
    GError* error = 0;
    g_input_stream_close(input_stream, 0, &error);
    if (error) {
      GerrorScopedHandle handle_h(error);
      g_warning("Cgu::basic_gstreambuf<charT , Traits>::close_stream(): %s\n", error->message);
      ret = false;
    }
    input_stream.reset();
    input_buffer.reset();
    this->setg(0, 0, 0);
  }

  if (io_stream.get()) {
    // we will already have sync()'ed in the first 'if' block so
    // just close the GIOStream object: note that the input and
    // output streams in that object may be different from those
    // maintained by this streambuffer - we may be  converting - so
    // the GIOStream object may still have a number of streams to
    // close here
    GError* error = 0;
    g_io_stream_close(io_stream, 0, &error);
    if (error) {
      GerrorScopedHandle handle_h(error);
      g_warning("Cgu::basic_gstreambuf<charT , Traits>::close_stream(): %s\n", error->message);
      ret = false;
    }
    io_stream.reset();
  }
  return ret;
}

// flush_buffer() returns 0 for success and -1 for error
template <class charT , class Traits>
int basic_gstreambuf<charT , Traits>::flush_buffer() {

  if (!output_stream.get()) return 0; // if there is no output stream, flushing
                                      // is redundant but not an error

  std::size_t count = this->pptr() - this->pbase();

  if (count) {
    gsize written;
    GError* error = 0;
    g_output_stream_write_all(output_stream,
                              output_buffer.get(),
			      count * sizeof(char_type),
			      &written,
			      0,
			      &error);

    if (error) set_output_error(error);
    if (written != count * sizeof(char_type)) return -1;
    this->pbump(-count);
  }

  // since this is not a buffered GIO stream, this is probably
  // unnecessary unless a converter stream keeps stuff lying around
  GError* error = 0;
  g_output_stream_flush(output_stream, 0, &error);
  if (error) {
    GerrorScopedHandle handle_h(error);
    g_warning("Cgu::basic_gstreambuf<charT , Traits>::flush_buffer(): %s\n", error->message);
    return -1;
  }
  return 0;
}

// returns true on success, false on failure
template <class charT , class Traits>
bool basic_gstreambuf<charT , Traits>::wind_back_input_buffer() {

  if (!input_stream.get())   // if there is no input stream, then winding back
    return true;             // is redundant but not an error

  if (seek_mismatch)         // this is only relevant if a GIOStream object has been
    return false;            // been attached: if one has been, it is an error if we
                             // we cannot seek but the underlying device is seekable,
                             // because then, if there is anything in the input buffer,
                             // with a GIOStream object attached the stream is in an
                             // inconsistent state

  if (!seekable)             // if we cannot seek and there is no seek mismatch,
    return true;             // then winding back is redundant but not an error


  goffset real_pos = g_seekable_tell((GSeekable*)input_stream.get());
  real_pos -= ((this->egptr() - this->gptr()) * sizeof(char_type));
  if (real_pos >= 0) {
    gboolean success = g_seekable_seek((GSeekable*)input_stream.get(),
                                       real_pos,
      			 	       G_SEEK_SET,
		      		       0,
				       0);  // the return value is sufficient to deal with an error
    if (success) {
      reset_input_buffer_pointers();
      return true;
    }
  }
  return false;
}

template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::attach_stream(const GobjHandle<GOutputStream>& output_stream_,
                                                     bool manage_,
						     const GobjHandle<GConverter>& converter_) {
  if (manage)
    close_stream(); // this will also call sync() where relevant
  else {
    basic_gstreambuf<charT , Traits>::sync();
    if (!is_output_stored())  // sync() succeeded
      wind_back_input_buffer(); // since we are not closing the stream, wind back the file
                                // pointer on an input stream to the logical position.
				// wind_back_input_buffer() checks whether there is an
				// input stream

    input_stream.reset();
    output_stream.reset();
    io_stream.reset();
    input_buffer.reset();
    output_buffer.reset();
    this->setg(0, 0, 0);
    this->setp(0, 0);
  }

  if (!output_stream_.get()) {
    manage = false;
    byteswap = false;
    seek_mismatch = false;
    seekable = false;
    return;
  }

  output_stream = find_base_output_stream(output_stream_);
  if (converter_.get()) {
    output_stream.reset(g_converter_output_stream_new(output_stream, converter_));
    seekable = false;
  }
  else {
    // as we are attaching a GOutputStream object, we do not need to test
    // for seek_mismatch
    seekable = (G_IS_SEEKABLE(output_stream.get())
                && g_seekable_can_seek((GSeekable*)output_stream.get()));
  }

#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
  output_buffer.reset(static_cast<char_type*>(g_slice_alloc(output_buf_size * sizeof(char_type))));
#else
  output_buffer.reset(new char_type[output_buf_size]);
#endif

  this->setp(output_buffer.get(), output_buffer.get() + output_buf_size);

  manage = manage_;
  byteswap = false;
  seek_mismatch = false;
}

template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::attach_stream(const GobjHandle<GInputStream>& input_stream_,
                                                     bool manage_,
						     const GobjHandle<GConverter>& converter_) {
  if (manage)
    close_stream(); // this will also call sync() where relevant
  else {
    basic_gstreambuf<charT , Traits>::sync();
    if (!is_output_stored())  // sync() succeeded
      wind_back_input_buffer(); // since we are not closing the stream, wind back the file
                                // pointer on an input stream to the logical position.
				// wind_back_input_buffer() checks whether there is an
				// input stream

    input_stream.reset();
    output_stream.reset();
    io_stream.reset();
    input_buffer.reset();
    output_buffer.reset();
    this->setg(0, 0, 0);
    this->setp(0, 0);
  }  

  if (!input_stream_.get()) {
    manage = false;
    byteswap = false;
    seek_mismatch = false;
    seekable = false;
    return;
  }

  input_stream = find_base_input_stream(input_stream_);
  if (converter_.get()) {
    input_stream.reset(g_converter_input_stream_new(input_stream, converter_));
    seekable = false;
  }
  else {
    // as we are attaching a GInputStream object, we do not need to test
    // for seek_mismatch
    seekable = (G_IS_SEEKABLE(input_stream.get())
                && g_seekable_can_seek((GSeekable*)input_stream.get()));
  }

#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
  input_buffer.reset(static_cast<char_type*>(g_slice_alloc((input_buf_size + putback_size)
                                                           * sizeof(char_type))));
#else
  input_buffer.reset(new char_type[input_buf_size + putback_size]);
#endif

  reset_input_buffer_pointers();

  manage = manage_;
  byteswap = false;
  seek_mismatch = false;
}

template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::attach_stream(const GobjHandle<GIOStream>& io_stream_,
                                                     bool manage_,
		   				     const GobjHandle<GConverter>& input_converter_,
		   				     const GobjHandle<GConverter>& output_converter_) {
  if (manage)
    close_stream(); // this will also call sync() where relevant
  else {
    basic_gstreambuf<charT , Traits>::sync();
    if (!is_output_stored())  // sync() succeeded
      wind_back_input_buffer(); // since we are not closing the stream, wind back the file
                                // pointer on an input stream to the logical position.
				// wind_back_input_buffer() checks whether there is an
				// input stream

    input_stream.reset();
    output_stream.reset();
    io_stream.reset();
    input_buffer.reset();
    output_buffer.reset();
    this->setg(0, 0, 0);
    this->setp(0, 0);
  }

  if (!io_stream_.get()) {
    manage = false;
    byteswap = false;
    seek_mismatch = false;
    seekable = false;
    return;
  }

  input_stream.reset(g_io_stream_get_input_stream(io_stream_));
  output_stream.reset(g_io_stream_get_output_stream(io_stream_));
  // acquire an additional reference to input_stream and output_stream
  // as the GIOStream object owns them
  g_object_ref(input_stream.get());
  g_object_ref(output_stream.get());
  
  // can these ever be filter streams?  I don't think so but do the test anyway
  input_stream = find_base_input_stream(input_stream);
  if (input_converter_.get()) {
    // check for seek mismatch
    if (G_IS_SEEKABLE(input_stream.get())
        && g_seekable_can_seek((GSeekable*)input_stream.get()))
      seek_mismatch = true;
    else
      seek_mismatch = false;
    input_stream.reset(g_converter_input_stream_new(input_stream, input_converter_));
    seekable = false;
  }
  else
    seek_mismatch = false;

  output_stream = find_base_output_stream(output_stream);
  if (output_converter_.get()) {
    // check for seek mismatch
    if (!seek_mismatch
        && G_IS_SEEKABLE(output_stream.get())
        && g_seekable_can_seek((GSeekable*)output_stream.get()))
      seek_mismatch = true;
    output_stream.reset(g_converter_output_stream_new(output_stream, output_converter_));
    seekable = false;
  }
  else if (!input_converter_.get()) {
    seekable = (G_IS_SEEKABLE(output_stream.get())
                && g_seekable_can_seek((GSeekable*)output_stream.get()));
  }

  // have a general rule that any GFileIOStream must be seekable or a seek
  // mismatch arises, even if there is no converter.  This duplicates
  // some of the above and is highly defensive: we need to catch any file
  // system which permits read-write opening of files and reports itself
  // as not supporting random access, but which in fact maintains a file
  // position pointer which is shared for reading and writing.  This is
  // a broad brush way of doing that which is subject to refinement
  if (!seekable && G_IS_FILE_IO_STREAM(io_stream_.get()))
    seek_mismatch = true;

  io_stream = io_stream_;

#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
  output_buffer.reset(static_cast<char_type*>(g_slice_alloc(output_buf_size * sizeof(char_type))));
#else
  output_buffer.reset(new char_type[output_buf_size]);
#endif

#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
  input_buffer.reset(static_cast<char_type*>(g_slice_alloc((input_buf_size + putback_size)
                                                           * sizeof(char_type))));
#else
  input_buffer.reset(new char_type[input_buf_size + putback_size]);
#endif

  this->setp(output_buffer.get(), output_buffer.get() + output_buf_size);
  reset_input_buffer_pointers();

  manage = manage_;
  byteswap = false;
}

template <class charT , class Traits>
typename basic_gstreambuf<charT , Traits>::int_type
basic_gstreambuf<charT , Traits>::overflow(int_type c) {

  if (!output_stream.get()) return traits_type::eof();

  // the next block is only relevant if we have a GIOStream object
  // attached (otherwise is_input_stored() will return false).  If we do,
  // we need to check this because if output buffering is off, xsputn()
  // or this method will always be called on a write and so can do the
  // synchronisation, rather than require the user to do so manually:
  // if output buffering is on the user must do it manually as many
  // writes will call sputc() to load into the buffer
  if (is_input_stored()) {

    // first check if the stream is in an inconsistent state
    if ((is_output_stored() && seekable)    // stream in inconsistent state
	|| !wind_back_input_buffer())       // wind back fails where underlying
                                            // device seekable
      return traits_type::eof();
  }

  if (!output_buffer.get()) {
    // we are not buffered
    if (!traits_type::eq_int_type(c, traits_type::eof())) {
      gsize count;
      GError* error = 0;
      const char_type letter = c;
      g_output_stream_write_all(output_stream,
				&letter,
				sizeof(char_type),
				&count,
				0,
				&error);
                                           
      if (error) {
	set_output_error(error);
	return traits_type::eof();
      }
      return c;
    }
    return traits_type::not_eof(c);
  }

  // we are buffered
  // the stream must be full: empty buffer and then put c in buffer
  if (flush_buffer() == -1) return traits_type::eof();
  if (traits_type::eq_int_type(traits_type::eof(), c)) return traits_type::not_eof(c);
  // write c to output buffer
  return this->sputc(c);
}

#ifndef CGU_GSTREAM_USE_STD_N_READ_WRITE
template <class charT , class Traits>
std::streamsize basic_gstreambuf<charT , Traits>::xsputn(const char_type* source_p, std::streamsize num) {

  if (!output_stream.get()) return 0;

  // the next block is only relevant if we have a GIOStream object
  // attached (otherwise is_input_stored() will return false).  If we do,
  // we need to check this because if output buffering is off, overflow()
  // or this method will always be called on a write and so can do the
  // synchronisation, rather than require the user to do so manually:
  // if output buffering is on the user must do it manually as many
  // writes will call sputc() to load into the buffer
  if (is_input_stored()) {

    // first check if the stream is in an inconsistent state
    if ((is_output_stored() && seekable)    // stream in inconsistent state
	|| !wind_back_input_buffer())       // wind back fails where underlying
                                            // device seekable
      return 0;
  }

  // if num is less than or equal to the space in the buffer, put it in the buffer
  if (output_buffer.get() && num <= this->epptr() - this->pptr()) {
    traits_type::copy(this->pptr(), source_p, num);
    this->pbump(num);
  }

  else {
    // make a block write to the target, so first
    // flush anything in the buffer
    if (flush_buffer() == -1) num = 0;  // flush_buffer() returns 0 if output_buffer.get() == 0
                                        // and g_output_stream_flush() flushes correctly
    else {
      gsize count;
      GError* error = 0;
      g_output_stream_write_all(output_stream,
                                source_p,
				num * sizeof(char_type),
			      	&count,
			      	0,
				&error);

      if (error) set_output_error(error);
      num = count/sizeof(char_type);
    }
  }
  return num;
}
#endif /*CGU_GSTREAM_USE_STD_N_READ_WRITE*/

template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::set_byteswap(bool swap) {
  if (swap != byteswap) {  
    byteswap = swap;
    std::for_each(this->eback(),
                  this->egptr(),
       		  swap_element);
  }
}

template <class charT , class Traits>
typename basic_gstreambuf<charT , Traits>::int_type
basic_gstreambuf<charT , Traits>::underflow() {

  if (!input_stream.get())
    return traits_type::eof();

  // is read position before end of buffer?
  if (this->gptr() < this->egptr()) return traits_type::to_int_type(*this->gptr());

  // the next block is only relevant if we have a GIOStream object
  // attached (otherwise is_output_stored() will return false).  If we do,
  // we must flush output if the underlying device is seekable.  As we are
  // here then we know gptr() == egptr() so stream is not in inconsistent state
  if (is_output_stored()              // characters pending in output buffer
      && (seekable || seek_mismatch)  // the underlying device is seekable
      && flush_buffer() == -1)        // so flush
    return traits_type::eof();

  int putback_count = this->gptr() - this->eback();
  if (putback_count > putback_size) putback_count = putback_size;

  /* copy up to putback_size characters previously read into the putback
   * area -- use traits_type::move() because if the size of file modulus
   * input_buf_size is less than putback_size, the source and destination
   * ranges can overlap on the last call to underflow() (ie the one which
   * leads to traits_type::eof() being returned)
   */
  traits_type::move(input_buffer.get() + (putback_size - putback_count),
		    this->gptr() - putback_count,
		    putback_count);

  // read at most input_buf_size new characters
  GError* error = 0;
  gssize result = g_input_stream_read(input_stream,
                                      input_buffer.get() + putback_size,
				      input_buf_size * sizeof(char_type),
				      0,
				      &error);

  if (error) set_input_error(error);
  if (sizeof(char_type) > 1 && result > 0) { // check for part character fetch - this block will be
                                             // optimised out where char_type is a plain one byte char
    std::size_t part_char = result % sizeof(char_type);
    if (part_char) {
      gsize result2 = 0;
      std::streamsize remaining = sizeof(char_type) - part_char;
      char* temp_p = reinterpret_cast<char*>(input_buffer.get());
      temp_p += putback_size * sizeof(char_type) + result;
      g_input_stream_read_all(input_stream,
                              temp_p,
			      remaining,
			      &result2,
			      0,
			      &error);
      result += result2;
      if (error) set_input_error(error);
    }
  }
 
  if (result <= 0) {
    // error (result == -1) or end-of-file (result == 0)
    // we might as well make the put back area readable so reset
    // the buffer pointers to enable this
    this->setg(input_buffer.get() + (putback_size - putback_count), // beginning of putback area
	       input_buffer.get() + putback_size,                   // start of read area of buffer
	       input_buffer.get() + putback_size);                  // end of buffer - it's now empty
    return traits_type::eof();
  }
  
  // reset buffer pointers
  this->setg(input_buffer.get() + (putback_size - putback_count),             // beginning of putback area
	     input_buffer.get() + putback_size,                               // read position
	     input_buffer.get() + (putback_size + result/sizeof(char_type))); // end of buffer

  // do any necessary byteswapping on newly fetched characters
  if (sizeof(char_type) > 1 && byteswap) {
    std::for_each(this->gptr(),
                  this->egptr(),
       		  swap_element);
  }
  // return next character
  return traits_type::to_int_type(*this->gptr());
}

#ifndef CGU_GSTREAM_USE_STD_N_READ_WRITE
template <class charT , class Traits>
std::streamsize basic_gstreambuf<charT , Traits>::xsgetn(char_type* dest_p, std::streamsize num) {

  if (!input_stream.get()) return 0;

  // the next block is only relevant if we have a GIOStream object
  // attached (otherwise is_output_stored() will return false).  If we
  // do, we must flush output if the underlying device is seekable.
  if (is_output_stored()                              // characters pending in output buffer
      && (seekable || seek_mismatch)                  // the underlying device is seekable
      && (is_input_stored() || flush_buffer() == -1)) // bail out if is_input_stored(), because then
                                                      // stream is in an inconsistent state, or flush
    return 0;

  std::streamsize copied_to_target = 0;
  const std::streamsize available = this->egptr() - this->gptr();

  // if num is less than or equal to the characters already in the buffer,
  // extract from buffer
  if (num <= available) {
    traits_type::copy(dest_p, this->gptr(), num);
    this->gbump(num);
    copied_to_target = num;
  }

  else {
    // first copy out the buffer
    if (available) {
      traits_type::copy(dest_p, this->gptr(), available);
      copied_to_target = available;
    }

    // we now know from the first 'if' block that there is at least one more character required

    gssize remaining = (num - copied_to_target) * sizeof(char_type); // this is in bytes
    bool stream_fail = false;

    if (num - copied_to_target > input_buf_size) {
      // this is a big read, and we are going to read up to everything we need
      // except one character with a single block read() (leave one character
      // still to get on a buffer refill read in order (a) so that the final read
      // will not unnecessarily block after the xsgetn() request size has been met and
      // (b) so that we have an opportunity to fill the buffer for any further reads)
      gsize result = 0;
      GError* error = 0;
      remaining -= sizeof(char_type); // leave one remaining character to fetch
      char* temp_p = reinterpret_cast<char*>(dest_p);
      temp_p += copied_to_target * sizeof(char_type);
      g_input_stream_read_all(input_stream,
                              temp_p,
			      remaining,
			      &result,
			      0,
			      &error);
      if (error) set_input_error(error);
      remaining -= result;
      stream_fail = remaining;  // set true if eof or error
      copied_to_target += result/sizeof(char_type); // if the stream hasn't failed this must
                                                    // be a whole number of characters
      if (!copied_to_target) return 0;  // nothing available - bail out

      // do any necessary byteswapping on the newly fetched characters
      // which have been put directly in the target
      if (sizeof(char_type) > 1 && byteswap) {
        std::for_each(dest_p + available,
                      dest_p + copied_to_target,
       		      swap_element);
      }

      remaining += sizeof(char_type);   // reset the requested characters to add back the last one
    }

    char* buffer_pos_p = reinterpret_cast<char*>(input_buffer.get());
    buffer_pos_p += putback_size * sizeof(char_type);
    gsize bytes_fetched = 0;

    if (!stream_fail) {
      // now fill up the buffer as far as we can to extract sufficient
      // bytes for the one or more remaining characters requested
      gssize result = 0;
      GError* error = 0;
      do {
	result = g_input_stream_read(input_stream,
	                             buffer_pos_p,
				     input_buf_size * sizeof(char_type) - bytes_fetched,
				     0,
				     &error);
	if (result > 0) {
	  buffer_pos_p += result;
	  remaining -= result;
	  bytes_fetched += result;
	}
      } while (remaining > 0                         // we haven't met the request
	       && result  > 0);                      // not eof and no error

      if (error) set_input_error(error);
      if (!copied_to_target && !bytes_fetched) return 0;   // nothing available - bail out
      stream_fail = (result <= 0);
    }

    if (sizeof(char_type) > 1 && !stream_fail) { // check for part character fetch - this block will be
                                                 // optimised out where char_type is a plain one byte char
      std::size_t part_char = bytes_fetched % sizeof(char_type);
      if (part_char) {
	remaining = sizeof(char_type) - part_char;
      	gsize result = 0;
	GError* error = 0;
	g_input_stream_read_all(input_stream,
                                buffer_pos_p,
			        remaining,
			        &result,
			        0,
				&error);
	if (error) set_input_error(error);
        bytes_fetched += result;
      }
    }
    std::streamsize chars_fetched = bytes_fetched/sizeof(char_type);// if the stream hasn't failed this
                                                                    // must now be a whole number of
                                                                    // characters we have put directly
                                                                    // in the buffer

    // do any necessary byteswapping on newly fetched characters now in the buffer
    if (sizeof(char_type) > 1 && byteswap) {
      std::for_each(input_buffer.get() + putback_size,
      		    input_buffer.get() + (putback_size + chars_fetched),
       		    swap_element);
    }

    // now put the final instalment of requested characters into the target from the buffer
    std::streamsize copy_num = chars_fetched;
    std::streamsize putback_count = 0;
    if (copy_num) {
      if (copy_num > num - copied_to_target) copy_num = num - copied_to_target;
      traits_type::copy(dest_p + copied_to_target, input_buffer.get() + putback_size, copy_num);
      copied_to_target += copy_num;
    }

    if (copy_num < putback_size) { // provide some more putback characters: these
                                   // will by now have been byte swapped if relevant
      std::streamsize start_diff = copied_to_target;
      if (start_diff > putback_size) start_diff = putback_size;
      putback_count = start_diff - copy_num;
      traits_type::copy(input_buffer.get() + (putback_size - putback_count),
			dest_p + (copied_to_target - start_diff),
			putback_count);
    }
    // reset buffer pointers
    this->setg(input_buffer.get() + (putback_size - putback_count),    // beginning of putback area
	       input_buffer.get() + (putback_size + copy_num),         // read position
	       input_buffer.get() + (putback_size + chars_fetched));   // end of buffer
  }
  return copied_to_target;
}
#endif /*CGU_GSTREAM_USE_STD_N_READ_WRITE*/

template <class charT , class Traits>
GobjHandle<GInputStream> basic_gstreambuf<charT , Traits>::get_istream() const {
  return input_stream;
}

template <class charT , class Traits>
GobjHandle<GOutputStream> basic_gstreambuf<charT , Traits>::get_ostream() const {
  return output_stream;
}

template <class charT , class Traits>
GobjHandle<GIOStream> basic_gstreambuf<charT , Traits>::get_iostream() const {
  return io_stream;
}

template <class charT , class Traits>
void basic_gstreambuf<charT , Traits>::set_output_buffered(bool buffered) {

  if (!output_stream.get())
    return;

  if (buffered && !output_buffer.get()) {
#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
    output_buffer.reset(static_cast<char_type*>(g_slice_alloc(output_buf_size * sizeof(char_type))));
#else
    output_buffer.reset(new char_type[output_buf_size]);
#endif

    this->setp(output_buffer.get(), output_buffer.get() + output_buf_size);
  }

  else if (!buffered && output_buffer.get()) {
    basic_gstreambuf<charT , Traits>::sync();
    output_buffer.reset();
    this->setp(0, 0);
  }
}

template <class charT , class Traits>
typename basic_gstreambuf<charT , Traits>::pos_type
basic_gstreambuf<charT , Traits>::seekoff(off_type off,
		                          std::ios_base::seekdir way,
					  std::ios_base::openmode m) {

  pos_type ret = pos_type(off_type(-1));
  off *= sizeof(char_type);  // number of bytes

  // we do not need to check 'seek_mismatch': if 'seek_mismatch' is
  // true then 'seekable' must be false

  // if we have output to flush, do so (if there is something in both
  // the input buffer and the output buffer on anything other than an
  // intrinsically unseekable device such as socket, the rules for managing
  // GIOStream objects explained in the documentation have not been
  // followed and the stream is in an inconsistent state: this will
  // cause us to bail out)
  if (basic_gstreambuf<charT , Traits>::sync() == -1)
    return ret;

  // recipient of any overfetch offset on read for use if way == std::ios_base::cur
  goffset overfetch_adj = 0;
  GSeekType seek_type;
  if (way == std::ios_base::cur) {
    seek_type = G_SEEK_CUR;
    overfetch_adj = this->egptr() - this->gptr();
  }
  else if (way == std::ios_base::end)
    seek_type = G_SEEK_END;
  else
    seek_type = G_SEEK_SET;  // start of file

  if ((m & std::ios_base::out)  // eg seekp()
      && output_stream.get() 
      && seekable) {

    // specialisation for tellp()
    if (seek_type == G_SEEK_CUR && !off) {
      goffset temp = g_seekable_tell((GSeekable*)output_stream.get())/sizeof(char_type) - overfetch_adj;
      if (temp >= 0)
        ret = pos_type(off_type(temp));
    }

    else {
      gboolean success = g_seekable_seek((GSeekable*)output_stream.get(),
                                         goffset(off) - overfetch_adj * sizeof(char_type),
	      			   	 seek_type,
		      			 0,
					 0);  // the return value is sufficient to deal with an error
      if (success) {
        ret = pos_type(off_type(g_seekable_tell((GSeekable*)output_stream.get())/sizeof(char_type)));
	if (input_stream.get())
	  reset_input_buffer_pointers();
	overfetch_adj = 0;
      }
    }
  }

  // don't enter the block where a GIOStream object is attached, both
  // the std::ios_base::in and std::ios_base::out bits are set and
  // the 'way' argument is std::ios_base::cur: in that case we have
  // nothing to do as the preceding output stream operations will have
  // done all that is required
  if (!io_stream.get() || !(m & std::ios_base::out) || seek_type != G_SEEK_CUR) {

    if ((m & std::ios_base::in)  // eg seekg()
        && input_stream.get()
	&& seekable) {

      // specialisation for tellg()
      if (seek_type == G_SEEK_CUR && !off) {
        goffset temp = g_seekable_tell((GSeekable*)input_stream.get())/sizeof(char_type) - overfetch_adj;
        if (temp >= 0)
          ret = pos_type(off_type(temp));
      }
      else {
        gboolean success = g_seekable_seek((GSeekable*)input_stream.get(),
                                           goffset(off) - overfetch_adj * sizeof(char_type),
		      			   seek_type,
		      			   0,
					   0);  // the return value is sufficient to deal with an error
        if (success && ret == pos_type(off_type(-1))) {
          ret = pos_type(off_type(g_seekable_tell((GSeekable*)input_stream.get())/sizeof(char_type)));
          reset_input_buffer_pointers();
        }
      }
    }
  }

  return ret;
}

template <class charT , class Traits>
typename basic_gstreambuf<charT , Traits>::pos_type
basic_gstreambuf<charT , Traits>::seekpos(pos_type p,
                                          std::ios_base::openmode m) {

  if (p == pos_type(off_type(-1)))
    return p;
  return seekoff(off_type(p), std::ios_base::beg, m);
}

} // namespace Cgu

#endif /*CGU_GSTREAM_TPP*/
