/* 
Starts the application

Orion Sky Lawlor, olawlor@acm.org, 11/28/2001
*/
#include <string.h>
#include <stdlib.h>
#include "mytk.h"
#include "main.h"
#include "vtkTclUtil.h"
#include "ccs-client.h"
#include "sockRoutines.h"


/************* Initial CCS connection *****************/
CcsServer ccsServer;

extern "C" void tcl_skt_idleFn(void) {
	Tcl_DoOneEvent(0);
}
extern "C" int tcl_skt_abortFn(int errCode,const char *msg) {
	Tcl_VarEval(interp,"fatalError \"Communication socket error: ",msg,"\"",NULL);
	return 0;
}
void bad(const char *why) {
	//fprintf(stderr,"Fatal error: %s\n",why);
	if (interp) Tcl_VarEval(interp,"fatalError {",why,"}",NULL);
	fprintf(stderr,"Fatal error: %s\n",why);
	exit(1);
}
void status(const char *what) {
	//printf("Now: %s\n",what);
}
static void chk(int ret) {
	if (ret==TCL_OK) return;
	Tcl_Eval(interp,"fatalTcl");
	exit(1);
}

const char *ccsConnectStr(char *host)
{
	char *sep=strchr(host,':');
	if (sep==NULL) return "You must separate the host and port with a colon";
	*sep++=0; //divide host name from port
	int port;
	if (1!=sscanf(sep,"%d",&port)) return "Cannot parse port number";
	
//	printf("CCS connecting to '%s' port %d\n",host,port);
	CcsConnect(&ccsServer,host,port,NULL);
	
	return NULL;
}

static NetFEM_update *last_update;
const char *ccsGetStepAll(void) {
	char str[200];
	status("Getting each step");
	int c;
	chk(Tcl_Eval(interp,"setStatus {Requesting current step...}"));
	//Request the latest version from each chunk (request them all in parallel)
	int minTS=1000000000,maxTS=-1000000000;
	for (c=0;c<chunks::n;c++) {
		CcsServer &srv=ccsServer; 
		CcsSendRequest(&srv,"NetFEM_current",c,0,NULL);
		unsigned int size; char *buf;
		CcsRecvResponseMsg(&srv,&size,&buf,60);
		
		sprintf(str,"setStatus {Unpacking current step (%d bytes)...}",size);
		chk(Tcl_Eval(interp,str));
		NetFEM_update *u=new NetFEM_update(0,0);
		unpack(*u,buf,size);
		free(buf);

		chunks::get(c).setUpdate(u);
		last_update=u;
		if (u->getTimestep()<minTS) minTS=u->getTimestep();
		if (u->getTimestep()>maxTS) maxTS=u->getTimestep();
 
	}
	
	
	status("Updating timestep display");
	if (minTS==maxTS)
	  sprintf(str,"Timestep %d (%dD data)",minTS,last_update->getDim());
	else
	  sprintf(str,"Timesteps %d-%d (%dD data)",minTS,maxTS,last_update->getDim());
	chk(Tcl_VarEval(interp,"  .main.f.ts.l configure -text {",str,"}",NULL));
	chk(Tcl_Eval(interp,"setStatus {Ready}"));
	status("Done getting steps");
	return NULL;
}

/************* Vtk/Gui interface ***************/
static outputFilter *patchworkOF=new nodeOutputFilter(new patchworkFieldFilter);
static geomFilter *cloudGF=new cloudGeomFilter;
static geomFilter *triGF=NULL;
static dispFilter *noDF=new noDispFilter;

#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkScalarBarActor.h"
#include "vtkLookupTable.h"
#include "vtkRenderWindowInteractor.h"

static vtkRenderWindow *renWin=NULL;
static vtkScalarBarActor *scl=NULL;

const char *guiRerender(void) 
{ //Update the displayed geometry
	int c;
	outputFilter *o=outputFilter::get();
	o->beginRendering();
	for (c=0;c<chunks::n;c++)
		chunks::get(c).addGeometry(o);
	for (c=0;c<chunks::n;c++)
		chunks::get(c).updateRange(o);
	status("Created geometry");
	renWin->Render();
	status("Rendered geometry");
	return NULL;
}

void guiInit(void)
{
	vtkRenderer *ren1=vtkRenderer::New();
	renWin=vtkRenderWindow::New();
	    renWin->AddRenderer(ren1);
	    renWin->SetSize(500,500);

	vtkScalarsToColors *lut=NULL;
	for (int c=0;c<chunks::n;c++) {
		vtkMapper *map=chunks::get(c).getMap();
		lut=map->GetLookupTable();
		vtkActor *a=vtkActor::New();
		a->SetMapper(map);
		ren1->AddActor(a);
	}

	vtkRenderWindowInteractor *iren=vtkRenderWindowInteractor::New();
	    iren->SetRenderWindow(renWin);
	    iren->Initialize();
	
	scl=vtkScalarBarActor::New();
		scl->SetLookupTable(lut);
		scl->SetHeight(0.95);
		scl->SetWidth(0.15);
		scl->SetPosition(15,15);
		scl->SetTitle("Chunk Number");
	ren1->AddActor(scl);
	status("Created actors");
	guiRerender();
}

class filterBundle {
	outputFilter *of;
	geomFilter *gf;
	char title[200];
public:
	filterBundle(outputFilter *of_,geomFilter *gf_,const char *title_) 
		:of(of_), gf(gf_) {strcpy(title,title_);}
	void set(void) {
		scl->SetTitle(title);
		outputFilter::set(of);
		if (gf->getType()!=geomFilter::get()->getType())
			geomFilter::set(gf);
	}
};

const char *guiSetOF(void *ptr) {
	outputFilter::set((outputFilter *)ptr);
	return guiRerender();
}
const char *guiSetGF(void *ptr) {
	geomFilter::set((geomFilter *)ptr);
	return guiRerender();
}

const char *guiSetEF(void *ptr)
{
	((filterBundle *)ptr)->set();
	return guiRerender();
}

const char *guiSetDF(void *ptr) {
	dispFilter::set((dispFilter *)ptr);
	return guiRerender();
}

/************* Main window widgets ***************/
typedef enum {sLeft=0,sTop,sRight,sBottom} lSide;
static const char *lSide2str[]={"left","top","right","bottom"};
static void addLabel(const char *w,const char *path,const char *title,lSide side=sTop) {
	chk(Tcl_VarEval(interp,
	"  label ",w,".",path," -text {",title,"}\n",
	"  pack ",w,".",path," -side ",lSide2str[side]," -anchor nw\n",
	NULL));
}
static void addSpacer(const char *w) {
	addLabel(w,"spc"," ",sLeft);
}
static void addFrame(const char *w) {
	chk(Tcl_VarEval(interp,
	"  frame ",w,"\n",
	"  pack ",w," -side top -anchor nw -fill x -expand yes\n",
	NULL));
}

class bar {
	char w[200];
	int nButtons;
public:
	bar(const char *gw,const char *path,int indent,const char *name)
	{
		if (path==0 || strlen(path)==0)
			sprintf(w,"%s",gw);
		else
			sprintf(w,"%s.%s",gw,path);
		addFrame(w);
		for (int i=0;i<indent;i++) addSpacer(w);
		addLabel(w,"l",name,sLeft);
		nButtons=0;
	}
	void button(const char *butName,const char *cmd,const void *cmdParam,lSide s=sLeft)
	{
		char str[200],bp[200];
		sprintf(str,"%s %p",cmd,cmdParam);
		sprintf(bp,"%s.b%d",w,nButtons++);
		chk(Tcl_VarEval(interp,
		"c2tcl_eval {\n",
		"  button ",bp," -text {",butName,"} -command {c2tcl_eval {",str,"}}\n",
		"  pack ",bp," -side ",lSide2str[s],"\n",
		"}\n",
		NULL));	
	}
};

void buttonBarWidget(
	//Widget location
	const char *gw,const char *path,
	//Widget indent and label
	int indent,const char *name,
	//Button (name and command to execute when pressed)
	const char *butName=NULL,const char *cmd=NULL,const void *cmdParam=NULL)
{
	bar b(gw,path,indent,name);
	if (butName!=0) b.button(butName,cmd,cmdParam);
}

static const char *axisNames[]={"X","Y","Z"};
static const char *numNames[]={"1","2","3","4","5","6","7","8","9","10"};

//Write a human-readable version of the name of this field into str. 
// Return a table of the names of its components
const char **fieldName(const NetFEM_doubleField *f,char *str)
{
	const char **subNames=numNames;
	if (f->getSpatial()) {
		sprintf(str,"Vector ");
		subNames=axisNames;
	} else if (f->getStride()>1)
		sprintf(str,"%d-Scalar ",f->getStride());
	else
		sprintf(str,"");
	sprintf(&str[strlen(str)],f->getName());
	return subNames;
}

void nodeFieldWidgets(const char *w,const NetFEM_doubleField *f,geomFilter *gf,int fNo) {
	char str[200];
	const char **subNames=fieldName(f,str);
	bar b(w,"",1,str);
	if (f->getStride()==1)
		b.button("Show","guiSetEF",new filterBundle(new nodeOutputFilter(
			new scalarFieldFilter(new nodeFieldLoc(fNo,0))),gf,f->getName()),sRight);
	else {
		if (f->getSpatial()) {
			b.button("Disp","guiSetDF",new nodeDispFilter(fNo),sLeft);
			sprintf(str,"Mag %s",f->getName());
			b.button("Mag","guiSetEF",new filterBundle(new nodeOutputFilter(
				new magFieldFilter(new nodeFieldLoc(fNo,0))),gf,str),sRight);
		}
		for (int i=f->getStride()-1;i>=0;i--) {
			sprintf(str,"%s %s",subNames[i],f->getName());
			b.button(subNames[i],"guiSetEF",new filterBundle(new nodeOutputFilter(
				new scalarFieldFilter(new nodeFieldLoc(fNo,i))),gf,str),sRight);
		}
	}
}
void nodeWidgets(const char *w,const NetFEM_nodes *n) {
	char str[200],lw[200];
	sprintf(str,"%d Nodes:",n->getItems());
	buttonBarWidget(w,"spacer",0," ");
	buttonBarWidget(w,"n",0,str, "Point Nodes","guiSetGF",cloudGF);
	//Skip first field (it's just the coordinates)
	int i;
	for (i=1;i<n->getFields();i++)
	{
		sprintf(lw,"%s.f%d",w,i);
		nodeFieldWidgets(lw,&n->getField(i),triGF,i);
	}
}
void elemFieldWidgets(const char *w,const NetFEM_doubleField *f,geomFilter *gf,int eType,int fNo) {
	char str[200];
	const char **subNames=fieldName(f,str);
	bar b(w,"",1,str);
	if (f->getStride()==1)
		b.button("Show","guiSetEF",new filterBundle(new elemOutputFilter(
			new scalarFieldFilter(new elemFieldLoc(eType,fNo,0)),eType),gf,f->getName()),sRight);
	else {
		if (f->getSpatial()) {
			sprintf(str,"Mag %s",f->getName());
			b.button("Mag","guiSetEF",new filterBundle(new elemOutputFilter(
				new magFieldFilter(new elemFieldLoc(eType,fNo,0)),eType),gf,str),sRight);
		}
		for (int i=f->getStride()-1;i>=0;i--) {
			sprintf(str,"%s %s",subNames[i],f->getName());
			b.button(subNames[i],"guiSetEF",new filterBundle(new elemOutputFilter(
				new scalarFieldFilter(new elemFieldLoc(eType,fNo,i)),eType),gf,str),sRight);
		}
	}
}

void elemWidgets(const char *w,const NetFEM_elems *e,int eType) {
	char str[200],lw[200];
	sprintf(str,"%d %s:",e->getItems(),e->getName());
	buttonBarWidget(w,"spacer",0," ");
	bar b(w,"n",0,str);
	if (e->getNodesPer()==3) 
		b.button("Fill","guiSetGF",new triGeomFilter(eType));
	b.button("Outline","guiSetGF",new outlineGeomFilter(eType));
	int i;
	geomFilter *gf=new elemGeomFilter(eType);
	for (i=0;i<e->getFields();i++)
	{
		sprintf(lw,"%s.f%d",w,i);
		elemFieldWidgets(lw,&e->getField(i),gf,eType,i);
	}
}

/************ Startup Routine ************/
const char *ccsWidgets(char *w) {
	char str[200];
	
	outputFilter::set(patchworkOF);

//Get cursory information about computation:
	sprintf(str,"Running on %d processors",CcsNumPes(&ccsServer));
	addLabel(w,"npe",str);
	
	//FIXME: may have multiple chunks per PE
	int nChunks=CcsNumPes(&ccsServer);
	sprintf(str,"global nChunks\n  set nChunks %d\n",nChunks);
	tcl_eval(str);
	chunks::init(nChunks);

//Begin to create the GUI	
	buttonBarWidget(w,"ts",0,"-Placeholder-", "Update","v_step",NULL);
	
//Load each chunk
	ccsGetStepAll();
	NetFEM_update *u=last_update;
	
//Figure out which element types are displayable and set the initial filters
	int ne;
	triGF=NULL;
	for (ne=0;ne<u->getElems();ne++)
		if (u->getElem(ne).getNodesPer()==3)
		{
			triGF=new triGeomFilter(ne);
		}
	if (triGF==NULL)
		triGF=cloudGF;
	geomFilter::set(triGF);
	dispFilter::set(noDF);
	
	sprintf(str,"%d Chunks",nChunks);	
	bar b(w,"chk",0,str);
	b.button("Show","guiSetEF",new filterBundle(patchworkOF,triGF,"Chunk Number"),sRight);

//Set up the GUI for the nodes
	char lw[200];
	sprintf(lw,"%s.n",w);
	addFrame(lw);
	nodeWidgets(lw,&u->getNodes());

//Set up the GUI for the elements
	for (ne=0;ne<u->getElems();ne++) {
		sprintf(lw,"%s.e%d",w,ne);
		addFrame(lw);
		elemWidgets(lw,&u->getElem(ne),ne);
	}

//Start VTK
	guiInit();

	status("Done with ccsWidgets");
	
	return NULL;
}

/************* Base Tcl calls ************/
void tcl_eval(const char *string) {
	chk(Tcl_Eval(interp,(char *)string));
}

static int str2tcl(const char *err) {
	if (err==NULL) return TCL_OK;
	Tcl_SetResult(interp, (char *)err, TCL_STATIC);
	return TCL_ERROR;
}

typedef const char *(*stringFn)(char *arg);
static int callStringFn(ClientData cd,
	Tcl_Interp *interp, int objc, struct Tcl_Obj * CONST objv[])
{
	stringFn f=(stringFn)cd;
	if (objc!=2) return str2tcl("this routine takes exactly one string as argument");
	else {
		char *s=Tcl_GetStringFromObj(objv[1],NULL);
		return str2tcl( (f)(s) );
	}
}

typedef const char *(*voidPtrFn)(void *);
static int callPtrFn(ClientData cd,
	Tcl_Interp *interp, int objc, struct Tcl_Obj * CONST objv[])
{
	voidPtrFn f=(voidPtrFn)cd;
	if (objc!=2) return str2tcl("this routine takes a pointer as an argument");
	else {
		char *s=Tcl_GetStringFromObj(objv[1],NULL);
		void *p;
		if (1!=sscanf(s,"%p",&p)) return str2tcl("Cannot parse argument as a pointer");
		return str2tcl( (f)(p) );
	}
}

typedef const char *(*voidFn)(void);
static int callVoidFn(ClientData cd,
	Tcl_Interp *interp, int objc, struct Tcl_Obj * CONST objv[])
{
	voidFn f=(voidFn)cd;
	if (objc!=1) return str2tcl("this routine takes no arguments");
	else {
		return str2tcl( (f)() );
	}
}

static int modalCount=0;
static int Cmd_modalBegin(ClientData cd,
	Tcl_Interp *interp, int objc, struct Tcl_Obj * CONST objv[])
{
	int modalStart=modalCount;
	modalCount++;
	while (modalCount!=modalStart)
		Tcl_DoOneEvent(0);
	return TCL_OK;
}
static int Cmd_modalEnd(ClientData cd,
	Tcl_Interp *interp, int objc, struct Tcl_Obj * CONST objv[])
{
	modalCount--;
	return TCL_OK;
}


/*Register commands before reading TCL routines*/
void tkMyAppInit(Tcl_Interp *interp)
{
	//Application:
	Tcl_CreateObjCommand(interp,"ccsConnect",callStringFn,(ClientData)ccsConnectStr,NULL);
	Tcl_CreateObjCommand(interp,"ccsWidgets",callStringFn,(ClientData)ccsWidgets,NULL);
	Tcl_CreateObjCommand(interp,"ccsGetStepAll",callVoidFn,(ClientData)ccsGetStepAll,NULL);
	Tcl_CreateObjCommand(interp,"guiRerender",callVoidFn,(ClientData)guiRerender,NULL);
	Tcl_CreateObjCommand(interp,"guiSetOF",callPtrFn,(ClientData)guiSetOF,NULL);
	Tcl_CreateObjCommand(interp,"guiSetGF",callPtrFn,(ClientData)guiSetGF,NULL);
	Tcl_CreateObjCommand(interp,"guiSetEF",callPtrFn,(ClientData)guiSetEF,NULL);
	Tcl_CreateObjCommand(interp,"guiSetDF",callPtrFn,(ClientData)guiSetDF,NULL);
	
	//Utility:
	Tcl_CreateObjCommand(interp,"modalBegin",Cmd_modalBegin,0,NULL);
	Tcl_CreateObjCommand(interp,"modalEnd",Cmd_modalEnd,0,NULL);
}

/*Start the application proper*/
void tkMyAppStart(Tcl_Interp *interp,char **argv)
{
	skt_set_abort(tcl_skt_abortFn);
	skt_set_idle(tcl_skt_idleFn);
	
	argv++; //skip over app. name
	if (*argv!=NULL) {//user passed parameter on command line
		if (Tcl_VarEval(interp,"c2tcl_eval {startAppWith {",
			*argv,"} }",NULL)==TCL_ERROR) bad("error during startup");
	} else {
		if (Tcl_Eval(interp,"c2tcl_eval {startApp}")==TCL_ERROR) bad("error during arg startup");
	}
	printf("App started\n");
}

