// This may look like C code, but it is really -*- C++ -*-
/*
 ************************************************************************
 *
 *			   Class Endian_IO
 *	to read integers of various sizes taking the byte order
 *			    into account
 *			  and bit-stream IO
 *
 *   Two different byte orders are supported
 *	bigendian	- most significant byte first
 *	littleendian	- most significant byte last
 *
 * $Id: endian_io.h,v 2.11 2000/10/19 01:55:08 oleg Exp oleg $
 *
 ************************************************************************
 */

#if !defined(__GNUC__)
#pragma once
#else
#pragma interface
#endif

#ifndef endian_io_h
#define endian_io_h

#include <fstream.h>
#include "myenv.h"

#if defined(__GNUC__)
#define _IOS_Bin_               ios::bin
#elif defined(__SUNPRO_CC)
#define _IOS_Bin_               0
#else
#define _IOS_Bin_               ios::binary
#endif

				// Extension to 'ios' to keep byte order
				// and stream sharing info
class EndianIOData : virtual public ios
{
  enum { MSBfirst, LSBfirst } byte_order;
  bool shared;			// Was this stream shared with some other
				// stream?

				// These are deliberately private and
				// unimplemented to discourage
				// copy-constructing
  EndianIOData(const EndianIOData&);
  EndianIOData& operator = (const EndianIOData&);

  				// ios::delbuf(0) is usually used to prevent
  				// destruction of the ios buffer, if any
				// libstdc++ doesn't use it, and doesn't define
#if defined(__GNUC__) || defined(B_BEOS_VERSION)
  void delbuf(const int) {}	// so we define it here, for generality
#endif

public:
				// Means having this stream reference the same
				// disk structure *and* the memory buffer
				// as a_file
  void share_with(EndianIOData& a_file);
  void unshare(void);		// Check for sharing and break it
  EndianIOData(void) : byte_order(LSBfirst), shared(false)  {}
  ~EndianIOData(void)		{ unshare(); }

  void set_bigendian(void)  		// Most significant byte first
  				{ byte_order = MSBfirst; }
  void set_littlendian(void)		// Least significant byte first
				{ byte_order = LSBfirst; }
  bool q_MSBfirst(void) const	{ return byte_order == MSBfirst; }
  bool was_shared(void) const	{ return shared; }
				// Returns TRUE if something goes wrong
				// with the I/O
  bool operator ! ()	{ return !good(); }
  void error(const char *descr);
				// Make sure the stream was opened successfully
  void assert_open(const char * file_name);
};

			// Class to read (binary) data from the input stream
			// keeping in mind the byte order
class EndianIn : public EndianIOData, public ifstream
{
				// Private and not implemented: assignment
				// is prohibited.
  EndianIn& operator = (const EndianIn&);

public:
  EndianIn(void) {}
  EndianIn(const char * file_name) : ifstream(file_name,ios::in|_IOS_Bin_)
				{ assert_open(file_name); }
  ~EndianIn(void) 		{ unshare(); }

					// Open by example. File handle of
					// 'file' is dup-ed, so closing the
					// present stream would not close 'file'
  EndianIn(EndianIn& file)
  	: EndianIOData(), ifstream()	{ share_with(file); }

  void open(const char *name, int mode=ios::in|_IOS_Bin_)
		{ ifstream::open(name,mode); assert_open(name); }

  void close(void);		// Close the stream breaking sharing if any

				// The following I/O functions take
				// the char string op_description
				// that tells what this operation is for
				// On error, this string is printed along
				// with the error description
  unsigned char  read_byte(const char * op_description);
  unsigned short read_short(const char * op_description);
  unsigned long  read_long(const char * op_description);
};

inline unsigned char EndianIn::read_byte(const char * op_description) 
{
  char c;
  if( !get(c) )
    error(op_description);
  return c;
}

			// Class to write (binary) data from the input stream
			// keeping in mind the byte order
class EndianOut : public EndianIOData, public ofstream
{
				// Private and not implemented: assignment
				// is prohibited.
  EndianOut& operator = (const EndianOut&);

public:
  EndianOut(void) {}
  EndianOut(const char * file_name)
    : ofstream(file_name,ios::out|ios::trunc|_IOS_Bin_)
				{ assert_open(file_name); }
  ~EndianOut(void) 		{ unshare(); }

					// Open by example. File handle of
					// 'file' is dup-ed, so closing the
					// present file would not close 'file'
  EndianOut(EndianOut& file)
  	: EndianIOData(), ofstream()	{ share_with(file); }

  void open(const char *name, int mode=ios::out|ios::trunc|_IOS_Bin_)
		{ ofstream::open(name,mode); assert_open(name); }

  void close(void);		// Close the stream

				// The following I/O functions take
				// the char string op_description
				// that tells what this operation is for
				// On error, this string is printed along
				// with the error description

				// Note, the first operand of write_byte
				// has to be specified as 'int' rather
				// than 'unsigned char', as bizarre as
				// it may seem. Otherwise, write_byte(0xff)
				// results in the i/o error
				// 'Inappropriate IOCTL for device'
				// after some 2000-4000 writings such a byte.
  void write_byte(const int item, const char * op_description = "");
  void write_short(const unsigned short item, const char* op_description = "");
  void write_long(const unsigned long item, const char * op_description = "");
};

/*
 *------------------------------------------------------------------------
 *			Bit-stream Input-Output
 * Note, bits are written in first-in first-out mode
 */

class BitIOBuffer
{
protected:
  char buffer;
  enum { largest_bit_in_buffer = (1<<7) };
  unsigned char curr_bit_pos;
public:
  BitIOBuffer(void) : buffer(0), curr_bit_pos(largest_bit_in_buffer) {}
};

class BitIn : BitIOBuffer, public EndianIn
{
				// Private and not implemented: assignment
				// is prohibited.
  BitIn(const BitIn&);
  BitIn& operator = (const BitIn&);

public:
			// This is a dummy constructor. Use open() function
			// of EndianIO class to perform the actual opening
  BitIn(void) {}

  int  get_bit(void);			// Get a bit from the input stream
  short get_short(void);		// Get a short integer that was written
  					// using a variable size code
};

class BitOut : BitIOBuffer, public EndianOut
{
				// These are deliberately private and
				// unimplemented to discourage
				// copy-constructing
  BitOut(const BitOut&);
  BitOut& operator = (const BitOut&);

public:
			// This is a dummy constructor. Use open() function
			// of EndianIO class to perform the actual opening
  BitOut(void) {}
  ~BitOut(void);

  void put_bit(const char bit);		// Write a bit into the output stream
  void put_short(const short item);	// Write a signed short integer 
  					// using a variable size code
  void close(void);			// Close the stream
};

			// Put a bit (0/1) into the bit stream
inline void BitOut::put_bit(const char bit)
{
  if( bit )
    buffer |= curr_bit_pos;
  if( (curr_bit_pos>>=1) == 0 )
    write_byte(buffer), curr_bit_pos = largest_bit_in_buffer, buffer = 0;
}

			// Flush the bit buffer on closing the stream      
inline void BitOut::close(void)
{
					// If the buffer contains something
  if( curr_bit_pos != largest_bit_in_buffer )
  {
    write_byte(buffer);			// Flush the buffer
    curr_bit_pos = largest_bit_in_buffer, buffer = 0;
  }
  EndianOut::close();
}

inline BitOut::~BitOut(void)				{ close(); }


			// Read a bit from the bit stream
inline int BitIn::get_bit(void)
{
  if( curr_bit_pos & largest_bit_in_buffer )
  {
    if( eof() ) 
      error("Reading an 8-bit chunk");
    get(buffer);
  }
  unsigned char bit_pos = curr_bit_pos;
  if( (curr_bit_pos >>=1) == 0 )
    curr_bit_pos = largest_bit_in_buffer;
  return buffer & bit_pos ? 1 : 0;
}

#endif
