/*
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 "converse.h"
#include "pup.h"
#include "ckhashtable.h"

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

/*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*/,const char *desc)
{
#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,const char *desc)
{
#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,const char *desc)
{
#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*/,const char *desc)
	{fwrite(p,itemSize,n,F);}
void PUP::fromDisk::bytes(void *p,int n,size_t itemSize,dataType /*t*/,const char *desc)
	{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=CmiFalse;
}
PUP::seekBlock::~seekBlock() 
{
	if (!hasEnded)
		endBlock();
}

void PUP::seekBlock::seek(int toSection) 
{
	if (toSection<0 || toSection>=nSec)
		CmiAbort("Invalid section # passed to PUP::seekBlock::seek!");
	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=CmiTrue;
}

/** 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);}
  

/**************** PUP::able support **********************
If a class C inherits from PUP::able, 
and you keep a new/delete pointer to C "C *cptr" somewhere,
you can call "p(cptr)" in your pup routine, and the object
will be saved/delete'd/new'd/restored properly with no 
additional effort, even if C has virtual methods or is actually
a subclass of C.  There is no space or time overhead for C 
objects other than the virtual function.

This is implemented by registering a constructor and PUP::ID
for each PUP::able class.  A packer can then write the PUP::ID
before the class; and unpacker can look up the constructor
from the PUP::ID.
 */

//For allocatable objects: new/delete object and call pup routine
void PUP::er::object(able** a,const char *desc)
{
	if (isUnpacking()) 
	{ //Find the object type; create the object
		PUP::able::ID id;//The object's id
		(*this)(id.hash,PUP::able::ID::len,desc);
		//Find the object's constructor and invoke it (calls new)
		*a=PUP::able::get_constructor(id) ();
	} else //Just write out the object type
		(*this)((unsigned char *)((*a)->get_PUP_ID().hash),
			PUP::able::ID::len,desc);
	(*a)->pup(*this);
	if (isDeleting())
	{
		delete *a;
		*a=0;
	}
}

//Empty destructor
PUP::able::~able() {}
void PUP::able::pup(PUP::er &p) {}


//Compute a good hash of the given string 
// (registration-time only-- allowed to be slow)
void PUP::able::ID::setName(const char *name)
{
	int i,o,n=strlen(name);
	int t[len]={0};
	for (o=0;o<n;o++)
		for (i=0;i<len;i++) {
			unsigned char c=name[o];
			int shift1=(((o+2)*(i+1)*5+4)%13);
			int shift2=(((o+2)*(i+1)*3+2)%11)+13;
			t[i]+=(c<<shift1)+(c<<shift2);
		}
	for (i=0;i<len;i++) 
		hash[i]=(unsigned char)(t[i]%20117 + t[i]%1217 + t[i]%157);
}

//Registration routines-- called at global initialization time
class PUP_regEntry {
public:
	PUP::able::ID id;
	const char *name;
	PUP::able::constructor_function ctor;
	PUP_regEntry(const char *Nname,
		const PUP::able::ID &Nid,PUP::able::constructor_function Nctor)
		:name(Nname),id(Nid),ctor(Nctor) {}
	PUP_regEntry(int zero) {
		name=NULL; //For marking "not found"
	}
};

typedef CkHashtableT<PUP::able::ID,PUP_regEntry> PUP_registry;

static PUP_registry *PUP_getRegistry(void) {
	static PUP_registry *reg=NULL;
	if (reg==NULL)
		reg=new PUP_registry();
	return reg;
}

PUP::able::ID PUP::able::register_constructor
	(const char *className,constructor_function fn)
{
	PUP::able::ID id(className);
	PUP_getRegistry()->put(id)=PUP_regEntry(className,id,fn);
	return id;
}
PUP::able::constructor_function PUP::able::get_constructor
	(const PUP::able::ID &id)
{
	const PUP_regEntry &cur=PUP_getRegistry()->get(id);
	if (cur.name!=NULL)
		return cur.ctor; 
	//Error! ID not in list-- unknown class
	CmiAbort("Unrecognzied PUP::able::ID passed to get_constructor!");
	return NULL;
}
