/* ====================================================================
 * Copyright (c) 2003-2008  Martin Hauner
 *                          http://subcommander.tigris.org
 *
 * Subcommander is licensed as described in the file doc/COPYING, which
 * you should have received as part of this distribution.
 * ====================================================================
 */

#ifndef _ICONV_STREAM_H
#define _ICONV_STREAM_H

// sc
#include "types.h"
#include "apr.h"
#include "Error.h"
#include "AprError.h"
#include "ErrorCodes.h"

// sys
#include <istream>
#include <cstdio>

// apr util
#include <apr_xlate.h>


class iconvstreambuf : public std::streambuf
{
  static const sc::Size CSize    = 2052;
  static const sc::Size CPutBack = 4;

public:
  iconvstreambuf( std::istream& input, const char* cpIn, const char* cpOut )
    : _in(input), _inoff(0), _pool(0), _xlate(0), _error(0)
  {
    apr_status_t status = apr_pool_create( &_pool, 0 );
    if( status != APR_SUCCESS )
    {
      _error = apr::createError(status);
      return;
    }

    const char* cpIn2  = cpIn;
    const char* cpOut2 = cpOut;

    if( *cpIn2 == '*' )
      cpIn2 = APR_LOCALE_CHARSET;

    if( *cpOut2 == '*' )
      cpOut2 = APR_LOCALE_CHARSET;

    status = apr_xlate_open( &_xlate, cpOut2, cpIn2, _pool );
    if( status != APR_SUCCESS )
    {
      // empty iconv folder?
      sc::String msg = sc::strError(sc::ErrEncoding);
      msg += " (";
      msg += cpIn;
      msg += "/";
      msg += cpOut;
      msg += ")";
      _error = createError( sc::ErrEncoding, msg, apr::createError(status) );
      return;
    }

    setg( _buf+CPutBack, _buf+CPutBack, _buf+CPutBack );
  }

  ~iconvstreambuf()
  {
    if( _xlate )
    {
      apr_status_t status = apr_xlate_close(_xlate);

      if( status != APR_SUCCESS )
      {
        // nothing we could do here..
      }
    }
    if( _pool )
    {
      apr_pool_destroy(_pool);
    }
  }

public:
 bool ok() const
 {
   return _error == sc::Success;
 }

 const sc::Error* getError() const
 {
   return _error;
 }

 void setParent( std::istream* parent )
 {
   _parent = parent;
 }

protected:
  int_type underflow()
  {
    // adjust put back buffer
    int pb = (int)(gptr() - eback());
    if( pb > (int)CPutBack )
    {
      pb = CPutBack;
    }
    ::memmove( _buf+(CPutBack-pb), gptr()-pb, pb );

    // read from input
    _in.read( _inbuf+_inoff, (std::streamsize)(CSize-CPutBack-_inoff) );
    sc::Size got = _in.gcount() + _inoff;

    // we didn't read anything and we don't have anything (left) in _inbuf?
    if( _in.gcount() == 0 && _inoff == 0 )
    {
      return EOF;
    }

    // before the xlate call this is set to the size of the _inbuf (src)
    // and _buf (dst)  
    // after the xlate call this contains the unused bytes in _inbuf (src)
    // and _buf (dst)
    apr_size_t srcLen = got;
    apr_size_t dstLen = CSize-CPutBack;

    // this fails in most cases, because the xlate won't fit byte-to-byte
    // into _buf, so we ignore the result and check the returned xLen values
    // instead.
    apr_status_t status =
      apr_xlate_conv_buffer( _xlate, _inbuf, &srcLen, _buf+CPutBack, &dstLen );

    ::memmove( _inbuf, _inbuf+got-srcLen, srcLen );
    _inoff = srcLen;

    // nothing xlate'd? that looks wrong
    if( srcLen == got )
    {
      _error = apr::createError(status);
      _parent->setstate( std::ios::badbit );
      return EOF;
    }

    setg( _buf+(CPutBack-pb), _buf+CPutBack, _buf+CPutBack + (CSize-CPutBack-dstLen) );
    return (unsigned char)(*gptr());
  }

private:
  std::istream& _in;
  std::istream* _parent;

  char          _buf[CSize];               // target for xlated characters

  char          _inbuf[CSize-CPutBack];    // source of xlated characters
  sc::Size      _inoff;

  apr_pool_t*   _pool;
  apr_xlate_t*  _xlate;

  const sc::Error* _error;
};


/////////////////////////////////////////////////////////////////////

class iconvstreambuf2 : public std::streambuf
{
  static const int CSize = 5;

public:
  iconvstreambuf2( std::ostream& output, const char* cpIn, const char* cpOut )
    : _out(output), /*_outoff(0),*/ _pool(0), _xlate(0), _error(0)
  {
    _c1 = _c2 = _c3 = 0;

    apr_status_t status = apr_pool_create( &_pool, 0 );
    if( status != APR_SUCCESS )
    {
      _error = apr::createError(status);
      return;
    }

    if( *cpIn  == '*' )
      cpIn = APR_LOCALE_CHARSET;

    if( *cpOut == '*' )
      cpOut = APR_LOCALE_CHARSET;

    status = apr_xlate_open( &_xlate, cpOut, cpIn, _pool );
    if( status != APR_SUCCESS )
    {
      sc::String msg = sc::strError(sc::ErrEncoding);
      msg += " (*, locale charset)";
      _error = createError( sc::ErrEncoding, msg, apr::createError(status) );
      return;
    }

    setp( _buf, _buf+CSize-1 );
  }

  ~iconvstreambuf2()
  {
    sync();

    if( _xlate )
    {
      apr_status_t status = apr_xlate_close(_xlate);

      if( status != APR_SUCCESS )
      {
        // nothing we could do here..
      }
    }
    if( _pool )
    {
      apr_pool_destroy(_pool);
    }
  }

public:
  bool ok() const
  {
    return _error == sc::Success;
  }

  const sc::Error* getError() const
  {
    return _error;
  }

protected:
  int_type overflow( int_type c )
  {
    if( c != EOF )
    {
      // buffer c
      *pptr() = c;
      pbump(1);
    }

    if( flush() == EOF )
    {
      return EOF;
    }
    return c;
  }

  int sync()
  {
    if( flush() == EOF )
    {
      return -1;
    }
    return 0;
  }

  int flush()
  {
    sc::Size write = pptr() - pbase();

    apr_size_t srcLen = write;
    apr_size_t dstLen = CSize;

    // this fails in most cases, because the xlate won't fit byte-to-byte
    apr_status_t status =
      apr_xlate_conv_buffer( _xlate, _buf, &srcLen, _outbuf, &dstLen );

    if( status != APR_SUCCESS )
    {
      // so we ignore the result and check the returned xLen values instead.
    }

    // check failure, return EOF
    if( dstLen == (apr_size_t)CSize )
    {
      //_error = createAprError(status);
      return EOF;
    }

    _out.write( _outbuf, (std::streamsize)(CSize-dstLen) );

    // copy anything we didn't write to the beginning of the buffer
    ::memmove( _buf, _buf+write-srcLen, srcLen );

    pbump(-(int)(write-srcLen));
    return (int)(CSize-dstLen);
  }

private:
  char          _c1;
  char          _buf[CSize];
  char          _c2;
  char          _outbuf[CSize];
  char          _c3;

  std::ostream& _out;

  apr_pool_t*   _pool;
  apr_xlate_t*  _xlate;

  const sc::Error* _error;
};

/////////////////////////////////////////////////////////////////////

class iconvistream :  public std::istream
{
public:
  iconvistream( std::istream& in, const char* cpIn, const char* cpOut )
    : std::istream(0), _buf(in,cpIn,cpOut)
  {
    // required to propagate errors
    _buf.setParent(this);

    // iconv setup was succesfull?
    if( ! _buf.ok() )
    {
      //no
      setstate( std::ios::badbit );
    }

    // because _buf is a member it is initialized after std::istream!
    // add _buf to the stream after it is initialized.
    init(&_buf);
  }

  const sc::Error* getError() const
  {
    return _buf.getError();
  }

  iconvstreambuf _buf;
};

/////////////////////////////////////////////////////////////////////

class ostreambuf
{
protected:
  ostreambuf( std::ostream& out, const char* cpIn, const char* cpOut )
  : _buf(out,cpIn,cpOut) {}
  
  iconvstreambuf2 _buf;
};

class iconvostream : private ostreambuf, public std::ostream
{
public:
  iconvostream( std::ostream& out, const char* cpIn, const char* cpOut )
    : ostreambuf(out,cpIn,cpOut), std::ostream(&_buf)
  {
    if( ! _buf.ok() )
    {
      setstate( std::ios::badbit );
    }
  }

  const sc::Error* getError() const
  {
    return _buf.getError();
  }

  // if _buf is a member it is initialized after std::ostream!
};


#endif // _ICONV_STREAM_H
