/* Copyright (C) 2001, 2004, 2009, 2010, 2011 and 2012 Chris Vine

 The following code declares classes to read from and write to
 Unix file descriptors.

 The whole work comprised in files fdstream.h and fdstream.tpp 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_FDSTREAM_TPP
#define CGU_FDSTREAM_TPP

namespace Cgu {

// provide the fdstream class methods

template <class charT , class Traits>
basic_fdoutbuf<charT , Traits>::basic_fdoutbuf(int fd_, bool manage_) : fd(fd_), manage(manage_) {

  if (fd_ >= 0) {

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

    this->setp(buffer.get(), buffer.get() + buf_size);
  }
}

template <class charT , class Traits>
basic_fdoutbuf<charT , Traits>::~basic_fdoutbuf() {
  if (fd >= 0) {
    basic_fdoutbuf<charT , Traits>::sync();
    if (manage) {
      while (::close(fd) == -1 && errno == EINTR);
    }
  }
}

template <class charT , class Traits>
void basic_fdoutbuf<charT , Traits>::set_buffered(bool buffered) {

  if (fd < 0)
    return;

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

    this->setp(buffer.get(), buffer.get() + buf_size);
  }

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

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

  if (fd < 0) return -1;

  int ret_val = flush_buffer();
  // if the file descriptor is bound to a pipe the fsync() operation
  // is meaningless and we will probably get EINVAL, but we can
  // ignore that.  We will also ignore EIO - the error will be picked
  // up and reported on the next write if there really is a problem
  while (fsync(fd) == -1 && errno == EINTR);
  return ret_val;  
}

template <class charT , class Traits>
void basic_fdoutbuf<charT , Traits>::attach_fd(int fd_, bool manage_) {
  if (fd >= 0) {  // old descriptor
    basic_fdoutbuf<charT , Traits>::sync();
    if (manage) {
      while (::close(fd) == -1 && errno == EINTR);
    }
  }

  if (fd_ >= 0) {  // new descriptor
    if (!buffer.get()) {
#if defined(CGU_USE_GLIB_MEMORY_SLICES_COMPAT) || defined(CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT)
      buffer.reset(static_cast<char_type*>(g_slice_alloc(buf_size * sizeof(char_type))));
#else
      buffer.reset(new char_type[buf_size]);
#endif
    }
    this->setp(buffer.get(), buffer.get() + buf_size);
  }
  else {
    buffer.reset();
    this->setp(0, 0);
  }

  fd = fd_;
  manage = manage_;
}

template <class charT , class Traits>
bool basic_fdoutbuf<charT , Traits>::close_fd() {
  bool ret;
  if (fd >= 0) {
    ret = true;
    if (basic_fdoutbuf<charT , Traits>::sync() == -1)
      ret = false;
    int result;
    while ((result = ::close(fd)) == -1 && errno == EINTR); 
    if (result == -1)
      ret = false;
    fd = -1;
  }
  else
    ret = false;
  return ret;
}

// flush_buffer() returns 0 for success and -1 for error
template <class charT , class Traits>
int basic_fdoutbuf<charT , Traits>::flush_buffer() {
  std::size_t count = this->pptr() - this->pbase();
  if (count) {
    std::size_t remaining = count * sizeof(char_type);
    ssize_t result;
    const char* temp_p = reinterpret_cast<const char*>(buffer.get());
    do {
      result = write(fd, temp_p, remaining);
      if (result > 0) {
	temp_p += result;
	remaining -= result;
      }
    } while (remaining && (result != -1 || errno == EINTR));

    if (remaining) return -1;
    this->pbump(-count);
  }
  return 0;
}

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

  if (!buffer.get()) {
    // we are not buffered
    if (!traits_type::eq_int_type(c, traits_type::eof())) {
      std::size_t remaining = sizeof(char_type);
      ssize_t result;
      const char_type letter = c;
      const char* temp_p = reinterpret_cast<const char*>(&letter);
      do {
        result = write(fd, temp_p, remaining);
      	if (result > 0) {
	  temp_p += result;
	  remaining -= result;
        }
      } while (remaining && (result != -1 || errno == EINTR));
                                           
      if (!remaining) return c;
      return traits_type::eof();
    }
    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);
  return this->sputc(c);
}

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

  // if num is less than or equal to the space in the buffer, put it in the buffer
  if (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 and then use Unix ::write()
    if (flush_buffer() == -1) num = 0;  // flush_buffer() returns 0 if buffer.get() == 0
    else {
      std::size_t remaining = num * sizeof(char_type);
      ssize_t result;
      const char* temp_p = reinterpret_cast<const char*>(source_p);
      do {
	result = write(fd, temp_p, remaining);
	if (result > 0) {
	  temp_p += result;
	  remaining -= result;
	}
      } while (remaining && (result != -1 || errno == EINTR));
      
      if (remaining) num -= remaining/sizeof(char_type);
    }
  }
  return num;
}
#endif /*FDSTREAM_USE_STD_N_READ_WRITE*/

// static function to pass to std::for_each() to carry out
// the wchar_t byte swapping
template <class charT , class Traits>
void basic_fdinbuf<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>
basic_fdinbuf<charT , Traits>::basic_fdinbuf(int fd_, bool manage_): fd(fd_),
		                                                     manage(manage_),
								     byteswap(false) {
  reset();
}

template <class charT , class Traits>
basic_fdinbuf<charT , Traits>::~basic_fdinbuf() {
  if (fd >= 0) {
    if (manage)
      while (::close(fd) == -1 && errno == EINTR);
    else {                                     // since we are not closing the file descriptor, wind
      basic_fdinbuf<charT , Traits>::seekpos(  // back the file pointer to the logical position  
        basic_fdinbuf<charT , Traits>::seekoff(0, std::ios_base::cur)
      );
    }
  }
}

template <class charT , class Traits>
void basic_fdinbuf<charT , Traits>::reset() {
  this->setg(buffer + putback_size,     // beginning of putback area
	     buffer + putback_size,     // read position
	     buffer + putback_size);    // end position
}

template <class charT , class Traits>
void basic_fdinbuf<charT , Traits>::attach_fd(int fd_, bool manage_) {
  if (fd >= 0) {
    if (manage)
      while (::close(fd) == -1 && errno == EINTR);
    else {                                     // since we are not closing the file descriptor, wind
      basic_fdinbuf<charT , Traits>::seekpos(  // back the file pointer to the logical position  
        basic_fdinbuf<charT , Traits>::seekoff(0, std::ios_base::cur)
      );
    }
  }
  reset();
  fd = fd_;
  manage = manage_;
  byteswap = false;
}

template <class charT , class Traits>
bool basic_fdinbuf<charT , Traits>::close_fd() {
  bool ret;
  if (fd >= 0) {
    ret = true;
    reset();
    int result;
    while ((result = ::close(fd)) == -1 && errno == EINTR); 
    if (result == -1)
      ret = false;
    fd = -1;
  }
  else
    ret = false;
  return ret;
}

template <class charT , class Traits>
void basic_fdinbuf<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_fdinbuf<charT , Traits>::int_type
basic_fdinbuf<charT , Traits>::underflow() {
  // insert new characters into the buffer when it is empty

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

  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 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(buffer + (putback_size - putback_count),
		    this->gptr() - putback_count,
		    putback_count);

  // read at most buf_size new characters
  ssize_t result;
  do {
    result = read(fd, buffer + putback_size, buf_size * sizeof(char_type));
  } while (result == -1 && errno == EINTR);

  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) {
      ssize_t result2;
      std::streamsize remaining = sizeof(char_type) - part_char;
      char* temp_p = reinterpret_cast<char*>(buffer);
      temp_p += putback_size * sizeof(char_type) + result;
      do {
	result2 = read(fd, temp_p, remaining);
	if (result2 > 0) {
	  temp_p += result2;
	  remaining -= result2;
	  result += result2;
	}
      } while (remaining                              // we haven't met the request
	       && result2                             // not end of file
	       && (result2 != -1 || errno == EINTR)); // no error other than syscall interrupt
    }
  }
 
  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(buffer + (putback_size - putback_count), // beginning of putback area
	       buffer + putback_size,                   // start of read area of buffer
	       buffer + putback_size);                  // end of buffer - it's now empty
    return traits_type::eof();
  }
  
  // reset buffer pointers
  this->setg(buffer + (putback_size - putback_count),             // beginning of putback area
	     buffer + putback_size,                               // read position
	     buffer + (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 FDSTREAM_USE_STD_N_READ_WRITE
template <class charT , class Traits>
std::streamsize basic_fdinbuf<charT , Traits>::xsgetn(char_type* dest_p, std::streamsize num) {
  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

    ssize_t remaining = (num - copied_to_target) * sizeof(char_type); // this is in bytes
    std::size_t bytes_fetched = 0;

    ssize_t result = 1; // a dummy value so that the second if block
                        // is always entered if next one isn't

    if (num - copied_to_target > 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 system 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)
      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);
      do {
	result = read(fd, temp_p, remaining);
	if (result > 0) {
	  temp_p += result;
	  remaining -= result;
	  bytes_fetched += result;
	}
      } while (remaining                             // more to come
	       && result                             // not end of file
	       && (result != -1 || errno == EINTR)); // no error other than syscall interrupt

      copied_to_target += bytes_fetched/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
      bytes_fetched = 0;                // reset bytes_fetched
    }

    char* buffer_pos_p = reinterpret_cast<char*>(buffer);
    buffer_pos_p += putback_size * sizeof(char_type);
    // at this point bytes_fetched will have been set or reset to 0

    if (result > 0) { // no stream failure
      // now fill up the buffer as far as we can to extract sufficient
      // bytes for the one or more remaining characters requested
      do {
	result = read(fd, buffer_pos_p, buf_size * sizeof(char_type) - bytes_fetched);
	if (result > 0) {
	  buffer_pos_p += result;
	  remaining -= result;
	  bytes_fetched += result;
	}
      } while (remaining > 0                         // we haven't met the request
	       && result                             // not end of file
	       && (result != -1 || errno == EINTR)); // no error other than syscall interrupt

      if (!copied_to_target && !bytes_fetched) return 0;   // nothing available - bail out
    }

    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 = bytes_fetched % sizeof(char_type);
      if (part_char) {
	remaining = sizeof(char_type) - part_char;
	do {
	  result = read(fd, buffer_pos_p, remaining);
	  if (result > 0) {
            buffer_pos_p += result;
	    remaining -= result;
	    bytes_fetched += result;
	  }
	} while (remaining                             // we haven't met the request
		 && result                             // not end of file
		 && (result != -1 || errno == EINTR)); // no error other than syscall interrupt
      }
    }
    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(buffer + putback_size,
      		    buffer + (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, buffer + 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(buffer + (putback_size - putback_count),
			dest_p + (copied_to_target - start_diff),
			putback_count);
    }
    // reset buffer pointers
    this->setg(buffer + (putback_size - putback_count),    // beginning of putback area
	       buffer + (putback_size + copy_num),         // read position
	       buffer + (putback_size + chars_fetched));   // end of buffer
  }
  return copied_to_target;
}
#endif /*FDSTREAM_USE_STD_N_READ_WRITE*/

/** functions wtih implementation common to both streambuffer classes **/

template <class charT , class Traits>
bool basic_fdoutbuf<charT , Traits>::can_seek() const {
  return (lseek(fd, 0, SEEK_CUR) != off_t(-1));
}

template <class charT , class Traits>
bool basic_fdinbuf<charT , Traits>::can_seek() const {
  return (lseek(fd, 0, SEEK_CUR) != off_t(-1));
}

template <class charT , class Traits>
typename basic_fdoutbuf<charT , Traits>::pos_type
basic_fdoutbuf<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

  if (fd == -1)
    return ret;
  if (flush_buffer() == -1)
    return ret;

  int seek_type;
  if (way == std::ios_base::cur) {
    seek_type = SEEK_CUR;
  }
  else if (way == std::ios_base::end)
    seek_type = SEEK_END;
  else
    seek_type = SEEK_SET;  // start of file

  if ((m & std::ios_base::out)  // eg seekp()
      && can_seek()) {

    off_t temp = lseek(fd, off_t(off), seek_type);
    if (temp != -1) {
      ret = pos_type(off_type(temp/sizeof(char_type)));
    }
  }

  return ret;
}

template <class charT , class Traits>
typename basic_fdoutbuf<charT , Traits>::pos_type
basic_fdoutbuf<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);
}

template <class charT , class Traits>
typename basic_fdinbuf<charT , Traits>::pos_type
basic_fdinbuf<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

  if (fd == -1)
    return ret;

  // recipient of any overfetch offset on read for use if way == std::ios_base::cur
  off_t overfetch_adj = 0;
  int seek_type;
  if (way == std::ios_base::cur) {
    seek_type = SEEK_CUR;
    overfetch_adj = this->egptr() - this->gptr();
  }
  else if (way == std::ios_base::end)
    seek_type = SEEK_END;
  else
    seek_type = SEEK_SET;  // start of file

  if ((m & std::ios_base::in)  // eg seekg()
      && can_seek()) {

    // specialisation for tellg()
    if (seek_type == SEEK_CUR && !off) {
      off_t temp = lseek(fd, 0, SEEK_CUR)/sizeof(char_type) - overfetch_adj;
      if (temp >= 0)
        ret = pos_type(off_type(temp));
    }
    else {
      off_t temp = lseek(fd, off_t(off) - overfetch_adj * sizeof(char_type), seek_type);
      if (temp != -1) {
        ret = pos_type(off_type(temp/sizeof(char_type)));
        reset();
      }
    }
  }

  return ret;
}

template <class charT , class Traits>
typename basic_fdinbuf<charT , Traits>::pos_type
basic_fdinbuf<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_FDSTREAM_TPP*/
