/***************************** 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 <Xm/Xm.h>
#include <X11/Xatom.h>
#include "mars.h"
#include "Drag.h"
#include "drop.h"

typedef struct drop{
	struct drop *next;
	svc         *s;
	DropProc    recvproc;
	DropProc    sendproc;
	void        *senddata;
	void        *recvdata;
	Widget	    widget;
}drop;

static dropid *ids   = NULL;
static drop   *drops = NULL;

static request* look_up(const char* name,const char *value)
{
	request *r = mars.setup;
	static char* service = NULL;
	if(!service) service = strcache("service");

	while(r)
	{
		if(r->name == service)
		{
			const char* p = get_value(r,name,0);
			if(p && strncmp(p,value,strlen(p)) == 0)
				return r;
		}
		r = r->next;
	}

	return 0;
}


static const char* find_other_service(DragCallbackStruct* cb)
{
	XClassHint hint;
	XTextProperty text;
	Window parent,root,*children;
	Display *dpy = XtDisplay(DragIconToWidget(cb->icon));
	unsigned int count;
	Window win = cb->remote_window;
	request *r;

	while(win != 0)
	{
		if(XGetClassHint(dpy,win,&hint))
		{
			if(hint.res_name)
			{
				r = look_up("x_resource_name",(const char*)hint.res_name);
				XFree(hint.res_name);
				if(r) return get_value(r,"name",0);
			}
			if(hint.res_class)
			{
				r = look_up("x_resource_class",(const char*)hint.res_class);
				XFree(hint.res_class);
				if(r) return get_value(r,"name",0);
			}
		}

		/* Must free text.value ????? */
		XGetTextProperty(dpy,win,&text,XA_WM_CLIENT_MACHINE);
		if(text.value)
		{
			r = look_up("x_client_machine",(const char*)text.value);
			/* XFree(text.value); */
			if(r) return get_value(r,"name",0);
		}

		XGetTextProperty(dpy,win,&text,XA_WM_COMMAND);
		if(text.value)
		{
			r = look_up("x_command",(const char*)text.value);
			/* XFree(text.value); */
			if(r) return get_value(r,"name",0);
		}

		XGetTextProperty(dpy,win,&text,XA_WM_ICON_NAME);
		if(text.value)
		{
			r = look_up("x_icon_name",(const char*)text.value);
			/* XFree(text.value); */
			if(r) return get_value(r,"name",0);
		}

		XGetTextProperty(dpy,win,&text,XA_WM_NAME);
		if(text.value)
		{
			r = look_up("x_name",(const char*)text.value);
			/* XFree(text.value); */
			if(r) return get_value(r,"name",0);
		}

		if(XQueryTree(dpy,win,&root,&parent,&children,&count))
		{
			win = parent;
			XFree(children);
		}
		else break;
	}

	return 0;
}

static request* find_other_mode(dropid* id,DragCallbackStruct* cb)
{
	return NULL;
}

static  int find_other_action(dropid* id,DragCallbackStruct* cb)
{
	request *r = mars.setup;
	static char* drop_action = NULL;
	if(!drop_action) drop_action = strcache("drop_action");

	while(r)
	{
		if(r->name == drop_action)
		{
			const char* p = get_value(r,"name",0);
			if(p && EQ(p,id->service))
			{
				const char* p = get_value(r,"class",0);
				if(!p || EQ(p,cb->icon_class))
					return atol(get_value(r,"action",0));
			}
		}
		r = r->next;
	}

	return DROP_ACTION_RESOLVE;
}

static drop *get_info(Widget w)
{
	drop *p = drops;
	while(p)
	{
		if(p->widget == w) return p;
		p = p->next;
	}
	return NULL;
}

static dropid *get_dropid(Widget w,drop *info,DragCallbackStruct *cb,
	boolean sender)
{
	const char *s;
	dropid *p = ids;

	while(p)
	{
		if(p->batch == cb->icon_batch && p->sender == sender)
			return p;
		p = p->next;
	}

	p = NEW_CLEAR(dropid);

	p->s       = info->s;	
	p->next    = ids;
	p->count   = cb->icon_count;
	p->cb      = (DragCallbackStruct*)XtCalloc(cb->icon_count,sizeof(DragCallbackStruct));
	p->batch   = cb->icon_batch;
	p->data    = (void*)info;
	p->sender  = sender;

	s  = DragGetInfo(cb);
	if(s) 
	{
		char service[80];
		char dummy[80];
		sscanf(s,"%s %s %s",service,dummy,dummy);
		p->service = strcache(service);
	}
	else {
		p->service = strcache(find_other_service(cb));
	}

	/*p->widget  = w;*/

	ids = p;
	return p;
}

static void add_cb(dropid *id,DragCallbackStruct *cb)
{
	memcpy(&id->cb[cb->icon_no-1],(void*)cb,sizeof(DragCallbackStruct));
	id->cb[cb->icon_no-1].icon_name   = strcache(cb->icon_name);
	id->cb[cb->icon_no-1].icon_class  = strcache(cb->icon_class);
	id->cb[cb->icon_no-1].remote_name = strcache(cb->remote_name);
}

static void free_dropid(dropid *s)
{
	dropid *p = ids;
	dropid *q = NULL;

	while(p)
	{
		if(p == s)
		{
			int i;

			if(q) q->next = p->next;
			else ids = p->next;

			for(i=0;i<p->count;i++)
			{
				strfree((char*)p->cb[i].icon_class);
				strfree((char*)p->cb[i].icon_name);
				strfree((char*)p->cb[i].remote_name);
			}
			XtFree((char*)p->cb);
			strfree(p->service);
			free_all_requests(p->header);
			free_all_requests(p->mode);
			free_all_requests(p->context);

			FREE(p);
			break;
		}
		q = p;
		p = p->next;
	}
}

static void destroy_callback(Widget w,XtPointer cd,XtPointer cb)
{
	drop *p = drops;
	drop *q = NULL;

	while(p)
	{
		if(p == (drop*)cd)
		{
			if(q) q->next = p->next;
			else drops = p->next;
			FREE(cd);
			return;
		}
		q = p;
		p = p->next;
	}
}

/* Receiver side */

static void drop_callback(Widget w,XtPointer cd,DragCallbackStruct *cb)
{
	drop *info = (drop*)cd;
	dropid *id;


	/* Ignore internal drops ... */
	if(cb->remote_widget && (strcmp(info->s->name,"VisMod") != 0)) return;

/*
	if(cb->message == NULL || strcmp(id,(char*)cb->message) != 0)
	{
		marslog(LOG_WARN,"Iter-Metview drops not supported (%s)",cb->message);
		cb->accept_it = False;
		return;
	}
*/

	id = get_dropid(w,info,cb,false);

	add_cb(id,cb);

	if(cb->icon_no == cb->icon_count)
	{
		/* We receive a proc */
		if(info->recvproc) {
			/* char *p = DragGetInfo(cb); */

			request *r = empty_request("DROP_REQUEST");
			info->recvproc(w,id,info->recvdata);

			set_value(r,     "BATCH",   "%d", id->batch);
			set_value(r,     "ACTION",  "%d", id->action);
			set_subrequest(r,"MODE",    id->mode);
			set_subrequest(r,"HEADER",  id->header);

			print_all_requests(r);
			send_drop_info(info->s,id->service,r,0);
			free_all_requests(r);
		}
		free_dropid(id);
	}
	cb->accept_it = True;
}

/* Sender side ... */


static void send_callback(Widget w,XtPointer cd,DragCallbackStruct *cb)
{
	drop *info         = (drop*)cd;
	dropid *id;

	/* Ignore internal drops ... */
	if(cb->remote_widget && (strcmp(info->s->name,"VisMod") != 0)) return;

	cb->send_it    = True;

	id = get_dropid(w,info,cb,true);
	add_cb(id,cb);
}


/* Sender side ... */

static void answer_callback(Widget w,XtPointer cd,DragCallbackStruct *cb)
{
	drop *info = (drop*)cd;
	dropid *id;
	/* char *service      = DragGetInfo(cb);  */

	if(cb->remote_widget && (strcmp(info->s->name,"VisMod") != 0)) return;

	id = get_dropid(w,info,cb,true);
	id->state = (cb->remote_widget != 0);

	switch(cb->code)
	{
	case DROP_OK:
		break;

	case DROP_REFUSED:
		marslog(LOG_WARN,"Cannot drop this icon here, drop refused");
		break;

	case DROP_ABORTED:
		/* This message should go if other windows accepted */
		marslog(LOG_WARN,"Cannot drop this icon here, invalid target");
		break;
	}
}

static void other_callback(Widget w,XtPointer cd,DragCallbackStruct *cb)
{
	request *r = empty_request("DROP");
	drop *info         = (drop*)cd;

	dropid *id ; 
	
	set_value(r,"X","%d",cb->x);
	set_value(r,"Y","%d",cb->y);
	set_value(r,"WINDOW","%d",cb->remote_window);
	set_value(r,"ICON_CLASS","%s",cb->icon_class);
	set_value(r,"ICON_NAME","%s",cb->icon_name);

	/* make an id for sender and receiver */
	/* id  = get_dropid(w,info,cb,false); */
	/* add_cb(id,cb); */

	id  = get_dropid(w,info,cb,true);
	add_cb(id,cb);

	if(id->service == NULL)
	{
		marslog(LOG_WARN,"Cannot drop this icon here, invalid target");
		free_dropid(id);
	}
	else if(cb->icon_no == cb->icon_count)
	{
		request *d = empty_request("DROP_REQUEST");

		set_value(d,     "BATCH",   "%d", id->batch);
		set_value(d,     "ACTION",  "%d", find_other_action(id,cb));
		set_subrequest(d,"MODE",    find_other_mode(id,cb));
		set_subrequest(d,"HEADER",  r);

		print_all_requests(d);
		send_drop_info(info->s,info->s->name,d,0);
		free_all_requests(d);
	}

	free_all_requests(r);
		

}

static void drop_request(svcid *id,request *r,void *data)
{
	int n;
	dropid *p = ids;
	print_all_requests(r);
	n = atoi(get_value(r,"BATCH",0));
	while(p)
	{
		if(p->batch == n && p->sender)
		{
			drop *info = (drop*)p->data;	
			p->mode   = get_subrequest(r,"MODE",0);
			p->header = get_subrequest(r,"HEADER",0);
			p->context = get_subrequest(r,"_CONTEXT",0);
			p->action = atoi(get_value(r,"ACTION",0));
			info->sendproc(info->widget,p,info->senddata);
			free_dropid(p);
			break;
		}
		p = p->next;
	}

}

static void install_drop_svc(svc *s)
{
	static installed = 0;
	if(installed) return;
	installed = 1;
	add_drop_callback(s,NULL,drop_request,NULL);
}

DragCallbackStruct *DropObjectFromSource(Widget widget,XEvent  *ev,
	Position  x,    Position  y,
	Dimension width,Dimension height,
	DragDrawProc draw,
	XtPointer data)
{
	DragCallbackStruct *cb = DragDropObject(widget,ev,x,y,width,height,
		draw,data);
	if(cb) {
		dropid *id;
		DragDropSend(widget,cb);
		id = get_dropid(widget,get_info(widget),cb,true);
		id->sender = true;
		add_cb(id,cb);
	}
	return cb;
}


void RegisterDropSource(svc *s,Widget w,DropProc proc,void *data)
{
	char buf[1024];

	drop *info = get_info(w);
	
		
	if(info == NULL)
	{
		info = NEW_CLEAR(drop);
		info->next     = drops;
		drops          = info;
		info->s        = s;
		info->widget   = w;
	}
	info->sendproc = proc;
	info->senddata = data;

	install_drop_svc(s);

	sprintf(buf,"%s %s %s",s->name,getenv("EVENT_HOST"),getenv("EVENT_PORT"));
	DragSetInfo(w,buf);

	if(XtIsDrag(w))
	{
		XtAddCallback(w,XmNsendmsgCallback,
			(XtCallbackProc)send_callback,(XtPointer)info);
		XtAddCallback(w,XmNanswerCallback,
			(XtCallbackProc)answer_callback,(XtPointer)info);
		XtAddCallback(w,XmNotherDropCallback,
			(XtCallbackProc)other_callback,(XtPointer)info);
	}
	XtAddCallback(w,XmNdestroyCallback,
		(XtCallbackProc)destroy_callback,(XtPointer)info);
}

void RegisterDropTarget(svc *s,Widget w,DropProc proc,void *data)
{
	char buf[1024];
	drop *info = get_info(w);
	
		
	if(info == NULL)
	{
		info = NEW_CLEAR(drop);
		info->next     = drops;
		drops          = info;
		info->s        = s;
		info->widget   = w;
	}

	info->recvproc = proc;
	info->recvdata = data;	

	install_drop_svc(s);

	if(XtIsDrag(w))
		XtAddCallback(w,XmNdropCallback,
		    (XtCallbackProc)drop_callback,(XtPointer)info);
	else
		DragAcceptDropCallback(w,
		    (XtCallbackProc)drop_callback,(XtPointer)info);

	XtAddCallback(w,XmNdestroyCallback,
	    (XtCallbackProc)destroy_callback,(XtPointer)info);

	sprintf(buf,"%s %s %s",s->name,getenv("EVENT_HOST"),getenv("EVENT_PORT"));
	DragSetInfo(w,buf);
}
