#include <string.h>
#include <stream.h>
#include <strstream.h>

extern "C" { void exit(int); }

#define GPR_DEFAULT_FILL GPR_SOLID

#include "xmCi2.h"

struct copyInfo;

class Document : public CiObject
{
	char* contents;
	CiObject* prevParent;
	gprPoint prevLoc;

	void edit(void*);
	void storeLoc(void*);

	void print(char**);
	void copy(copyInfo*);
	void moveBack(void*);
	void drop(bool*);

	char* className()	{ return("Document"); }
public:
	Document(char* = "noname", int = -1, int = -1);
	~Document();

	Document* setContents(char* txt)	{ delete contents; contents = strcpy(new char[strlen(txt) + 1], txt); return(this); }
};

class Printer : public CiObject
{
friend class Copier;
	int copies;

	void printIt(ciClientData*);
	void setup(void*);

	void drop(bool*);

	char* className()	{ return("Printer"); }
public:
	Printer(char* = "Printer", int = -1, int = -1);
};

class Copier : public Printer
{
	void copyIt(ciClientData*);

	char* className()	{ return("Copier"); }
public:
	Copier(char* = "Copier", int = -1, int = -1);
};

/*
class Filer : public CiObject
{
};
*/

class TrashCan : public CiObject
{
	void dropIt(ciClientData*);
public:
	TrashCan(char* = "Trash Can", int x = -1, int y = -1);
};

// misc declarations //////////////////////////////////////////////////////////////

struct copyInfo
{
	int count;
	CiObject** copies;

	copyInfo(int);
	~copyInfo()		{ delete copies; }
};

copyInfo::copyInfo(int n)
{
	copies = new CiObject*[(count = n)];
	for(int i = 0; i < n; i++)
		copies[i] = NULL;
}

// implementation /////////////////////////////////////////////////////////////////


Document::Document(char* name, int x, int y) : CiObject(new Rectangle(x, y, 50, 60), name)
{
	contents = new char[500];
	prevParent = NULL;
	prevLoc.x = x;
	prevLoc.y = y;

	strcpy(contents, "This is the contents of the document named ");
	strcat(contents, name);

	setText(name);
	setCallback(Move, this, CB(Document::storeLoc), TRUE, TRUE);
	setCallback(Assign, this, CB(Document::storeLoc), TRUE, TRUE);
	setCallback(Activate, this, CB(Document::edit));
	allowAssigningTo("Printer*", "Copier*", "Trash*", "WORLD", NULL);
	allowAssignmentOf("DocumentClass", NULL);
	registerMethod("print", CB(Document::print), "char**");
	registerMethod("copy", CB(Document::copy), "copyInfo*");
	registerMethod("moveBack", CB(Document::moveBack));
	registerMethod("drop", CB(Document::drop), "bool*");
};

Document::~Document()
{
	cerr << "document destroyed...\n";
	delete contents;
}

void Document::edit(void*)
{
	char* reply;

	if(reply = (new XmPrompter("Enter document text: ", "Edit Document", contents, XmSmboxQuestion, getWorld()))->prompt())
		strcpy(contents, reply);
}

void Document::storeLoc(void*)
{
	prevParent = getParent();
	prevLoc = origin();
}

void Document::print(char** ptr)
{
	*ptr = contents;
}

Document* doc;

void Document::copy(copyInfo* inf)
{
	for(int i = 0; i < inf->count; i++)
	{	cerr << this << " " << getName() << " copy...\n";
		inf->copies[i] = (new Document(getName()))->setContents(contents);
	}
}

void Document::moveBack(void*)
{
// cerr << "back to " << (prevParent ? prevParent->getName() : "WORLD") << " at " <<
//	prevLoc.x << "@" << prevLoc.y << "\n";
	assign(prevParent, prevLoc.x, prevLoc.y);
}

void Document::drop(bool* qry)
{
	ostrstream aMsg;

	aMsg << "Are you sure to delete\nthe document " << getName() << "?";
	*qry = (new XmMsgBox(aMsg.str() , "Delete", XmSmboxQuestion, getWorld()))->showMsg();
}


Printer::Printer(char* name, int x, int y) : CiObject(new Rectangle(x, y, 100, 80), name)
{
	copies = 1;

	setText(name);
	allowAssigningTo("TrashCan", NULL);
	allowAssignmentOf("doc*" /* "DocumentClass" */, NULL);
	setCallback(ChangeContents, this, CB(Printer::printIt),	TRUE, FALSE, CB_CI_DATA);
	setCallback(Activate, this, CB(Printer::setup),	TRUE, FALSE, CB_CI_DATA);
	registerMethod("drop", CB(Printer::drop), "bool*");
};

extern "C" { unsigned int sleep(unsigned int); }

void Printer::printIt(ciClientData* cd)
{
	char* ptr = NULL;
	char** txt = &ptr;

	if(cd->operation == OP_ADD && cd->obj)
	{	getWorld()->refresh();
		cd->obj->msg("print", "char**", txt);
		for(int i = 0; i < copies; i++)
		{	cerr << "Printer: " << getName() << "\nprinting: " << cd->obj->getName();
			cerr << "(Copy No. " << i + 1 << ")\n\n";
			cerr << (*txt ? *txt : "Object not printable." ) << "\n\n";
		}
		sleep(2);
		cd->obj->msg("moveBack");
	}
}

void Printer::setup(void*)
{
	char* reply;

	if(reply = (new XmPrompter("Enter number of copies: ", "Configuration", "1", XmSmboxQuestion, getWorld()))->prompt())
	{	istrstream inp(reply);
		int newNo = 0;
		inp >> newNo;
		if(newNo < 1 || newNo > 10)
			newNo = 1;
		copies = newNo;
	}
}

void Printer::drop(bool* qry)
{
	if(*qry)
	{	ostrstream aMsg;
		aMsg << "Are you sure to drop\nthe tool " << getName() << "?";
		if((new XmMsgBox(aMsg.str() , "Delete", XmSmboxQuestion, getWorld()))->showMsg())
			delete this;
	}
	else
		delete this;
}

Copier::Copier(char* n, int x, int y) : Printer(n, x, y)
{
	setCallback(ChangeContents, this, CB(Copier::copyIt), TRUE, FALSE, CB_CI_DATA);
}

void Copier::copyIt(ciClientData* cd)
{
	copyInfo* cinf = new copyInfo(copies);

	if(cd->operation == OP_ADD && cd->obj)
	{	getWorld()->refresh();
		cd->obj->msg("copy", "copyInfo*", cinf);
		int xy = 10;
		for(int i = 0; i < copies; i++)
		{	CiObject* anObj = cinf->copies[i];
			if(anObj)
			{	sleep(1);
				addChild(anObj);
				anObj->move(xy, xy);
				getWorld()->refresh();
				xy += 5;
				continue;
			}
			cerr << "Copier: paper jam!\n\n";
			break;
		}
		sleep(2);
		cd->obj->msg("moveBack");
	}
	delete cinf;
}

TrashCan::TrashCan(char* name, int x, int y) : CiObject(new Rectangle(x, y, 80, 100), name)
{
	setText(name);
	allowAssigningTo("TrashCan", NULL);
	allowAssignmentOf("doc*", "Printer", NULL);
	setCallback(ChangeContents, this, CB(TrashCan::dropIt),	TRUE, FALSE, CB_CI_DATA);
};

void TrashCan::dropIt(ciClientData* cd)
{
	bool reallyDrop = TRUE;

	if(cd->obj)
	{	getWorld()->refresh();
		cd->obj->msg("drop", "bool*", &reallyDrop);
		if(reallyDrop)
			delete cd->obj;
		else
			cd->obj->msg("moveBack");
	}
}

class TestWindow : public XmDialogWindow
{
	XmCiWorld* world;

	bool initWindowSize(int&, int&, int& nw, int& nh)
		{ nw = 400; nh = 600; return(TRUE); } 
	void createCiWorld();
public:
	TestWindow();

	void myMenuItem(char*);
	void open(void*);
	void close(void*);

	void quit(void*);
};

void XmApp::initialize()
{
	new TestWindow;
};


TestWindow::TestWindow() : XmDialogWindow("Test")
{
	XmDropdownMenu* m = createDropdownMenu();

	m->addLabel("&File");
	m->addLabel("&Edit");
	m->addLabel("&Misc");
	m->setCurrentLabel("File");
	m->addItem(Entry("&Open", (XmObject* )NULL, (cbProc )&TestWindow::open));
	m->addItem(Entry("&Close", (XmObject* )NULL, (cbProc )&TestWindow::close));
	m->addSeparator();
	m->addItem(Entry("E&xit", (XmObject* )NULL, (cbProc )&TestWindow::quit));

	createCiWorld();
}

void TestWindow::open(void*)
{
	(new TestWindow)->realize();
}

void TestWindow::close(void*)
{
	destroyOnDelete();
	delete this;
}


void TestWindow::quit(void*)
{
	exit(0);
}


void TestWindow::createCiWorld()
{
	add(world = new XmCiWorld("World", 10, 10, 400, 400));

	world->addChild(new Document("doc1", 10, 10));
	world->addChild(new Document("doc2", 20, 20));
	world->addChild(new Document("doc3", 30, 30));
	world->addChild(new Printer("Printer ", 40, 40));
	world->addChild(new Copier("Copier ", 50, 50));
	world->addChild(new TrashCan("Trash ", 60, 60));
}
