//=-- Serialize.h - <de>Serialize data ---------------------------------------=
//
//  The class handles serialization of data to a serialization file.  It is 
//    fairly typesafe, and it's pretty much kinda cool.
//
//=---------------------------------------------------------------------------=
//  This file is copyright (c) 1997-1998 Chris Lattner
//=---------------------------------------------------------------------------=

#include <stdio.h>       // Yes Jim, we do use standard io...
#include <assert.h>
#include <string.h>      // strlen, memcpy, memset

#include "Serialize.h"

// The F member of the Serialize class is declared as a void* in the class
// decl. so that it is not neccesary to include <stdio.h> with everything that
// includes Serialize.h.  If only C++ let you do real type hiding... or if only
// FILE was a class or struct...


// Obviously, you can create an object with a filename...   If the
//   OpenForReading parameter is true, the file is opened for reading, 
//   if it is false, the file is opened for writing...
//
Serialize::Serialize(const char *Filename, int OpenForReading) { 
  ErrorMethod = 0;
  F = 0;
  
  if (OpenForReading)
    Load(Filename);
  else
    Save(Filename);
}

// eof() - Returns true if past end of file...
//
int Serialize::eof() {
  return feof((FILE*)F);
}


// PeekEOF() - Returns true if the next byte is past the end of file
//
int Serialize::PeekEOF() {
  char c = fgetc((FILE*)F);
  int i = feof((FILE*)F);
  ungetc(c, (FILE*)F);
  return i;
}


// <sigh> char* uses strlen     // Handle insertion of char *'s
Serialize &Serialize::operator <<(const char *P) {
	return Write(CHARPTR, P, strlen(P)+1);
}

// Don't pass a ptr to a char*  // Handle extraction of char *'s
Serialize &Serialize::operator >>(char *i) {
  return Read(CHARPTR, i, (unsigned)-1);
}

// Load() - Open a file for reading...
//
void Serialize::Load(const char *Filename) {
  Close();
  IsReading = 1;
  F = fopen(Filename, "rb");
  if (F == NULL) 
    ReportError("Error opening file for reading!");
}

// Save() - Open a file for writing...
//
void Serialize::Save(const char *Filename) {
  Close();
  
  IsReading = 0;                   // Saving is not reading
  F = fopen(Filename, "wb");
  if (F == NULL)
    ReportError("Error opening file for writing!");
}

// Close() - Close the data file associated with SerMan object...
//
void Serialize::Close() {
  IsReading = -1;
  AtSection = ErrorFlag = 0;
  
  if (F != NULL) {
    fclose((FILE*)F);
    F = NULL;
  }
}



// ReportError() - This function is called whenever an error happens.  In
//   subclasses, this can be overriden to provide customized error handling.
//
void Serialize::ReportError(const char *Message) {

  ErrorFlag = 1;
  switch (ErrorMethod) {
  case ERR_SET_FLAGS:
    break;

  case ERR_STDOUT:
    printf("\nSerialize ERROR: %s\n", Message);
    break;

  case ERR_STDERR:
    fprintf(stderr, "\nSerialize ERROR: %s\n", Message);
    break;

  case ERR_ASSERT:
    fprintf(stderr, "\nSerialize ERROR: %s\n", Message);
    assert(ErrorFlag == 0);
    break;

  #ifdef EXCEPTIONS
  case ERR_EXCEPTION:
    throw SerializationException(Message);
    break;
  #endif
  };
}

int Serialize::PeekLength(unsigned char *TYPE, int Restore) {
  unsigned int RealLength;
  long SavedPos = 0;
  int T;
  
  if (AtSection) return -1;         // At the end of the section!

  if (Restore) SavedPos = ftell((FILE*)F);

  T = fgetc((FILE*)F);

  if (T == EOF) {      // Uh-oh... Mr. Bad Guy EOF!
    if (Restore) ungetc(T, (FILE*)F);
    return -1;
  }
  
  if (TYPE != 0)       // If we need to return the type:
    (*TYPE) = T/32;    //   High bits indicate type, unless SIZE is returned
  
  if (T / 32 != SIZE) {// If SIZE wasn't returned, then it's a simple case
    if (Restore) ungetc(T, (FILE*)F);
    return T & 31;     //   where size < 32
  }
  
  if (TYPE != 0)       // If the high bits indicate SIZE, the low bits 
    (*TYPE) = T & 31;  //   indicate the real type...
  
  // Read the real size from the stream (up to 4 gigs! :)...
  fread(&RealLength, sizeof(RealLength), 1, (FILE*)F);
  
  if (Restore)         // Restore stream position to original position...
    fseek((FILE*)F, SavedPos, SEEK_SET);
  
  return RealLength;   // Finally, return the REAL length.
}

// Write() - Writes a chunk of data to the output file as a type.
//
Serialize &Serialize::Write(unsigned char Type, const void *Data, unsigned int Length) {
  if (IsReading != 0) {
    ReportError("File not opened for saving!");
    return *this;
  }

  // If the type is some kind of integer, reduce it into the smallest
  //   integral type nessesary to hold the data - to save disk space.
  if (Type == UINT) {
    unsigned int NewLen=1;
    for (unsigned int i = 0; i < Length; i++) {
      if (((char*)Data)[i])
        NewLen = i+1;
    }

    Length = NewLen;
  } else if (Type == SINT)  {
    unsigned int NewLen=1;
    signed char Sign = 0;

    if (((signed char *)Data)[Length-1] < 0)
      Sign = -1;

    for (unsigned int i = 0; i < Length; i++) {
      if (((signed char*)Data)[i] != Sign)
        NewLen = i+1;
    }
    
    if ((((unsigned char)Sign) & 128) != (((unsigned char*)Data)[NewLen-1] & 128))
      NewLen++;

    Length = NewLen;
  }

  if (Length < 32) { 
    Type = Type*32+Length;
    fwrite(&Type, sizeof(char), 1, (FILE*)F);
  } else {
    Type = SIZE*32 + Type;
    fwrite(&Type, sizeof(char), 1, (FILE*)F);
    fwrite(&Length, sizeof(Length), 1, (FILE*)F);
  }
  
  if (Length > 0)
    fwrite(Data, Length, 1, (FILE*)F);

  if (ferror((FILE*)F))
    ReportError("Write Failed!");
  return *this;
}

// Read() - Reads a chunk of data from the output file, verifying the type.
//
Serialize &Serialize::Read(unsigned char Type, void *Data, unsigned int Size) {
  unsigned int Length;
  unsigned char type;

  if (IsReading != 1) {
    ReportError("File not opened for loading!");
    return *this;
  }

  if (AtSection) {
    ReportError("At the end of the file section!");
    return *this;
  }
  
  Length = PeekLength(&type, 0);
  if (Length == (unsigned)-1) {
    ReportError("Read past end of file!");
    return *this;
  }

  if ((Length > Size) && ((Type == SINT) || (Type == UINT))) {  
    fseek((FILE*)F, Length, SEEK_CUR);  // Skip past it...
    ReportError("Cannot deserialize integer type into smaller integer container!");
    return *this;
  }

  if (type != Type) { // Error... we have to read into the same type... 
    fseek((FILE*)F, Length, SEEK_CUR);  // Skip past it...
    if (type == SECTION) {
      AtSection = 1;
      ReportError("At the end of the file section!");
    } else
      ReportError("Invalid typecast!");

    return *this;
  }
  
  if (Length > 0)
    fread(Data, Length, 1, (FILE*)F);

  if (Length < Size) {
    if (type == UINT) {
      for (unsigned int i = Length; i < Size; i++)
        ((unsigned char*)Data)[i] = 0;

    } else if (type == SINT) {
      signed char Sign;        // The sign bits for the number
      
      if (((signed char *)Data)[Length-1] < 0)
        Sign = -1;
      else
        Sign = 0;

      for (unsigned int i = Length; i < Size; i++)
        ((unsigned char*)Data)[i] = Sign;
    }
  }

  if (ferror((FILE*)F))
    ReportError("Read Failed!");
  return *this;
}

// NewSection() - Starts a new section in the output file.
//
void Serialize::NewSection() {
  Write(SECTION, 0, 0);
}

// NextSection() - Goes to the next section in an input file
//
void Serialize::NextSection() {
  if (!AtSection) {
    unsigned char Type = !SECTION;
    int Length;

    while (Type != SECTION) { 
      Length = PeekLength(&Type, 0);
      if (Length < 0) return;
      fseek((FILE*)F, Length, SEEK_CUR);    // Skip data...
    }
  }

  AtSection = 0;
}
