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

File: binin.cc
May 2000
K.Becker

Purpose: to show how binary I/O works
	Program to read a bunch of text records, 
	save them in a binary file (blocked)

	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

	BIOIN will
	then read them back in and print them to show they still exist

	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& tfile, fstream& bfile )
//  Cleanup      : closes files, etc.
//  WriteHeader  : to pack and write the header
//                 ( Header& head )
//
//  WritedBuffer  : to dump the buffer
//                 ( Buffer& buf )
//  Pack         : packs record into buffer
//                 ( unsigned char* buffer,  Record& rec, int recptr )
//  WriteRecord  : writes the next record; flushes 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
fstream tfile; // the text 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 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 WriteBuffer( unsigned char* buf );
int WriteHeader( head_struct& Header );

/********************************************************************/
/****************************  Startup  *****************************/
/********************************************************************/
// Purpose: get file names
//          open files
//          initialize Header and buffer

void Startup( fstream& tfile, fstream& bfile )
{
  // EXIT if file open unsuccessful

  char bname[64];
  char tname[64];

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

  tfile.open( tname, ios::in );
  if (tfile.fail())
    {
      cout << "Error opening text file. Code is: " 
	   << tfile.rdstate() << endl;
      exit(1);
    }

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

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

  Header.rec_cnt = 0;
  Header.rec1_ptr = 24;
  Header.index_cnt = 65; // just so we can see it 'a'
  Header.ilist_ptr = 66; // 'b'
  Header.free_cnt = 67; // 'c'
  Header.flist_ptr = 68; // 'd'

} // end Startup

/********************************************************************/
/**************************  Cleanup  ***************************/
/********************************************************************/
// Purpose: flush buffer if necessary (final 'write')
//          update Header
//          close files

int Cleanup (  )
{
  int OK;
  
  if (recptr != 0)
    {
      // need to flush the buffer on write
      OK = WriteBuffer( buffer );
    }
  OK = WriteHeader( Header );

  bfile.close();
  tfile.close();

} // end Cleanup

/********************************************************************/
/**************************  WriteHeader  ***************************/
/********************************************************************/
// Purpose: write current state of header at start of file
//          (done once at start to 'put it in place')
//          done at end of program

int WriteHeader ( head_struct& head )
{
  // save the header information
  unsigned char* p = (unsigned char*)&head; // pointer to header

  bfile.seekp (0, ios::beg);
  bfile.write ( p, HEADSZ );
  bufptr = 24; // now points to first buffer block
  return bfile.rdstate();
  
} // end WriteHeader

/********************************************************************/
/**************************  WriteBuffer  ***************************/
/********************************************************************/
// Purpose: writes the current contents of the buffer to the file
//          clear out buffer (set back to 0's)
//          reset pointer INTO buffer
//          update FILE pointer (left at start of last block written)

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

  // dump the next buffer-full to the file
  unsigned char* p = buf;

  bfile.seekp( bufptr+1, ios::beg );
  bfile.write( p, BLOCKSZ );
  if (bfile.rdstate() == 0)
    {
      bufptr += BLOCKSZ;
      cout << "Wrote Buffer. bufptr = " << bufptr << endl;
      recptr = 0; // start fresh
      for( i = 0; i < BUFFERSZ; i++)
	buf[i] = '\000';
      return 0;
    }
  else
    return bfile.rdstate();

}// end WriteBuffer

/********************************************************************/
/*******************************  Pack  *****************************/
/********************************************************************/
// Purpose: take contents of RECORD and load it into the buffer
//          right after the last record we put in there
//
//          assumes buffer has room for record
//           (WriteRecord handles the case where it's doesn't)
//
// NOTE: currently contains lots of superfluous output to screen
//       so we can see what it's actually writing and where
//
// Idea is to get the address of the 1st byte we want to load inside 
// the buffer; make that address also be a pointer to the type of thing
// we have to write 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 Pack( 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;


  cout << "Packing " << rec.name << " at " << recptr << endl;
  p =  &(buffer[recptr]); // p points to the beginning of the part of the 
                          // buffer we want to write to

  // QUESTION: What happens if we try to print a pointer's value when
  //           that pointer happens to point to a char?
  temp = p;
  cout << "Buffer adder: " << temp << endl;

  recsize = 13 + strlen(rec.name);
  // recsize so far... we'll add the rest as we figure it out

  // 2 BYTES
  p += 2; // leave room for the record size - it gets set last

  // 2 BYTES
  // namelen
  namelen = strlen(rec.name); // length of name
  // write 2-byte name char-count
  //   move it into 2-byte string
  temp = p;
  intp = (short int*)b2; // intp points same place as b2
  *intp = namelen;       // put a short int there
  for( i=0; i < 2; i++)  // move it into buffer, byte by byte
    {
     *p = b2[i];
      p++;
    }
  cout << "namelen: " << *intp << " p= " << temp
       << "[" << sizeof(*intp) << "]" << endl;

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

  // 4 BYTES
  // ID number
  // stuff like this must be on word boundary to convert so move
  // it to temp location first
  temp = p;
  lip = (long int*)b4; // pointer to 4-byte id number
                       // lip points to same place as b4
  *lip = rec.ID;       // put the ID there (it's 4 bytes)
  for (i = 0; i < 4; i++) // copy to buffer
    {
      *p = b4[i];
      p++;
    }
  cout << "ID: " << *lip<< " p= " << temp
       << "[" << sizeof(*lip) << "]" << endl;

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

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

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

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

  p =  &(buffer[recptr]);  // record size, at start
  intp = (short int*)b2; // intp points same place as b2
  *intp = recsize;       // put a short int there
  for( i=0; i < 2; i++)  // move it into buffer, byte by byte
    {
     *p = b2[i];
      p++;
    }

  recptr += recsize;
  
} // end Pack;

/********************************************************************/
/*************************** WriteRecord  ***************************/
/********************************************************************/
// Purpose: pack the record into the buffer if there's room
//          if not enough room, flush the buffer; then pack it

void WriteRecord ( unsigned char* buffer, Record& rec )
{
  // calculate reclen of next record
  //   if no room in buffer then buffer done; flush
  //       write buffer to file
  //       recptr = 0;
  //   Pack a Record

  int OK;
  int i;

  cout << "Writing record: " << rec.name << endl;

  // how big is this record?
  recsize = 13 + strlen(rec.name) + (10 * rec.n_of_courses);

  // is there room for the record?
  if (recsize > (BLOCKSZ - recptr)) // NO
    {
      // write 0-bytes to the end of the current buffer
      for (i = recptr; i < BLOCKSZ; i++)
	buffer[i] = '\000';

      // dump the buffer
      OK = WriteBuffer( buffer );
    }
  
  Pack( buffer, rec, recptr );
      
}// end WriteRecord

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

  Startup( tfile, bfile );

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

  OK = WriteHeader( Header );

  // read some stuff from the file - 1 record at a time
  while (! tfile.eof())
    {
      tfile >> rec.ID;
      if (tfile.eof())
	break;
      reccnt++;

      tfile  >> tname >> rec.gpa  >> nc;

      rec.n_of_courses = (unsigned char)nc;
      nlen = strlen(tname);
      rec.name = new char[nlen];
      strcpy( rec.name, tname );
      
      cout << rec.ID << " = " << rec.name << " = " << rec.gpa << " = "
	   << int(rec.n_of_courses) << endl;

      rec.courses = new char* [ nc ];
      for (i = 0; i < nc; i++)
	{
	  rec.courses[i] = new char[10];
	  for (j = 0; j < 10; j++)
	    rec.courses[i][j] = '\000';
	  tfile >> rec.courses[i];
	}

      // echo what we've got:
      for (i = 0; i < nc; i++)
	cout << i << ":                    " << rec.courses[i] << endl;

      WriteRecord( buffer, rec );

      // get rid of strings
      delete [] rec.name;
      rec.name = NULL;
      for (i = 0; i < nc; i++)
	{
	  delete [] rec.courses[i];
	}
      delete [] rec.courses;
      rec.courses = NULL;

    } // while not eof

  Header.rec_cnt = reccnt;

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