/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <iostream>
#include <Xm/Xm.h>
#include <Xm/List.h>
#include <Xm/RowColumn.h>
#include <Xm/Text.h>
#include <sys/types.h>
#include <signal.h>
#include <Drag.h>
#include "mars.h"

using namespace std;

extern "C" {
#include "xec.h"
#include "drop.h"
void debug_callback(Widget,XtPointer,XmAnyCallbackStruct*);
void kill_callback(Widget,XtPointer,XmAnyCallbackStruct*);
void info_callback(Widget,XtPointer,XmAnyCallbackStruct*);
void open_icon_callback(Widget,XtPointer,DragCallbackStruct*);
void create_request_top(Widget);
void create_top ( Display*, char*, int, char** );
void create_action_menu(Widget);
void exit_callback(Widget,XtPointer,XmAnyCallbackStruct*);
void module_callback(Widget,XtPointer,XmAnyCallbackStruct*);
}

extern Widget top;
extern Widget drag_service,drag_wait,drag_request;
extern Widget info_label,progress_list;
XtAppContext app_context;
Display *display;       /*  Display             */

extern Widget service_top, service_form, service_drag;
extern Widget request_top, request_form, request_text;
extern Widget action_menu;
extern Widget name_label;

static svc *s;
request *status  = NULL;

const char *_service  = strcache("service");
const char *_object   = strcache("object");
const char *_REPLY    = strcache("REPLY");
const char *_WAIT     = strcache("WAIT");
const char *_REGISTER = strcache("REGISTER");
const char *_SERVICE  = strcache("SERVICE");
const char *_EXIT     = strcache("EXIT");   
const char *_PROGRESS = strcache("PROGRESS");   
const char *_FOLLOWUP = strcache("FOLLOWUP");   
const char *_BUSY     = strcache("BUSY");   


class Info {
	static Info *head;
	Info *next;
	long    id;
protected:

	Icon    icon;
	char    *name;
	static Info *curr;

public:

	Info(long);
	virtual ~Info();

	virtual void Open();
	virtual void Kill()              = 0;
	virtual void Debug()             = 0;
	virtual void Update(request*)    {};
	virtual void Update(long)        {};

static Info* Find(long);
};

class Service : public Info{

	char    *host;
	char    *user;
	long    pid;
	long    ref;
	int     kill_level;

	void Kill();
	void Debug();
	void Open();
public:

	Service(long,request*);
	~Service();

};

class Request : public Info{
	request *r;
	long from;
	long to;
	void Kill();
	void Debug();
	void Update(request*);
	void Update(long t)     { to = t; }
	void Open();
public:
	Request(long,long,long,request*,int);
	~Request();
};

class Wait : public Info{
	request *r;
	long from;
	char *to;
	void Kill()  {           }
	void Debug() {           }
	void Open();
public:
	Wait(long,long,const char*,request*);
	~Wait();
};

Info *Info::head = NULL;
Info *Info::curr = NULL;

Info::Info(long ref) : id(ref)
{
	next        = head;
	head        = this;
	icon        = 0 ;
	name        = 0;
}

void reset_request_window()
{
	xec_ClearList(progress_list);
	XmTextSetString(request_text,"");
	xec_SetLabel(info_label,"-");
}

const char *nice_name(const char *name)
{
	request *s  = mars.setup;
	while(s)
	{
		if( s->name == _service )
		{
			const char *n = get_value(s,"name",0);
			if(n == name)
			{
				const char *p = get_value(s,"fullname",0);
				if(p) return p;
			}
		}
		s = s->next;
	}
	return name;
}

Service::Service(long ref,request *r) : Info(ref)
{
	
	name        = strcache(get_value(r,"NAME",0));

	// Look for  @
	int at  = 0;
	const char *p = name;
	while(*p) { if(*p == '@') at++; p++; }

	host        = strcache(get_value(r,"HOST",0));
	user        = strcache(get_value(r,"USER",0));
	pid         = atol(get_value(r,"PID",0));

    const char *refp = get_value(r,"REF",0);
    if( refp ) ref = atol( refp );
	else       ref = 0;

	name        = strcache(nice_name(name));
	if(!at) icon  = DragAddIcon(drag_service,"SERVICE",name,this,0,0);
	kill_level  = 0;
}

Request::Request(long ref,long f,long t,request *s,int sync) : 
	Info(ref),from(f),to(t)
{
	r       = clone_all_requests(s);
	const char *c = get_value(r,"_CLASS",0);
	const char *n = mbasename(get_value(r,"_NAME",0));

	if(!c || !*c) c = "REQUEST";
	if(!n || !*n) n = r->name;

	if(sync)
	{
		char buf[1024];
		sprintf(buf,"%s (synchrone)",n);
		name = strcache(buf);
	}
	else name = strcache(n);

	icon    = DragAddIcon(drag_request,c,name,this,0,0);
}

Request::~Request()
{
	free_all_requests(r);
}

void Request::Update(request* s)
{
	const char *p;
	int n = 0;
	while(p = get_value(s,"PROGRESS",n++))
	{
		add_value(r,"_MONITOR_PROGRESS","%s",p);
		if(curr == this)
			xec_AddListItem(progress_list,p);
	}
}

Wait::Wait(long ref,long f,const char *t,request *s) : 
	Info(ref),from(f),to(strcache(t))
{
	r       = clone_all_requests(s);
	const char *c = get_value(r,"_CLASS",0);
	const char *n = mbasename(get_value(r,"_NAME",0));

	char buf[1024];
	sprintf(buf,"%s waiting for %s",n?n:r->name,nice_name(to));
	n = buf;

	if(!c || !*c) c = "REQUEST";
	if(!n || !*n) n = r->name;
	name = strcache(n);
	icon    = DragAddIcon(drag_wait,c,n,this,0,0);
}

Wait::~Wait()
{
	strfree(to);
	free_all_requests(r);
}

Info *active = NULL;

Info::~Info()
{
	Info *s = head;
	Info *t = 0;

	while(s)
	{
		if(s == this)
		{
			if(t) t->next = s->next;
			else  head    = s->next;
			break;
		}
		t = s;
		s = s->next;
	}
	if(icon) DragDeleteIcon(DragIconToWidget(icon),icon);
	strfree(name);

	if(curr == this)
	{
		curr = 0;
		reset_request_window();
	}		

	if(active == this)
		active = 0;
}

Service::~Service()
{
	strfree(user);
	strfree(host);
}


Info* Info::Find(long r)
{
	Info *s = head;
	while(s)
	{
		if(s->id == r) return s;
		s = s->next;
	}
	return 0;
}

void Service::Debug()
{
	kill(pid,SIGUSR2);
}

void Service::Kill()
{
	int sig;
	switch(kill_level++)
	{
		case 0:  sig = 15; break;
		default: sig = 9;  break;
	}
	kill(pid,sig);
}

void Request::Debug()
{
	Info *p = Find(to);
	if(p) p->Debug();
}

void Request::Kill()
{
	Info *p = Find(to);
	if(p) p->Kill();
}


void Info::Open()
{
	curr = this;
	reset_request_window();
	XtManageChild(request_form);
	xec_SetLabel(info_label,name);
}


void Service::Open()
{
	Info::Open();
	char buf[1024];
	sprintf(buf,"Host: %s\nUser: %s\nPID : %d",
		host,user,pid);
		XmTextSetString(request_text,buf);
}

void Request::Open()
{
	Info::Open();
	const char *p;
	int n = 0;
	while(p = get_value(r,"_MONITOR_PROGRESS",n++))
			xec_AddListItem(progress_list,p);
	p = request2string(r);
	XmTextSetString(request_text,(char*)p);
}

void Wait::Open()
{
	Info::Open();
	char *p = request2string(r);
	XmTextSetString(request_text,p);
}


void monitor()
{
	request *t = empty_request("MONITOR");
	call_switchboard(s,t);
	free_all_requests(t);
}


void draw(Widget w,XRectangle *r,Pixmap pix,GC gc,XtPointer)
{
	XFillRectangles(XtDisplay(w),
		pix,
		gc,
		r,1);
}

void open_icon_callback(Widget,XtPointer,DragCallbackStruct *cb)
{
	/* DropObjectFromSource(w,cb->event,0,0,20,20,draw,NULL); */
	Info *s = (Info*)cb->icon_data;
	s->Open();
}


void info_callback(Widget,XtPointer,XmAnyCallbackStruct*)
{
	if(active) active->Open();
}

void kill_callback(Widget,XtPointer,XmAnyCallbackStruct*)
{
	if(active) active->Kill();
}

void debug_callback(Widget,XtPointer,XmAnyCallbackStruct*)
{
	if(active) active->Debug();
}


void exit_callback(Widget,XtPointer,XmAnyCallbackStruct*)
{
	exit(0);
}

void start_module(const char *name)
{
	request *r = empty_request("START"); 
	set_value(r,"NAME","%s",name); 
	call_switchboard(s,r);
	free_all_requests(r);
}

void module_callback(Widget w,XtPointer,XmAnyCallbackStruct*)
{
	const char *name = (char*)xec_GetUserData(w);
	start_module(name);
}


void input(Widget w,XtPointer,XEvent *ev,Boolean*)
{
	Icon icon = DragFindIconByPosition(w,ev->xbutton.x, ev->xbutton.y);

	switch(ev->xbutton.button)
	{
	case 2:
		if(icon)
		{
			active = (Info*)DragGetIconData(w,icon);
			//active->ShowRelations();
		}
		break;

	case 3:

		XtSetSensitive(action_menu,icon != NULL);
		if(icon)
		{
			xec_SetLabel(name_label,DragGetIconName(w,icon));
			active = (Info*)DragGetIconData(w,icon);
		}
		else
		{
			active = NULL;
			xec_SetLabel(name_label,"-");
		}

		XmMenuPosition(action_menu,(XButtonPressedEvent*)ev);
		XtManageChild(action_menu);
		break;
	}
}



void info(svcid *id,request *r,void *)
{	
	long ref   = atol(get_value(id->r,"REF",0));
	//const char *name = get_value(id->r,"NAME",0);
	const char *mode = get_value(id->r,"MODE",0);


	if(mode == _REPLY)
	{
		long reqid = atol(get_value(r,"REQ_ID",    0));
		Info *p = Info::Find(reqid);
		delete p;
	}
	else if(mode == _WAIT)
	{
		long reqid = atol(get_value(r,"REQ_ID",    0));
		long from  = atol(get_value(r,"SOURCE_REF",0));
		const char *to   = get_value(r,"TARGET",0);

		Info *p = Info::Find(reqid);
		delete p;
		new Wait(reqid,from,to,r->next);
	}
	else if(mode == _REGISTER)
	{
		Info *p = Info::Find(ref);
		delete p;
		new Service(ref,r);
	}
	else if(mode == _BUSY)
	{
		r = r->next;		
		long reqid = atol(get_value(r,"REQ_ID",    0));
		Info *p = Info::Find(reqid);
		delete p;
	}
	else if(mode == _SERVICE)
	{
		long reqid = atol(get_value(r,"REQ_ID",    0));
		long from  = atol(get_value(r,"SOURCE_REF",0));

		const char *x   = get_value(r,"WAITMODE",0);
		int sync  = x?atoi(x):0;

		Info *p = Info::Find(reqid);
		delete p;
		if( r->next )  //-- Q&D, 000810/vk
		    new Request(reqid,from,ref,r->next,sync);
	} 
	else if(mode == _EXIT)
	{
		Info *p = Info::Find(ref);
		delete p;
	}
	else if(mode == _PROGRESS)
	{
		long reqid = atol(get_value(r,"REQ_ID",    0));
		Info *p = Info::Find(reqid);
		if(p) p->Update(r);
	}
	else if(mode == _FOLLOWUP)
	{
		long reqid = atol(get_value(r,"REQ_ID",    0));
		Info *p = Info::Find(reqid);
		if(p) p->Update(ref);
	}

}

static void dropSourceCB(Widget,dropid *,void*)
{
}

static void dropTargetCB(Widget,dropid *id,void*)
{
	id->action = DROP_ACTION_RESOLVE;
	id->header = empty_request("MONITOR");
}

void dummy(svcid *id,request *,void *)
{
	if(XtIsRealized(top))
		XMapRaised(XtDisplay(top),XtWindow(top));
	send_reply(id,NULL);
}

void load_resources()
{
	XrmDatabase database = XtDatabase(display);
	char res[1024];

	request *r = mars.setup;

	while(r)
	{
		if(strcmp(r->name,"object") == 0)
		{
			const char *p = get_value(r,"class",0);
			const char *q = get_value(r,"pixmap",0);
			if(p && q)
			{
				sprintf(res, "Metview*%s.iconLargeBitmap", p);
				XrmPutStringResource(&database, res,  q);
			}
		}
		r = r->next;
	}

}

static void init_drag(Widget drag)
{
	XtAddEventHandler(drag,ButtonPressMask,False,input,NULL);
	XtAddCallback(drag,XtNdblClickCallback,
		(XtCallbackProc)open_icon_callback,NULL);

	RegisterDropSource(s, drag, dropSourceCB,NULL);
	RegisterDropTarget(s, drag, dropTargetCB,NULL);
	DragUseSmallIcons(drag,True);
}

static int xerror(Display *d, XErrorEvent *e)
{
  cout << "\nXlib error encountered!!!" << endl;
  cout << "   To find the originating location in the source code, i.e." << endl;
  cout << "   to get a proper call stack, run 'mvmon' in a debugger" << endl;
  cout << "   with run-time flag '-synchronous' (otherwise call stack" << endl;
  cout << "   may not correspond to the originating error location).\n" << endl;

  char buf[1024];
  XGetErrorText(d,e->error_code,buf,sizeof(buf));
  cout << "xerror: " << buf << "\n" << endl;

  abort();
  return 0;    //-- we could use this for non-fatal errors...
}

int main (int argc,char **argv)
{
	marsinit(&argc,argv,0,0,0);
	XtToolkitInitialize ();
	app_context = XtCreateApplicationContext ();
	display = XtOpenDisplay (app_context, NULL, argv[0], "Metview",
	                         NULL, 0, &argc, argv);

	if (!display)
	{
	    printf("%s: can't open display, exiting...\n", argv[0]);
	    exit (-1);
	}

#if (XmVersion < 2000)
	/* Register converters, just in case you are really unlucky !! */
	XmRegisterConverters();
#endif

	/* String to unit type doesn't get added !! */
	XtAddConverter ( XmRString, XmRUnitType, XmCvtStringToUnitType, NULL, 0 );
	create_top ( display, argv[0], argc, argv );
	create_action_menu(drag_service);
	create_request_top(drag_service);
	XtRealizeWidget (top);

	s = RegisterService(app_context,progname());
	add_service_callback(s,NULL,dummy,NULL);
	add_progress_callback(s,NULL,info,NULL);

	XSetErrorHandler(xerror);

	load_resources();
	init_drag(drag_service);
	init_drag(drag_wait);
	init_drag(drag_request);
	monitor();

	XtAppMainLoop (app_context);
	exit (0);
}


