/**********************************************************************

File: binin.cc
May 2000
K.Becker

Purpose: to show how binary I/O works
	Program reads binary records, converts them to text
	and prints them out

	The records are varying length, using dynamically allocated strings
	so straight-forward block reads and writes won't work

	 - to keep it simple buffer (blocks) will be 256 bytes

	BINOUT will
        read a text file and save it as binary

	CAST ONLY POINTERS NOT DATA

	the file header is at the START of the file

	There is some unnecessary duplication of operations in these routines
	- done so origin of values is clear from reading code

ASSUMES: (checked on CC & g++ using 'sizeof')
	int = 4 bytes
	short int = 2 bytes
	long int = 4 bytes
	float = 4 bytes
	unsigned char = 1 byte

RECORD STRUCTURE IN PROGRAM:
	long int      ID;
        char*         name; ** dynamic ** length unknown
        float         gpa;
        unsigned char n_of_courses;
        char**        courses; ** dynamic ** each course = 12 bytes
	                         # of courses unknown

HEADER comes first [24 BYTES]; looks like:
         4 bytes: # of records
	 4 bytes: byte-offset of first record
	 4 bytes: # index entries (unused)
	 4 bytes: byte offset of 1st index entry (unused)
	 4 bytes: # free-list entries (unused)
	 4 bytes: byte offset of 1 free-list entry (unused)

RECORD STRUCTURE ON FILE:
         2   BYTES: record size (short int)
	 2   BYTES: length of name (short int) (no null terminator) /N/
	 1   BYTE : number of courses in list (unsigned) /M/
	 4   BYTES: ID number (int)
	 4   BYTES: gpa (float)
	 /N/ BYTES: name (ASCII)
	 /M/
	     sets of 10 BYTES: the courses (null fill)
	 

/********************************************************************/
/***********************  TABLE OF CONTENTS  ************************/
/********************************************************************/
//  Startup      : to get file names and open files
//                 ( fstream& bfile )
//  Cleanup      : closes files, etc.
//  ReadHeader   : to read and unpack the header
//                 ( Header& head )
//
//  ReadBuffer   : to dump the buffer
//                 ( Buffer& buf )
//  UnPack       : unpacks buffer into record
//                 ( unsigned char* buffer,  Record& rec, int recptr )
//  ReadRecord   : reads the next record; refillss buffer if necessary
//                 ( Record& rec )



/**********************************************************************/


#include <iostream.h>
#include <iomanip.h>
#include <stdlib.h>
#include <fstream.h>

// some handy values...

const int BLOCKSZ = 256;
const int BUFFERSZ = 257;
const int EMPTY = 0;
const int NIL = -1;
const int HEADSZ = 24;

/********************************************************************/
/****************************  structures  **************************/
/********************************************************************/
// file stuff
fstream bfile; // the binary file


// the header to the file
typedef struct head_struct {
	unsigned long int rec_cnt;
	unsigned long int rec1_ptr;
        unsigned long int index_cnt;
        unsigned long int ilist_ptr;
        unsigned long int free_cnt;
        unsigned long int flist_ptr;
     };

// what a record looks like in the program
typedef struct Record {
	long int      ID;
        char*         name;
        float         gpa;
        unsigned char n_of_courses;
        char**        courses;
      };

head_struct Header; // total size = 24 bytes

// the buffer; small so we can see what's going on
unsigned char*  buffer; 

// some other stuff we'll need...

int current = 0;       // record number 

int bufptr = 0;        // our file pointer
int recptr = 0;        // byte 'offset' within the buffer
short int nextch;      // for stepping through buffer

short int*    intp;    // generic pointer to an int - 2 bytes
float*        fltp;    // generic pointer to float - 4 bytes
long int*     lip;     // generic pointer to long int - 4 bytes
unsigned char num;     // 1 byte number

Record       rec;      // place for the Unpacked record
short int    recsize;  // size of record in file
short int    namelen;  // size of name string in file


int ReadBuffer( unsigned char* buf );
int ReadHeader( head_struct& Header );

/********************************************************************/
/****************************  Startup  *****************************/
/********************************************************************/
// Purpose: get file name
//          open file
//          load Header and buffer

void Startup( fstream& bfile )
{
  // EXIT if file open unsuccessful
  int OK;
  char bname[64];


  // intros...
  cout << "Binary File Test Program\n\n";
  cout << "Name of Binary File: ";
  cin >> bname;

  bfile.open( bname, ios::binary | ios::in );
  if (bfile.fail())
    {
      cout << "Error opening binary file. Code is: " 
	   << bfile.rdstate() << endl;
      exit(1);
    }

  buffer = new unsigned char[BUFFERSZ];
  bufptr = 0;

  OK = ReadHeader( Header );

} // end Startup

/********************************************************************/
/**************************  Cleanup  ***************************/
/********************************************************************/
// Purpose: 
//          close files

int Cleanup (  )
{
  
  bfile.close();

} // end Cleanup

/********************************************************************/
/**************************  ReadHeader  ***************************/
/********************************************************************/
// Purpose: read current state of header at start of file

int ReadHeader ( head_struct& head )
{
  unsigned char* p = (unsigned char*)&head;

  bfile.seekg (0, ios::beg);
  bfile.read ( p, HEADSZ );
  bufptr = 24; // now points to first buffer block

  cout << "N Records: " << head.rec_cnt << endl;
  cout << "1st Record Location: " << head.rec1_ptr << endl;
  return bfile.rdstate();
  
} // end ReadHeader

/********************************************************************/
/**************************  ReadBuffer  ***************************/
/********************************************************************/
// Purpose: reads the next block into the buffer
//          reset pointer INTO buffer
//          update FILE pointer (left at start of last block read)

int ReadBuffer ( unsigned char* buf )
{
  int i;

  // get the next buffer-full from the file

  bfile.seekg( bufptr, ios::beg );
  bfile.read(buf, BLOCKSZ );
  if (bfile.rdstate() == 0)
    {
      bufptr += BLOCKSZ;
      cout << "Read Buffer. bufptr = " << bufptr << endl;
      recptr = 0; // start fresh
      return 0;
    }
  else
    return bfile.rdstate();

}// end ReadBuffer

/********************************************************************/
/*******************************  UnPack  *****************************/
/********************************************************************/
// Purpose: fill the record with the data from the buffer
//
// Idea is to get the address of the 1st byte we want to read from 
// the buffer; make that address also be a pointer to the type of thing
// we have to read and then just assign the value through the pointer
// IMPORTANT: the pointer must be set to point to the beginning of a word
//            boundary or else we will generate a BUS ERROR
// Since some of these things don't start at a word boundary in the buffer
// we will use an intermediate string; load that and then transfer what 
// we've got, byte by byte, into the buffer (we can transfer byte by byte
// if the pointer thinks it's pointing to char

void UnPack( unsigned char* buffer, 
	   Record& rec,
	   int& recptr )
{

  unsigned char* p;    // used to 'index' the buffer using pointer arithmetic
  void* temp;          // used to print addresses to screen
  unsigned char b4[4]; // intermediate spot for int or float
  unsigned char b2[2]; // intermediate spot for short int
  int i,j;

  p =  &(buffer[recptr]); // p points to the beginning of the part of the 
                          // buffer we want to read from

  temp = p;
  cout << "Buffer adder: " << temp << endl;

  // 2 BYTES
  // get recsize
  for (i = 0; i < 2; i++)
    {
      b2[i] = *p;
      p++;
    }
  intp = (short int*)b2;
  recsize = *intp;

  // 2 BYTES
  // namelen
  // get 2-byte name char-count
  //   move it into 2-byte string
  temp = p;
  for( i=0; i < 2; i++) 
    {
      b2[i] = *p;
      p++;
    }
  intp = (short int*)b2; // intp points same place as b2
  namelen = *intp; 
  cout << "namelen: " << *intp << " p= " << temp
       << "[" << sizeof(*intp) << "]" << endl;

  // 1 BYTE
  // count of courses
  temp = p;
  rec.n_of_courses = *p; // 1-byte value: direct copy
  cout << "n-o-courses: " << (int)*p << " p= " << temp 
       << "[" << sizeof(*p) << "]" << endl;
  p ++;

  // 4 BYTES
  // ID number
  temp = p;
  for (i = 0; i < 4; i++) // copy to buffer
    {
      b4[i] = *p;
      p++;
    }
  lip = (long int*)b4; // pointer to 4-byte id number
  rec.ID = *lip; 
  cout << "ID: " << *lip<< " p= " << temp
       << "[" << sizeof(*lip) << "]" << endl;

  // 4 BYTES
  // GPA
  temp = p;
  for (i = 0; i < 4; i++) // copy to buffer
    {
      b4[i] = *p;
      p++;
    }
  fltp = (float*)b4; // b4 and fltp point to same place in memory
  rec.gpa = *fltp;
  cout << "GPA: " << *fltp << " p= " << temp
       << "[" << sizeof(*fltp) << "]" << endl;

  // NAMELEN BYTES
  // the name gets copied directly, byte by byte; NO NULL TERMINATOR
  rec.name = new char[namelen+1];
  for (i = 0; i < namelen; i++)
    {
      rec.name[i] = *p; // the name
      cout << rec.name[i];
      p++;
    }
  rec.name[namelen] = '\000';
  cout << endl;
  
  nextch = recptr+13 + namelen; // nextch = where we are in the buffer

  // N_OF_COURSES * 10 BYTES
  // the courses
  rec.courses = new char*[rec.n_of_courses];
  for (i = 0; i < rec.n_of_courses; i++)
    {
      rec.courses[i] = new char[11];
      for (j = 0; j < 10; j++)    // each course is given exactly 10 bytes
	{
	  rec.courses[i][j] = *p; // the name of the course
	  cout << *p;
	  p++;
	}
      rec.courses[i][10] = '\000';
      cout << endl;
      nextch += 10;
    }

  recsize = 13 + namelen + (10*rec.n_of_courses);
  
  cout << "size is: " << recsize << endl;

  recptr += recsize;
  
} // end UnPack;

/********************************************************************/
/*************************** ReadRecord  ***************************/
/********************************************************************/
// Purpose: get the next record from the buffer
//           refill buffer if necessary

int ReadRecord ( unsigned char* buffer, Record& rec )
{
  // get reclen of next record
  //   if 0 refill
  //       recptr = 0;
  //   read buffer from file
  //   UnPack a Record

  int OK;
  int i;
  unsigned char* p;
  unsigned char b2[2];

  current++;
  cout << "Reading record: " << current << endl;
  p =  &(buffer[recptr]); // p points to the beginning of the part of the 
                          // buffer we want to read from

  // how big is this record?
  // 2 BYTES
  // get recsize
  for (i = 0; i < 2; i++)
    {
      b2[i] = *p;
      p++;
    }
  intp = (short int*)b2;
  recsize = *intp;


  // is there another record?
  if (recsize == 0) // NO
    {
      // get the next buffer full
      OK = ReadBuffer( buffer );
    }
  
  UnPack( buffer, rec, recptr );
      
}// end ReadRecord

/********************************************************************/
/********************** BEGIN MAIN **********************************/
int main ()
{
  int OK;
  int i,j;
  Record rec;
  char tname[256];
  int nlen;
  int total;

  Startup( bfile );

  cout << "here we go... " << endl;

  total = Header.rec_cnt;
  cout << "current=" << current << endl;
  cout << "rec_cnt=" << total << endl;


  // read some stuff from the file - 1 record at a time
  // NOTICE: we can't simply test for end-of-file here because we are reading
  //         a block at a time and there are several records to process 
  //         in each block-full. Since the loop is run record-by-record
  //         a test for end-of-file might cause us to omit some records

  while ( current < total )
    {
      cout << "IN" << endl;
      OK = ReadRecord( buffer, rec );
      
      cout << rec.ID << " = " << rec.name << " = " << rec.gpa << " = "
	   << int(rec.n_of_courses) << endl;
      
      // echo what we've got:
      for (i = 0; i < rec.n_of_courses; i++)
	cout << i << ":                    " << rec.courses[i] << endl;
      
      // get rid of strings for the next round
      delete [] rec.name;
      rec.name = NULL;
      for (i = 0; i < rec.n_of_courses; i++)
	{
	  delete [] rec.courses[i];
	}
      delete [] rec.courses;
      rec.courses = NULL;

    } // while not end-of-records

  Cleanup();
  cout << "DONE." << endl;
  return 0;
} // END PROGRAM ---
