/*
Pack/UnPack Library for UIUC Parallel Programming Lab
Orion Sky Lawlor, olawlor@uiuc.edu, 4/5/2000

This library allows you to easily pack an array, structure,
or object into a memory buffer or disk file, and then read 
the object back later.  The library will also handle translating
between different machine representations.

This file is needed because virtual function definitions in
header files cause massive code bloat-- hence the PUP library
virtual functions are defined here.

*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "charm++.h"
#include "pup.h"

PUP::er::~er() {}

void PUP::er::comment(const char *message)
  { /* ignored by default */ }

void PUP::er::synchronize(unsigned int m)
  { /* ignored by default */ }

/*define CK_CHECK_PUP to get type and bounds checking during Pack and unpack.
This checking substantially slows down PUPing, and increases the space
required by packed objects. It can save hours of debugging, however.
*/
#ifdef CK_CHECK_PUP
static int bannerDisplayed=0;
static void showBanner(void) {
	bannerDisplayed=1;
	fprintf(stderr,"CK_CHECK_PUP pup routine checking enabled\n");
	CmiPrintf("CK_CHECK_PUP pup routine checking enabled\n");
}

class pupCheckRec {
	unsigned char magic[4];//Cannot use "int" because of alignment
	unsigned char type;
	unsigned char length[3];
	enum {pupMagic=0xf36c5a21,typeMask=0x75};
	int getMagic(void) const {return (magic[3]<<24)+(magic[2]<<16)+(magic[1]<<8)+magic[0];}
	void setMagic(int v) {for (int i=0;i<4;i++) magic[i]=(v>>(8*i));}
	PUP::dataType getType(void) const {return (PUP::dataType)(type^typeMask);}
	void setType(PUP::dataType v) {type=v^typeMask;}
	int getLength(void) const {return (length[2]<<16)+(length[1]<<8)+length[0];}
	void setLength(int v) {for (int i=0;i<3;i++) length[i]=(v>>(8*i));}
	
	/*Compare the packed value (from us) and the unpacked value
	  (from the user).
	 */
	void compare(const char *kind,const char *why,int packed,int unpacked) const
	{
		if (packed==unpacked) return;
		//If we get here, there is an error in the user's pack/unpack routine
		fprintf(stderr,"CK_CHECK_PUP error!\nPacked %s (%d, or %08x) does "
			"not equal unpacked value (%d, or %08x)!\nThis means %s\n",
			kind,packed,packed,unpacked,unpacked,why);
		CmiPrintf("CK_CHECK_PUP error! Run with debugger for more info.\n");
		//Invoke the debugger
		abort();
	}
public:
	void write(PUP::dataType t,int n) {
		if (!bannerDisplayed) showBanner();
		setMagic(pupMagic);
		type=t^typeMask;
		setLength(n);
	}
	void check(PUP::dataType t,int n) const {
		compare("magic number",
			"you unpacked more than you packed, or the values were corrupted during transport",
			getMagic(),pupMagic);
		compare("data type",
			"the pack and unpack paths do not match up",
			getType(),t);
		compare("length",
			"you may have forgotten to pup the array length",
			getLength(),n);
	}
};
#endif


void PUP::sizer::bytes(void * /*p*/,int n,size_t itemSize,dataType /*t*/)
{
#ifdef CK_CHECK_PUP
	nBytes+=sizeof(pupCheckRec);
#endif
	nBytes+=n*itemSize;
}

/*Memory PUP::er's*/
void PUP::toMem::bytes(void *p,int n,size_t itemSize,dataType t)
{
#ifdef CK_CHECK_PUP
	((pupCheckRec *)buf)->write(t,n);
	buf+=sizeof(pupCheckRec);
#endif
	n*=itemSize;
	memcpy((void *)buf,p,n); 
	buf+=n;
}
void PUP::fromMem::bytes(void *p,int n,size_t itemSize,dataType t)
{
#ifdef CK_CHECK_PUP
	((pupCheckRec *)buf)->check(t,n);
	buf+=sizeof(pupCheckRec);
#endif
	n*=itemSize; 
	memcpy(p,(const void *)buf,n); 
	buf+=n;
}

/*Disk PUP::er's*/
PUP::disk::~disk() 
	{fclose(F);}
void PUP::toDisk::bytes(void *p,int n,size_t itemSize,dataType /*t*/)
	{fwrite(p,itemSize,n,F);}
void PUP::fromDisk::bytes(void *p,int n,size_t itemSize,dataType /*t*/)
	{fread(p,itemSize,n,F);}

/****************** Seek support *******************
For seeking:
Occasionally, one will need to pack and unpack items in different
orders (e.g., pack the user data, then the runtime support; but
unpack the runtime support first, then the user data).  These routines
support this, via the "PUP::seekBlock" class.

The abstraction is a (nestable) "seek block", which may contain
several "seek sections".  A typical use is:
//Code:
	PUP::seekBlock s(p,2);
	if (p.isUnpacking()) {s.seek(0); rt.pack(p); }
	s.seek(1); ud.pack(p); 
	if (p.isPacking()) {s.seek(0); rt.pack(p); }
	s.endBlock();
*/
PUP::seekBlock::seekBlock(PUP::er &Np,int nSections)
	:nSec(nSections),p(Np) 
{
	if (nSections<0 || nSections>maxSections)
		CmiAbort("Invalid # of sections passed to PUP::seekBlock!");
	p.impl_startSeek(*this);
	if (p.isPacking()) 
	{ //Must fabricate the section table
		secTabOff=p.impl_tell(*this);
		for (int i=0;i<=nSec;i++) secTab[i]=-1;
	}
	p(secTab,nSec+1);
	hasEnded=true;
}
PUP::seekBlock::~seekBlock() 
{
	if (!hasEnded)
		endBlock();
}

void PUP::seekBlock::seek(int toSection) 
{
#if 0
	if (toSection<0 || toSection>=nSec)
		CmiAbort("Invalid section # passed to PUP::seekBlock::seek!");
#endif
	if (p.isPacking()) //Build the section table
		secTab[toSection]=p.impl_tell(*this);
	else if (p.isUnpacking()) //Extract the section table
		p.impl_seek(*this,secTab[toSection]);
	/*else ignore the seeking*/
}

void PUP::seekBlock::endBlock(void) 
{
	if (p.isPacking()) {
		//Finish off and write out the section table
		secTab[nSec]=p.impl_tell(*this);
		p.impl_seek(*this,secTabOff);
		p(secTab,nSec+1); //Write out the section table
	}
	//Seek to the end of the seek block
	p.impl_seek(*this,secTab[nSec]);
	p.impl_endSeek(*this);
	hasEnded=true;
}

/** PUP::er seek implementation routines **/
/*Default seek implementations are empty, which is the 
appropriate behavior for, e.g., sizers.
*/
void PUP::er::impl_startSeek(PUP::seekBlock &s) /*Begin a seeking block*/
{}
int PUP::er::impl_tell(seekBlock &s) /*Give the current offset*/
{return 0;}
void PUP::er::impl_seek(seekBlock &s,int off) /*Seek to the given offset*/
{}
void PUP::er::impl_endSeek(seekBlock &s)/*End a seeking block*/
{}


/*Memory buffer seeking is trivial*/
void PUP::mem::impl_startSeek(seekBlock &s) /*Begin a seeking block*/
  {s.data.ptr=buf;}
int PUP::mem::impl_tell(seekBlock &s) /*Give the current offset*/
  {return buf-s.data.ptr;}
void PUP::mem::impl_seek(seekBlock &s,int off) /*Seek to the given offset*/
  {buf=s.data.ptr+off;}

/*Disk buffer seeking is also simple*/
void PUP::disk::impl_startSeek(seekBlock &s) /*Begin a seeking block*/
  {s.data.loff=ftell(F);}
int PUP::disk::impl_tell(seekBlock &s) /*Give the current offset*/
  {return (int)(ftell(F)-s.data.loff);}
void PUP::disk::impl_seek(seekBlock &s,int off) /*Seek to the given offset*/
  {fseek(F,s.data.loff+off,0);}

/*PUP::xlater just forwards seek calls to its unpacker.*/
void PUP::xlater::impl_startSeek(seekBlock &s) /*Begin a seeking block*/
  {myUnpacker.impl_startSeek(s);}
int PUP::xlater::impl_tell(seekBlock &s) /*Give the current offset*/
  {return myUnpacker.impl_tell(s);}
void PUP::xlater::impl_seek(seekBlock &s,int off) /*Seek to the given offset*/
  {myUnpacker.impl_seek(s,off);}
void PUP::xlater::impl_endSeek(seekBlock &s) /*Finish a seeking block*/
  {myUnpacker.impl_endSeek(s);}
  

/****************** Text Pup ******************/
char *PUP::textUtil::beginLine(void) {
  //Indent level tabs over:
  for (int i=0;i<level;i++) cur[i]='\t';
  cur[level]=0;
  return cur+level;
}
void PUP::textUtil::endLine(void) {
  cur=advance(cur);
}
void PUP::textUtil::beginEnv(const char *type,int n)
{
  char *o=beginLine();
  sprintf(o,"begin "); o+=strlen(o);
  sprintf(o,type,n); o+=strlen(o);
  sprintf(o," {\n");
  endLine();
  level++;
}
void PUP::textUtil::endEnv(const char *type)
{
  level--;
  sprintf(beginLine(),"} end %s;\n",type);
  endLine();
}
PUP::textUtil::textUtil(unsigned int inType,char *buf)
  :er(inType)
{
  cur=buf;
  level=0;
}

void PUP::textUtil::comment(const char *message)
{
  sprintf(beginLine(),"//%s\n",message); endLine();
}

void PUP::textUtil::synchronize(unsigned int m)
{
  char *o=beginLine();
  sprintf(o,"sync=");o+=strlen(o);
  const char *consonants="bcdfgjklmprstvxz";
  const char *vowels="aeou";
  for (int firstBit=0;firstBit<32;firstBit+=6) {
	sprintf(o,"%c%c%c", consonants[0xf&(m>>firstBit)],
		vowels[0x3&(m>>(firstBit+4))], 
		(firstBit==30)?';':'-');
	o+=strlen(o);
  }
  sprintf(o,"\n"); endLine();
  
}

void PUP::textUtil::bytes(void *p,int n,size_t itemSize,dataType t) {
  if (t==Tchar) 
  { /*Character data is written out directly (rather than numerically)*/
    char *o=beginLine();
    sprintf(o,"string=");o+=strlen(o);
    *o++='\"'; /*Leading quote*/
    /*Copy each character, possibly escaped*/
    const char *c=(const char *)p;
    for (int i=0;i<n;i++) {
      if (c[i]=='\n') {
	sprintf(o,"\\n");o+=strlen(o);
      } else if (iscntrl(c[i])) {
	sprintf(o,"\\x%02X",(unsigned char)c[i]);o+=strlen(o);
      } else if (c[i]=='\\' || c[i]=='\"') {
	sprintf(o,"\\%c",c[i]);o+=strlen(o);
      } else
	*o++=c[i];
    }
    /*Add trailing quote and newline*/
    sprintf(o,"\";\n");o+=strlen(o);
    endLine();
  } else if (t==Tbyte)
  { /*Byte data is written out in hex (rather than decimal) */
    beginEnv("byte %d",n);
    const unsigned char *c=(const unsigned char *)p;
    char *o=beginLine();
    for (int i=0;i<n;i++) {
      sprintf(o,"%02X ",c[i]);o+=strlen(o);
      if (i%25==24 && (i+1!=n)) 
      { /* This line is too long-- wrap it */
	sprintf(o,"\n"); o+=strlen(o);
	endLine(); o=beginLine();
      }
    }
    sprintf(o,"\n");
    endLine();
    endEnv("byte");
  }
  else
  { /*Ordinary number-- write out in decimal */
    if (n!=1) beginEnv("array %d",n);
    for (int i=0;i<n;i++) {
      char *o=beginLine();
      switch(t) {
      case Tshort: sprintf(o,"short=%d;\n",((short *)p)[i]); break;
      case Tushort: sprintf(o,"ushort=%u;\n",((unsigned short *)p)[i]); break;
      case Tint: sprintf(o,"int=%d;\n",((int *)p)[i]); break;
      case Tuint: sprintf(o,"uint=%u;\n",((unsigned int *)p)[i]); break;
      case Tlong: sprintf(o,"long=%ld;\n",((long *)p)[i]); break;
      case Tulong: sprintf(o,"ulong=%lu;\n",((unsigned long *)p)[i]); break;
      case Tfloat: sprintf(o,"float=%.7g;\n",((float *)p)[i]); break;
      case Tdouble: sprintf(o,"double=%.15g;\n",((double *)p)[i]); break;
      case Tbool: sprintf(o,"bool=%s;\n",((bool *)p)[i]?"true":"false"); break;
      default:
	CmiAbort("Unrecognized numeric type passed to PUP::textUtil::bytes!\n");
      }
      endLine();
    }
    if (n!=1) endEnv("array");
  }
}
void PUP::textUtil::object(able** a) {
  beginEnv("object");
  er::object(a);
  endEnv("object");
}


//Text sizer
char *PUP::sizerText::advance(char *cur) {
  charCount+=strlen(cur);
  return line;
}

PUP::sizerText::sizerText(void)
  :textUtil(IS_SIZING,line),charCount(0) { }

//Text packer
char *PUP::toText::advance(char *cur) {
  charCount+=strlen(cur);
  return buf+charCount;
}

PUP::toText::toText(char *outBuf)
  :textUtil(IS_PACKING,outBuf),buf(outBuf),charCount(0) { }


/*For standalone compilation of "pup" routines*/
#ifndef _CHARMPP_H_
void PUP::er::object(PUP::able **a) {
	CmiAbort("PUP::able not supported in standalone version!\n");
}
#endif





