/*
A funky, funky I/O scheme: "pup", or Pack/UnPack.

The idea is that for a complicated object, 
reading and writing are *symmetric*--it's gotta
be the same data going out and coming back in.
So we might as well write just *one* routine
per object that does both reading and writing.
We call that routine "pup".

Orion Sky Lawlor, olawlor@acm.org, 2006/03/27 (Public Domain)
*/
#include <iostream>
#include <fstream>
#include <string>
#include <vector>

/********** tiny little example pup library (modify as needed!) ******/
/*
PUP: Pack / UnPack
	A pup_er accepts your object's data fields.
It's allowed to read *or* write to your fields.
*/
class pup_er {
public:
	virtual void bytes(void *data,int nbytes) =0;
};

// pup routines for builtin types.
//   For portability, these could use network byte order.
void pup(pup_er &p,int &c) {
	p.bytes((char *)&c,sizeof(int));
}
void pup(pup_er &p,double &c) {
	p.bytes((char *)&c,sizeof(double));
}

// pup routines for STL types.
void pup(pup_er &p,std::string &c) {
	int len=c.size();
	pup(p,len);
	c.resize(len);
	p.bytes(&c[0],len*sizeof(char));
}
template <class T>
void pup(pup_er &p,std::vector<T> &v) {
	int len=v.size();
	pup(p,len);
	v.resize(len);
	for (int i=0;i<len;i++)
		pup(p,v[i]);
	
}

/* A pup_er that pups objects into a disk file (writing pup_er) */
class pup_to_disk : public pup_er {
public:
	std::ostream &s;
	pup_to_disk(std::ostream &s_) :s(s_) {}
	void bytes(void *data,int nbytes){
		s.write((char *)data,nbytes);
	}
};
/* A pup_er that pups objects out of a disk file (reading pup_er) */
class pup_from_disk : public pup_er {
public:
	std::istream &s;
	pup_from_disk(std::istream &s_) :s(s_) {}
	void bytes(void *data,int nbytes){
		s.read((char *)data,nbytes);
	}
};

/*************** end of pup library, beginning example code *********/

class ItemQuantity {
public:
	std::string item;
	int quantity;
};
void pup(pup_er &p,ItemQuantity &q) {
	pup(p,q.item);
	pup(p,q.quantity);
}

class Order {
public:
	std::vector<ItemQuantity> stuff;
};
void pup(pup_er &p,Order &o) {
	pup(p,o.stuff);
}

class Customer {
public:
	std::string name;
	double balance; /* money customer owes us */
	std::string mailing;
	
	// Type of customer: ordinary or cool (over $1m business/year)
	enum {ordinary_t=0,cool_t=1};
	int type;
	
	// Pending customer orders
	std::vector<Order> pending;
};
void pup(pup_er &p,Customer &c) {
	pup(p,c.name);
	pup(p,c.balance);
	pup(p,c.mailing);
	pup(p,c.type);
	pup(p,c.pending);
}


int main() {
	int i,n=10000;
	
	// Prepare array
	Customer *arr=new Customer[n];
	for (i=0;i<n;i++) {
		arr[i].name=(i%2)?"bob":"bill";
		for (int j=0;j<(i%10);j++)
			arr[i].name+='k'+j;
		arr[i].balance=(i%10);
		arr[i].mailing=(i%2)?"1234 bob street":"4321 bill street";
		arr[i].type=(i%2)?Customer::cool_t:Customer::ordinary_t;
		ItemQuantity iq;
		iq.item="thingy";
		iq.quantity=44832;
		Order order;
		order.stuff.push_back(iq);
		arr[i].pending.push_back(order);
	}
	
	// Write customers to file
	std::ofstream os("out_bin.dat");
	pup_to_disk p(os);
	for (i=0;i<n;i++) pup(p,arr[i]);
	os.close();
	
	// Read a few customers from the file
	std::ifstream is("out_bin.dat");
	pup_from_disk ip(is);
	Customer c1; pup(ip,c1);
	Customer c2; pup(ip,c2);
	std::cout<<" C1="<<c1.name<<"\n";
	std::cout<<" C2="<<c2.name<<"\n";
	
	
	delete[] arr;
	return 0;
}
