
/*
   common parts of buffered methods.  these are general purpose ones,
   ie these do the standardized parts of management of buffered streamoids.
   they call the device-specific methods to do the actual data transfers.
*/

#include "io-defs.h"
#include <file.h>
#include <errno.h>

/*
   put streamoid's current output buffer back where it belongs
   in the file.  only used with bidirectional streamoids.
*/
local void __rewrite_output_buffer(self)
struct streamoid * self;
{
  int result;

/* make sure handle's positioned to where this buffer lives */
  if (self->flags & STR_BUF_READ_P)
	method_call_2(self, METHOD_SEEK_I, self->buffer_pos, L_SET);
/* and put this buffer back in the file */
  result = method_call_0(self, METHOD_PUTBUF);
  if (result < 0)
	errno = self->last_error = result;
  self->flags &= ~STR_BUF_DIRTY_P;
}

/*
   advance the position-in-file of the current (presumably exhausted)
   buffer by the current buffer index.
*/
local void __advance_input_buffer(self)
struct streamoid * self;
{
  long buffer_index = self->buffer_index;

  if ((self->flags & STR_OUTPUT_P) &&		/* bidirectional stream? */
      (self->flags & STR_BUF_DIRTY_P))
	__rewrite_output_buffer(self);

  self->flags &= ~STR_BUF_READ_P;		/* no longer have a read buf */
  self->buffer_pos += buffer_index;
  self->buffer_index = self->buffer_nbytes = 0;
}

/*
   general purpose get-more-input function, for buffered streamoids 
*/
static void __setup_next_input_buffer(self)
struct streamoid * self;
{
  int nbytes_read;

  if (self->flags & STR_AT_EOF_P)
	return;				/* if at eof, just give up */
  if (self->buffer_nbytes >= 0)
	__advance_input_buffer(self);
  nbytes_read = method_call_0(self, METHOD_GETBUF);
  self->flags |= STR_BUF_READ_P;	/* say this buf was read from file */
  if (nbytes_read == 0)			/* eof? */
	self->flags |= STR_AT_EOF_P;
  if (nbytes_read < 0)			/* error? */
	errno = self->last_error = nbytes_read;
  self->buffer_nbytes = nbytes_read;
  self->buffer_index = 0;
}

/*
   get one byte/char.  helper used by bin/text guys
*/
inline static int __tyi_internal(self)
struct streamoid * self;
{
  if (self->buffer_index >= self->buffer_nbytes)
	{
	__setup_next_input_buffer(self);
	if (self->flags & STR_AT_EOF_P)
		return(EOF_VALUE);		/* too bad this is so gross... */
	}
  self->file_position++;
  return(self->buffer[self->buffer_index++]);
}

/*
   get a byte, binary mode.
*/
DEFMETHOD m_tyi_binary(self)
struct streamoid * self;
{
  if (self->flags & STR_UNTYIED_P)
	{
	self->flags &= ~STR_UNTYIED_P;
	self->file_position++;
	return(self->untyied_char);
	}
  return(__tyi_internal(self));
}

/*
   get a character, text mode
*/
DEFMETHOD m_tyi_text(self)
struct streamoid * self;
{
  int ch;

  if (self->flags & STR_UNTYIED_P)
	{
	self->flags &= ~STR_UNTYIED_P;
	self->file_position++;
	return(self->untyied_char);
	}
  ch = __tyi_internal(self);

  if (ch == '\r')
	{
	int next_ch = __tyi_internal(self);
	
	if (next_ch == '\n')
		ch = next_ch;
	    else
		m_untyi_byte(self, next_ch);
	}
  return(ch);
}

/*
   get a bufferful.  common to both binary and text, as it currently
   just tyi's until eof or buf is full.  later, redo the binary case.
   returns nbytes read into buf.
*/
DEFMETHOD m_string_in(self, buf, nbytes_buf)
struct streamoid * self;
char * buf;
int nbytes_buf;
{
  int nbytes_read = 0;
  int ch;

  while ((nbytes_read < nbytes_buf) &&
  	 ((ch = method_call_0(self, METHOD_TYI)) != EOF_VALUE))
	buf[nbytes_read++] = ch;
  return(nbytes_read);
}

/*
   get a line. common to both binary and text, as it currently just
   tyi's until eol.  returns nbytes read into buf
*/
DEFMETHOD m_line_in(self, buf, nbytes_buf)
struct streamoid * self;
char * buf;
int nbytes_buf;
{
  int nbytes_read = 0;
  int ch;
  
  while ((nbytes_read < nbytes_buf) &&
	 ((ch = method_call_0(self, METHOD_TYI)) != EOF_VALUE))
	{
	buf[nbytes_read++] = ch;
	if (ch == '\n')
		break;
	}
  buf[nbytes_read] = '\0';
  return(nbytes_read);
}

/*
   output side stuff
*/

/*
   reset pointers, indices, and such, to set up for another buffer
*/
static void __advance_output_buffer(self)
struct streamoid * self;
{
  self->buffer_pos += self->buffer_nbytes;
  self->buffer_index = self->buffer_nbytes = 0;
}

static void __setup_new_output_buffer(self)
struct streamoid * self;
{
  int result;

  if (self->buffer_nbytes > 0)
	{
	if ((self->flags & STR_INPUT_P) &&	/* bidirectional stream? */
	    (self->flags & STR_BUF_DIRTY_P))
		__rewrite_output_buffer(self);
	    else
		{
		/* not bidirectional, do it the easy way */
		result = method_call_0(self, METHOD_PUTBUF);
		if (result < 0)				/* error? */
			errno = self->last_error = result;
		}
	}
  __advance_output_buffer(self);
}

void __force_output(self)
struct streamoid * self;
{
  if (self->flags & STR_OUTPUT_P)
	__setup_new_output_buffer(self);
}

/*
   guts of tyo
*/

inline static __tyo_internal(self, ch)
struct streamoid * self;
{
  if (self->buffer_index >= self->buffer_size)
	__setup_new_output_buffer(self);
  self->buffer[self->buffer_index++] = ch;
  if (self->buffer_nbytes < self->buffer_index)
	self->buffer_nbytes = self->buffer_index;
  self->file_position++;	
}

DEFMETHOD m_tyo_binary(self, byte)
struct streamoid * self;
int byte;
{
  self->flags |= STR_BUF_DIRTY_P;
  __tyo_internal(self, byte);
}

DEFMETHOD m_tyo_text(self, ch)
struct streamoid * self;
int ch;
{
  self->flags |= STR_BUF_DIRTY_P;
  if (ch == '\n')
	__tyo_internal(self, '\r');
  __tyo_internal(self, ch);
}

/* 
   write a bufferfull 
*/
DEFMETHOD m_string_out(self, buf, nbytes_buf)
struct streamoid * self;
char * buf;
int nbytes_buf;
{
  int i = 0;

  while (i < nbytes_buf)
	method_call_1(self, METHOD_TYO, buf[i++]);
}

/* 
   write a line. 
*/
DEFMETHOD m_line_out(self, buf)
struct streamoid * self;
char * buf;
{
  int ch;
/*  int eol_p; */

  while (ch = *buf++)
	{
	method_call_1(self, METHOD_TYO, ch);
/*	eol_p = (ch == '\n');	*/
	}
/*  if (!eol_p)
	method_call_1(self, METHOD_TYO, '\n'); */
}

/*
   General purpose seek method for buffered streams.  This should
   work for bidirectional streams, as well as unidirectional ones.
*/
DEFMETHOD m_seek(self, pos, how)
struct streamoid * self;
long pos;
int how;
{
  long result, real_pos, desired_pos;

  self->flags &= ~STR_AT_EOF_P;		/* always clear this */
/* first see if we have the buffer that this pos corresponds to */
  switch (how)
	{
	case L_SET:  { desired_pos = pos; break; };
	case L_INCR: { desired_pos = self->file_position + pos; 
		       how = L_SET;
		       break;
		     }
	case L_XTND: { desired_pos = -1; break; };	/* give up */
	default:     { errno = self->last_error = EBADARG ; return(-1); };
	};
  if ((desired_pos >= 0) &&
	(desired_pos >= self->buffer_pos) &&
	(desired_pos < (self->buffer_pos + self->buffer_nbytes)))
	{
	/* we win.  just set the buffer pointer */
	self->buffer_index = desired_pos - self->buffer_pos;
	return(self->file_position = desired_pos);
	}
/*
   ok, we don't have a buffer, or it didn't contain the position we want.
   flush any current buffers, and position the underlying handle.
*/
  if ((self->flags & STR_BUF_DIRTY_P))
	__rewrite_output_buffer(self);
/*
   this part isn't strictly necessary, but aids efficiency in cases
   where the caller is scanning backwards thru a file.  if desired_pos
   is less than current pos, seek back to desired_pos - buffer_size/2,
   then read a buffer and adjust indices.  If anything loses, we give
   up and skip the optimization, fall thru to the standard way.
*/
  if ((desired_pos >= 0) &&
      (desired_pos < self->buffer_pos))
    {
      int real_seek_pos = desired_pos - (self->buffer_size / 2);
	  
      if (real_seek_pos < 0)
	real_seek_pos = 0;
      result = real_pos = method_call_2(self, METHOD_SEEK_I, real_seek_pos, 0);
      if (result >= 0)
	{
	  self->buffer_pos = real_pos;	/* say where this buffer lives */
	  result = method_call_0(self, METHOD_GETBUF);
	  if (result >= 0)
	    {
	      self->buffer_nbytes = result;
	      self->buffer_index = desired_pos - real_seek_pos;
	      return(self->file_position = desired_pos);
	    }
	}
    }
   else			/* seeking forward */
    result = real_pos = method_call_2(self, METHOD_SEEK_I, pos, how);

  if (result < 0)			/* error? */
    {
      errno = self->last_error = result;
/* make sure we know what the current pos is */
      real_pos = method_call_2(self, METHOD_SEEK_I, 0, L_INCR);
    }
   else
    real_pos = result;
  self->buffer_pos = self->file_position = real_pos;
  self->buffer_nbytes = self->buffer_index = 0;
  return(real_pos);
}

