/***************************** 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 <Xm/FileSB.h>
#include <Xm/SelectioB.h>
#include <Xm/ToggleB.h>
#include <mars.h>
#include <Drag.h>
#include <ctype.h>
#include <math.h>
#include <sys/wait.h>

#include "progress.h"
#include "inc_iostream.h"

extern "C" {
#include <xec.h>
#include <drop.h>
#include "examwidgets.h"

void  drag_send_callback(Widget w, XtPointer cd, DragCallbackStruct *cb);
void  drag_drop_callback(Widget w, XtPointer cd, DragCallbackStruct *cb);
void  drag_click_callback(Widget w, XtPointer cd, DragCallbackStruct *cb);
void  options_callback(Widget,XtPointer,XtPointer);
void  help_callback(Widget,XtPointer,XtPointer);

};



XtAppContext app_context;
Display      *display;
svc          *s = NULL;
int          ac;
char       **av;

struct {
	const char *param;
	int  number;
	int  on;
	int  folder;
	int  cnt;
	const char *val;
} names[] = {
	{"TYPE",    0,0, 1,},
	{"LEVTYPE", 0,0, 1,},
	{"DATE",    1,0, 1,},
	{"TIME",    1,0, 1,},
	{"STEP",    1,0, 1,},
	{"PARAM",   1,1, 0,},
	{"LEVELIST",1,1, 0,},
	{"NUMBER",  1,1, 0,},
};

class CIcon {
public:
	virtual request* getRequest() { return NULL; };
	virtual void Open(void)		= 0;
	virtual ~CIcon()               {};
};


class FieldIcon : public CIcon {
	Widget  grib;
	Widget  form;
	request *req;
public:
	request* getRequest()                         { return req; }
	virtual void Open(void);
	FieldIcon(request *r) : req(r),grib(0)      {};
	~FieldIcon()                                { free_all_requests(req); };
};

class FolderIcon : public CIcon {
	Widget top;
	Widget form;
	Widget drag;
public:
	virtual void Open(void)                     {
	XtManageChild(form);
	XRaiseWindow(XtDisplay(top),XtWindow(top));};
	FolderIcon(Widget t,Widget f,Widget d) : top(t),form(f),drag(d) {}
	~FolderIcon()    { XtDestroyWidget(top);};
};

//==============================================

static char *nameof(request *r)
{
	static	char name[80];
	static	char unknown[] = "???";
	const char   *a;

	*name = 0;
	for(int i = 0;i<NUMBER(names);i++)
		if(names[i].on && (a = get_value(r,names[i].param,0)))
		{
			if(*name) strcat(name,"-");
			strcat(name,a);
		}

	if(!*name)
	   return unknown;

	return name;
}


void help_callback(Widget,XtPointer,XtPointer)
{
}

void options_callback(Widget,XtPointer,XtPointer)
{
}

void drag_send_callback(Widget, XtPointer, DragCallbackStruct *)
{
}

void drag_drop_callback(Widget, XtPointer, DragCallbackStruct*)
{
}


void FieldIcon::Open(void)
{

	if(grib == NULL)
	{
		char  *tmp = marstmp();
		char buf[1024];

		const char* cv = get_value(req,"OFFSET",0);
		int offset = atoi( cv ) + 1; //-- adjust MARS offset for 'tail' cmd

		cv = get_value(req,"LENGTH",0);
		int length = atoi( cv );

		freopen(tmp,"w",stdout);

		//-- build cmd line to call script mv_grib_dump
		sprintf(buf,"$METVIEW_BIN/mv_grib_dump \"%s\" %d %d 2>&1",
			get_value(req,"PATH",0), offset, length );

		system(buf);

		create_text_top(top);
		grib = text;
		form = text_form;
		XtVaSetValues(text_top,XmNtitle,nameof(req),NULL);
		xec_LoadText(grib,tmp,false);
		unlink(tmp);
	}
	XtManageChild(form);
}


void drag_click_callback(Widget, XtPointer, DragCallbackStruct *cb)
{
	CIcon *c = (CIcon*)cb->icon_data;
	c->Open();
}


static int sortmax;
static int sortcnt;

static int comp(const void *a,const void *b)
{
	request **r = (request**)a;
	request **s = (request**)b;

	progress_value(++sortcnt);

	for(int j = 0; j<NUMBER(names);j++)
	{
		const char *x = get_value(*r,names[j].param,0);
		const char *y = get_value(*s,names[j].param,0);

		int c = 0;

		if(x && y) { if(names[j].number) c = atol(x) - atol(y) ; else c = strcmp(x,y); }
		else if(x) c = 1;
		else if(y) c = -1;

		if(c) return c;
	}
	return 0;
}

static void dropSourceCB(Widget,dropid *id,void*)
{
	request *r = clone_all_requests(id->header);
	request *t = r;
	int i;

	switch(id->action)
	{
		case DROP_ACTION_NAME_AND_CLASS:
			/* Should report error here .... */
			break;

		case DROP_ACTION_DEFINITION:
		case DROP_ACTION_RESOLVE:
			for(i = 0 ; i < id->count ; i++)
			{
				CIcon   *c = (CIcon*)id->cb[i].icon_data;
				request *s = clone_all_requests(c->getRequest());

				if(t == NULL)
					r = t = s;
				else
				{
					request *q = t;
					while(t) { q = t; t = t->next; }
					q->next = s;
					t = s;
				}
			}
			break;
	}

	printf("Sending to %s\n",id->service);
	print_all_requests(r);
	call_service(id->s,id->service,r,NULL);

}

static void dropTargetCB(Widget w,dropid *id,void*)
{
	id->action = DROP_ACTION_RESOLVE;

	/* id->mode   = empty_request("METGRAM"); */

	id->header = empty_request("DROP");
	set_value(id->header,"WIDGET","%s",XtName(w));
}

static void install(Widget w,request **p,int& n,int max,int j,const char *q)
{
	const char *x = NULL;
	const char *y = NULL;

	RegisterDropSource(s, w, dropSourceCB,NULL);
	RegisterDropTarget(s, w, dropTargetCB,NULL);
	while(names[j].folder && !names[j].cnt) j++;

	while(n<max)
	{
		request *r = p[n];

		if( q && (x = get_value(r,q,0)))
			if(y == NULL) y = x;
			else if(x != y) break;

		if(names[j].cnt && names[j].folder)
		{
			const char *a = get_value(r,names[j].param,0);
			create_folder_top(top);
			XtVaSetValues(folder_top,XmNtitle,a?a:"???",NULL);
			Widget f = folder_drag;
			DragAddIcon(w,"Folder",
				a ? a : "???",
				(XtPointer)new FolderIcon(folder_top,folder_form,folder_drag),
				0,0);
			install(f,p,n,max,j+1,names[j].param);
			DragCleanUpIcons(f);
		}
		else
		{
			const char *a = get_value(r,"REPRES",0);
			DragAddIcon(w,
			    a ? a : "LL",
			    nameof(r),
			    (XtPointer)new FieldIcon(r),
				0,0);
			n++;
			progress_value(n);
		}
	}
}

#define MAXFILEDS      2048
#define SMALL_BUF_LEN  1024

//-- possible return codes from 'readany' function:
#define READANY_OK                0
#define READANY_EOF              -1
#define READANY_BUFFER_TOO_SMALL -3
#define READANY_MISPLACED_7777   -5

void build_drag(char *name,char *clss,char *path,request *filter)
{
	typedef request* reqp;
	char buf[1024];
	char small_buf[SMALL_BUF_LEN];

print_all_requests(filter);

	reqp *p = new reqp[MAXFILEDS];

	sprintf(buf,"Examine %s: Reading file",name);
	progress_init(top);
	progress_start(buf,3);

	int  cnt = 0;
	int i;

	int offsets = count_values(filter,"OFFSET");
	int indexes = count_values(filter,"INDEX");

	const char* c_offset = "OFFSET";
	const char* c_index  = "INDEX";
	const char *param    = c_offset;
	int   count = offsets;

	if(indexes) {
		param = c_index;
		count = indexes;
	}

	long *vals = new long[count];
	for(i = 0;i<count;i++)
		vals[i] = atol(get_value(filter,param,i));


	FILE *f = fopen(path,"r");
	if(!f)
	{
		marslog(LOG_EROR|LOG_PERR,"Cannot open %s",path);
		marsexit(1);
	}

	fseek(f,0,SEEK_END);
	long filesize = ftell(f);
	rewind(f);

	progress_start(buf,filesize);
	int n = 0;

	long offset   = 0;
	if(offsets)
	{
		offset = vals[0];
		fseek(f,offset,SEEK_SET);
	}

	bool endOfFile = false;
	while((cnt < MAXFILEDS) && !endOfFile )
	{
		long curPos = ftell(f);      //-- store the current file pos

		//-- call 'readany' with a small (but big enough) buffer,
		//-- 'readany' returns the true length in 3rd argument
		long msg_length = SMALL_BUF_LEN;
		err e = _readany(f, small_buf, &msg_length);
		if( e == READANY_EOF )
		    break;

cout << "msg_length = " << msg_length << endl;

		//-- now go back and read to a buffer of the right size
		fseek(f,curPos,SEEK_SET);    //-- go back to the stored file pos
		char* grib_buf = new char[msg_length];
		e = _readany(f,grib_buf, &msg_length);
		if( e != READANY_OK ) {
			if( e == READANY_MISPLACED_7777 )
			    marslog(LOG_EROR, "Missing/misplaced 7777 (end-of-msg)");
			else
			    marslog(LOG_EROR|LOG_PERR,"Read error: 'readany' returns %d", e);
			marsexit(1);
		}

		boolean ok = true;
		n++;

		if(indexes) {
			ok = false;
			for(i=0; i<count; i++)
				if(n == vals[i]) {
					ok = true;
					break;
				}
		}

		if(ok) {
			request *r = empty_request("GRIB");
			grib_to_request(r,grib_buf,msg_length);
			offset = ftell(f) - msg_length;

			set_value(r,"OFFSET","%d",offset);
			set_value(r,"LENGTH","%d",msg_length);
			set_value(r,"PATH","%s",path);
			set_value(r,"TYPE","GRIB");
			set_value(r,"_NAME", "%s/Field %d of %s",name,cnt+1,mbasename(name));
			set_value(r,"_CLASS","%s",clss);

			// set the REPRES parameter. If any of this fails, then
			// REPRES wil not be set, and a default of LL will be used.

			grib_handle *g = grib_handle_new_from_message(0,grib_buf,msg_length);

			if (g) {
				const size_t cMaxBuf = 99;
				char strbuf[ cMaxBuf+1 ];
				size_t slen = cMaxBuf;

				int  err = grib_get_string(g, "gridType", strbuf, &slen);
				if (!err) {
					     if (!strcmp (strbuf, "reduced_gg")) set_value(r,"REPRES", "GG");
					else if (!strcmp (strbuf, "regular_gg")) set_value(r,"REPRES", "GG");
					else if (!strcmp (strbuf, "regular_ll")) set_value(r,"REPRES", "LL");
					else if (!strcmp (strbuf, "rotated_ll")) set_value(r,"REPRES", "LL");
					else if (!strcmp (strbuf, "sh"))         set_value(r,"REPRES", "SH");
					else if (!strcmp (strbuf, "space_view")) set_value(r,"REPRES", "SV");

					// "lambert", "mercator" grids not catered for;
				}
			}

			for(int j = 0; j<NUMBER(names);j++)
			{
				const char *p;
				if(!names[j].val)
					names[j].val = get_value(r,names[j].param,0);
				else if(!names[j].cnt && (p = get_value(r,names[j].param,0)))
					names[j].cnt = p != names[j].val;
			}

			p[cnt++] = r;
		}

		offset += msg_length;

		progress_value(offset);

		if(offsets) {
			if(n >= offsets) break;
			offset = vals[n];
			fseek(f,offset,SEEK_SET);
		}

		delete [] grib_buf;
	}
	progress_stop();
	progress_value(1);

	fclose(f);
	delete[] vals;

	if(cnt)
	{

		sprintf(buf,"Examine %s: Sorting fileds",name);

		double dcnt = (double)cnt;
		sortmax = log(dcnt)/log(2.0)*dcnt; /* best guess */
		sortcnt = 0;

		progress_start(buf,sortmax);
		qsort(p,cnt,sizeof(request*),comp);
		progress_stop();


		progress_value(1);
		sprintf(buf,"Examine %s: Building interface",name);
		progress_start(buf,cnt);
		int n = 0;
		install(drag,p,n,cnt,0,NULL);
		XtRealizeWidget(top);
		DragCleanUpIcons(drag);
		progress_stop();
	}
	progress_stop();


	delete[] p;

}


void drop(svcid *id,request *r,void *)
{
	printf("Got drop: \n");
	mars.debug = true;
	print_all_requests(r);
	mars.debug = false;
	send_reply(id,NULL);
}

//==============================================

void exam(svcid *id,request *r,void *)
{

	char buf[1024];
	r = clone_all_requests(r);
	char *p = strcache(get_value(r,"PATH",0));
	char *n = strcache(get_value(r,"_NAME",0));
	char *c = strcache(get_value(r,"_CLASS",0));

	if(fork_service(id)>0) return;


	send_reply(id,NULL);

	XtToolkitInitialize ();
	app_context = XtCreateApplicationContext ();
	display=XtOpenDisplay(app_context,0,mbasename(av[0]),
		"Metview",0,0,
#if (XmVersion >= 1002)
		&ac,
#else
		(Cardinal*)&ac,
#endif
		av);
	if (!display) marslog(LOG_EXIT,"can't open display, exiting...");

	create_top(display, av[0], ac, av );
	sprintf(buf,"Data in %s",n);

	XtVaSetValues(top,XmNtitle,buf,NULL);

	build_drag(n,c,p,r);
	free_all_requests(r);

	ListenToService(app_context,s);
	XtRealizeWidget(top);

	XtAppMainLoop(app_context);
}


int main(int argc,char **argv)
{
    marsinit(&argc,argv,0,0,0);

	ac = argc;
	av = argv;

	s = create_service(progname());
	add_service_callback(s, "GRIB", exam,  NULL);
	add_service_callback(s, "IMAGE",exam,  NULL);
	add_service_callback(s, "DROP", drop,  NULL);
	service_run(s);

	return 0;
}
