// This may look like C code, but it is really -*- C++ -*-
// 
// <copyright> 
//  
//  Copyright (c) 1996
//  Institute for Information Processing and Computer Supported New Media (IICM), 
//  Graz University of Technology, Austria. 
//  
// </copyright> 
// 
// 
// <file> 
// 
// Name:        TIOrpcbuf.C
// 
// Purpose:     
// 
// Created:     20 Feb 1997   Joerg Faschingbauer
// 
// Modified:    
// 
// Description: a copy of rpcbuf.C
// 
// $Id: tiorpcbuf.C,v 1.2 1997/02/21 16:38:59 gorasche Exp $
// 
// $Log: tiorpcbuf.C,v $
// Revision 1.2  1997/02/21 16:38:59  gorasche
// changed scope var of readon
//
// Revision 1.1  1997/02/20 19:32:25  jfasch
// Initial revision
//
// 
// </file> 
#include "tiorpcbuf.h"

#include <hyperg/utils/assert.h>

#include <ctype.h>
#include <string.h>

/* hleitner: (16.3.94) -- no zapeof in libg++ 
gorasche: (1.2.96) ditto in MSC */
#if defined(__GNUC__) || defined(WIN32)       
#define zapeof(c) ((c) & 0xFF)
#endif

// --------------------------------------------------------------------
const char* TIOrpcbuf :: version1 = "TIOrpcbuf: $Id: tiorpcbuf.C,v 1.2 1997/02/21 16:38:59 gorasche Exp $" ;
Verbose TIOrpcbuf :: verbose ;

// I need a pointer to an iostreamb so I can insert and extract values
// in the length field of RPC requests.  If I don't have a stream, I
// won't allow you to call start_request().

TIOrpcbuf::TIOrpcbuf(iostreamb* mystream)
: streambuf(),
  _mystream(mystream),
  _rptr(nil),
  _actualWidth(0) {}

// Free the buffer used to store the put area.  The streambuf
// destructor will free the buffer used to store the get area.

// TIOrpcbuf::~TIOrpcbuf() {
//     close();

//     delete pbase();
//     setp(nil, nil);
// }
TIOrpcbuf::~TIOrpcbuf() {
    close();

    delete base() ; 
    setb (nil, 0, false) ; // quite a dummy, I think.

    delete pbase();
    setp(nil, nil);
}

TIOrpcbuf* TIOrpcbuf::attach (const TransparentIOPtr& tio) {
   hgassert (!tio_, "TIOrpcbuf::attach(): already using an IO") ;
   hgassert (tio, "TIOrpcbuf::attach(): nil IO") ;

   tio_ = tio ;
   return this;
}

// Empty the get/put areas, close the file descriptor if nothing else
// might use it, and detach the streambuf from the file descriptor.

TIOrpcbuf* TIOrpcbuf::close() {
   if (! tio_)
      return nil ;
   sync();
   tio_ = TransparentIOPtr() ;
   return this ;
}

// Finish the current request, if any, and then start a new request.
// The request begins with a length field, so insert a zero with
// enough padding characters (if using formatted I/O) to overwrite the
// zero later with 2**32 - 1, the largest possible length for a
// request (the length must fit in an int).

const int FIELDWIDTH = 11;	// large enough to hold "2147483647 "

int TIOrpcbuf::start_request() {
    if (!_mystream || !tio_ || allocate() == EOF) {
       return EOF;
    }

    finish_request();
    setr(pptr());

    const int length = 0;
    mystream().width(FIELDWIDTH);
    mystream() << length;
    _actualWidth = pptr() - rptr();

    return 0;
}

// Finish the current request by inserting its final length in the
// length field at the beginning of the request (overwriting the old
// value).  The length of the request includes its own length field.

void TIOrpcbuf::finish_request() {
    int length = pptr() - rptr();
    if (rptr() && length > 0) {
	pbump(-length);
	mystream().width(FIELDWIDTH);
	mystream() << length;
	if (_actualWidth != pptr() - rptr()) {
           DEBUGNL ("TIOrpcbuf::finish_request(): length field\'s width changed") ;
	}
	pbump(length - (pptr() - rptr()));
    }
    setr(nil);
}

// Calculate the width of the length field (_actualWidth) and check
// that all of the length field is in the buffer.  If so, extract the
// length, move the get pointer back to the beginning of the request,
// and return 0 if all of the request is in the buffer.  Otherwise,
// return EOF.  By checking for EOF, the caller avoids extracting a
// request before it's completely buffered.  The caller must call
// select() to wait for new input and call TIOrpcbuf::underflow() to
// enqueue it until the complete request is buffered.

int TIOrpcbuf::read_request() {
    if (!_mystream) {
       DEBUGNL ("TIOrpcbuf::read_request(): !_mystream") ;
       return EOF;
    }

    if (!_actualWidth) {
	char* orig = pptr();
	const int length = 0;
	mystream().width(FIELDWIDTH);
	mystream() << length;
	_actualWidth = pptr() - orig;
	pbump(orig - pptr());
    }

    int navail = in_avail();
    if (navail < _actualWidth) {
       // do not DEBUGNL since this is a common case
       // DEBUGNL ("TIOrpcbuf::read_request(): navail < _actualWidth") ;
       return EOF;
    }

    char* orig = gptr();
    int length = 0;
    mystream() >> length;
    gbump(orig - gptr()); // reset gptr()

    if (length <= 0) {
       DEBUGNL ("TIOrpcbuf::read_request(): zero or negative length") ;
       return EOF;
    }

    // make the get area large enough to hold twice the request
    // size (shouldn't simply the size be enough? ++++)
    if (length > ebuf() - eback() && !expand_g(length * 2)) {
       DEBUGNL ("TIOrpcbuf::read_request(): out of memory") ;
       return EOF;
    }

    if (navail < length) {
       DEBUGNL ("TIOrpcbuf::read_request(): navail < length") ;
       return EOF;
    } else {
       return 0;
    }
}

// Finish the current RPC request if there's nothing to append to it,
// thus allowing flush to send the current RPC request.  Send any
// outgoing data using a loop to safeguard against partial writes.
// Shift any still incomplete RPC request to the beginning of the put
// area to make room for more data.  Append the overflow char if any.

int TIOrpcbuf::overflow(int c) {
    if (!tio_ || allocate() == EOF) {
	return EOF;
    }

    // c == EOF means ostream::flush() has been called. have to finish the
    // current request (insert the length field) if any.
    if (c == EOF) {
	finish_request();
    }

//     // jfasch 20.3.1995: what if the request does *not* start at pbase()? Might
//     // be the case if a prior request was not terminated by flush(), but by a new
//     // request. Then the old request remains in the put area as regular stream
//     // data (finish_request() does neither ostream::flush() nor ostream::sync().
//     if (rptr() >= pbase() && pptr() >= epptr() && !expand_p()) {

    if (rptr() == pbase() && pptr() >= epptr() && !expand_p()) {
       DEBUGNL ("TIOrpcbuf::overflow(): out of memory") ;
       return EOF;
    }

    // bit of a hack, but nonetheless legitimate. I have to do this
    // because the rpcbu(f|g) cannot handle writes onto a nonblocking
    // connection.
    TransparentIOHandler* reader = (TransparentIOHandler*) tio_.ptr()->reader() ;
    TransparentIOHandler* writer = (TransparentIOHandler*) tio_.ptr()->writer() ;
    
    // set the whole graffl to block
    tio_.ptr()->registerInput (nil) ;
    tio_.ptr()->registerOutput (nil) ;

    // write any data in the put area except for the current request (if any).
    int nwrite = (rptr() >= pbase()) ? rptr() - pbase() : out_waiting();
    int nwritten = tio_.ptr()->write (pbase(), nwrite) ;

    // and restore the old state
    tio_.ptr()->registerInput (reader) ;
    tio_.ptr()->registerOutput (writer) ;

    if (nwritten != nwrite) {
       DEBUGNL ("TIOrpcbuf::overflow(): could not write ("<<nwritten<<')') ;
       return EOF ;
    }

    // if there is a request in the put area, and there were any
    // regular data preceding that, copy the request down to the
    // beginning of the area and adjust the request pointer.
    if (rptr() > pbase()) {
       ::memcpy (pbase(), rptr(), pptr() - rptr());
       rbump(-nwrite);
    }

    // adjust the put pointer (should probably be: if (nwrite) ... ++++)
    pbump(-nwrite);

    // append the overflow character
    if (c != EOF) {
	sputc(c);
    }

    return zapeof(c);
}

// Empty the put area before filling the get area in case the input
// depends on the output just flushed.  Move any unread data between
// gptr() and egptr() to the beginning of the get area.  The get area
// may contain unread data under nonblocking I/O because an incomplete
// RPC request is not extracted until the rest of its data arrives.
// Read as much data as available into the free space between egptr()
// and ebuf() (the get area occupies the entire buffer).  Move egptr()
// to the end of the new data.  Return the first unread character.

int TIOrpcbuf::underflow() {
    if (!tio_ || allocate() == EOF) {
	return EOF;
    }

    // Empty the put area before filling the get area in case the input depends
    // on the output just flushed.
    if (overflow() == EOF) {
       DEBUGNL ("TIOrpcbuf::underflow(): overflow() failed") ;
       return EOF;
    }

    // copy any unread data to the beginning of the get area to make
    // space for the data to be read below ...
    int nunread = in_avail();
    if (nunread) {
#ifdef WIN32
       return (unsigned char) *gptr();
#else
       memcpy (eback(), gptr(), nunread);
#endif
    }
    // .. and adjust the istream pointers accordingly.
    setg(eback(), eback(), eback() + nunread);

    // fill the rest of the get area (at least a part of it).

    int nread ;
    // if a reader is registered, we can be sure that there's data to
    // read because my user must have been notified of it. if there is
    // no data, this is either a bug (my user has not been notified,
    // but rather calls me out of his own will), or it is an internal
    // (TIO (-->system?)) error.
    if (tio_.ptr()->reader()) {
       nread = tio_.ptr()->read (egptr(), ebuf() - egptr()) ;
    }
    // if no reader is registered, I have to do this whole stuff in a
    // blocking manner. same hack as in overflow(). see there for an
    // explanation.
    else {
       TransparentIOHandler* writer = (TransparentIOHandler*)tio_.ptr()->writer() ;
       tio_.ptr()->registerOutput (nil) ;
       nread = tio_.ptr()->read (egptr(), ebuf() - egptr()) ;
       tio_.ptr()->registerOutput (writer) ;
    }

    if (nread == 0) {
       DEBUGNL ("TIOrpcbuf::underflow(): read(): encountered eof") ;
       return EOF ;
    }
    else if (nread < 0) {
       DEBUGNL ("TIOrpcbuf::underflow(): read(): encountered an error") ;
       return EOF ;
    }

    setg(eback(), gptr(), egptr() + nread);
    return zapeof(*gptr());
}

#ifdef WIN32
int TIOrpcbuf::readon() {
   if (!tio_ || allocate() == EOF) 
      return EOF;

   if (overflow() == EOF) 
      return EOF;

   int nunread = in_avail();
   if (nunread) 
      memcpy (eback(), gptr(), nunread);
   setg(eback(), eback(), eback() + nunread);

   int nread;
   // same as in underflow(). see there for an explanation.
   if (tio_.ptr()->reader())
      nread = tio_.ptr()->read (egptr(), ebuf() - egptr()) ;
   else {
      TransparentIOHandler* writer = (TransparentIOHandler*)tio_.ptr()->writer() ;
      tio_.ptr()->registerOutput (nil) ;
      nread = tio_.ptr()->read (egptr(), ebuf() - egptr()) ;
      tio_.ptr()->registerOutput (writer) ;
   }
   
   if ( (nread == 0) && (nunread==0) )
      {
         setg(0, 0, 0);
         return EOF;
      }

   setg(eback(), gptr(), egptr() + nread);
   return (unsigned char)*gptr();
}
#endif

// Probably called from ios's destructor.  Can't do anything with
// still unread data except discard it, but can flush any outgoing RPC
// requests from the put area.

int TIOrpcbuf::sync() {
/* gpani: (16.3.94) -- gnu has 'normal' streams */
/* gorasche: WinNT too! */
#if (defined(__GNUC__) || defined(WIN32))
   if (out_waiting()) {
      if (!tio_ || allocate() == EOF)
         return EOF;

      finish_request();
	  
//           // jfasch 20.3.1995 (for an explanation see overflow())
// 	  if (rptr() >= pbase() && pptr() >= epptr() && !expand_p()) {
      // (not changed)

      if (rptr() == pbase() && pptr() >= epptr() && !expand_p()) {
         DEBUGNL ("TIOrpcbuf::sync(): out of memory") ;
         return EOF;
      }
	  
      // same as in overflow. see there for an explanation.
      TransparentIOHandler* reader = (TransparentIOHandler*)tio_.ptr()->reader() ;
      TransparentIOHandler* writer = (TransparentIOHandler*)tio_.ptr()->writer() ;
      tio_.ptr()->registerInput (nil) ;
      tio_.ptr()->registerOutput (nil) ;
      int nwrite = (rptr() >= pbase()) ? rptr() - pbase() : out_waiting();
      int nwritten = tio_.ptr()->write (pbase(), nwrite) ;
      tio_.ptr()->registerInput (reader) ;
      tio_.ptr()->registerOutput (writer) ;
      
      if (nwritten != nwrite) {
         DEBUGNL ("TIOrpcbuf::sync(): could not write ("<<nwritten<<')') ;
         return EOF ;
      }

      // if there is a request in the put area, and there were any
      // regular data preceding that, copy the request down to the
      // beginning of the area and adjust the request
      // pointer. (see also in overflow().)
      if (rptr() > pbase()) {
         ::memcpy (pbase(), rptr(), pptr() - rptr());
         rbump(-nwrite);
      }
      pbump(-nwrite);
   }
   return 0;
#else
   gbump(in_avail());
   return out_waiting() ? overflow() : 0;
#endif
   }

// Can't seek on a socket, but can return the get pointer's current
// position so that the caller can find out how many bytes he read
// since the get pointer's last position (within the same request).

#if (defined(cplusplus_2_1) || defined(WIN32))
streampos TIOrpcbuf::seekoff(streamoff offset, ios::seek_dir dir, int mode)
#else
streampos TIOrpcbuf::seekoff(streamoff offset, seek_dir dir, int mode)
#endif
   {
      if (!tio_ || !gptr()) {
         return EOF;
      }

      if (offset != 0 || dir != ios::cur || mode != ios::in) {
         return EOF;
      }
   
      return (streampos)gptr();
   }

// Refuse any attempt to set the buffers for storing incoming and
// outgoing RPC requests because we need the ability to dynamically
// expand the buffers' sizes.

streambuf* TIOrpcbuf::setbuf(char*, int) {
    return nil;
}

// Dynamically allocate two separate buffers for storing incoming and
// outgoing RPC requests.  Allocating separate buffers for the get and
// put areas makes it easier to expand either area later if necessary.

// int TIOrpcbuf::doallocate() {
//     const int TIORPCBUFSIZE = 2032;

//     char* get = new char[TIORPCBUFSIZE];
//     if (!get) {
// 	error("TIOrpcbuf::doallocate: out of memory");
// 	return EOF;
//     }
//     setb(get, get + TIORPCBUFSIZE, true);
//     setg(get, get, get);

//     char* put = new char[TIORPCBUFSIZE];
//     if (!put) {
// 	error("TIOrpcbuf::doallocate: out of memory");
// 	return EOF;
//     }
//     setp(put, put + TIORPCBUFSIZE);
//     setr(nil);

//     return 0;
// }
int TIOrpcbuf::doallocate() {
    const int TIORPCBUFSIZE = 2032;

    char* get = new char[TIORPCBUFSIZE];
    if (!get) {
	DEBUGNL ("TIOrpcbuf::doallocate(): out of memory");
	return EOF;
    }
    setb(get, get + TIORPCBUFSIZE, false);
    setg(get, get, get);

    char* put = new char[TIORPCBUFSIZE];
    if (!put) {
	DEBUGNL ("TIOrpcbuf::doallocate(): out of memory");
	return EOF;
    }
    setp(put, put + TIORPCBUFSIZE);
    setr(nil);

    return 0;
}

// Expand the get area to make room for a large incoming request.

boolean TIOrpcbuf::expand_g(int newsize) {
    char* get = new char[newsize];
    if (!get) {
	return false;
    }

    int navail = in_avail();
    ::memcpy (get, gptr(), navail);
// --------- in TIOrpcbuf::doallocate    setb(get, get + TIORPCBUFSIZE, true);                  // kandrews
// ---------    true means automatically delete when setb( ) or ~streambuf() is called 
//     delete eback();

    // equals the get area, I think. I pass false ("dont delete it")
    // to setb() on every occasion, so I have to do it here.
    delete base() ; 

    setb(get, get + newsize, false); // you see? false!
    setg(get, get, get + navail);

    return true;
}

// Expand the put area to make room for additional data to be appended
// to an outgoing request.

boolean TIOrpcbuf::expand_p() {
    int newsize = (epptr() - pbase()) * 2;
    char* put = new char[newsize];
    if (!put) {
	return false;
    }

//    // jfasch 20.3.1995 (see below): the length of the regular stream data
//    // preceding the current request:
//    int reg_datalen = (rptr() >= pbase()) ?  rptr() - pbase() :  0 ;

    // out_waiting() is the number of chars between pbase() and pptr()
    int nwaiting = out_waiting();
    // copy the old put area (i.e., the interesting bytes) to the new one
    ::memcpy (put, pbase(), nwaiting);
    delete pbase();
    // set pbase() and pptr() to the beginning of the new put area (pbump follows
    // below), and epptr() to the end of it.
    setp(put, put + newsize);
    // adjust pptr()
    pbump(nwaiting);
    // the request begins at the beginning of the put area
    setr(put);

//     // jfasch 20.3.1995: but what if there was some regular stream data preceding
//     // the request in the old put area? (if (reg_datalen) ... ++++)
//     rbump (reg_datalen) ;

    return true;
}

