/***************************** 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 <inc_iostream.h>
#include <Xm/Xm.h>
#include <Xm/List.h>
#include <Xm/Text.h>
#include <mars.h>
#include <drop.h>
#include <math.h>
#include <gdbm.h>
#include <fcntl.h>
#include <Metview.h>
#include "station.h"
#include "utils.h"

extern "C" {
	void quit_callback(Widget,XtPointer,XtPointer);
	void text_activate_callback(Widget,XtPointer,XtPointer);
	void list_browse_callback(Widget,XtPointer,XmListCallbackStruct*);
	void help_callback(Widget,XtPointer,XtPointer);
	void clear_callback(Widget,XtPointer,XmToggleButtonCallbackStruct*);
	void names_callback(Widget,XtPointer,XmToggleButtonCallbackStruct*);
	void map_callback(Widget,XtPointer,XtPointer);
	void plot_sel_callback(Widget,XtPointer,XtPointer);
	void plot_all_callback(Widget,XtPointer,XtPointer);
	void EPS_only_callback(Widget,XtPointer,XtPointer);
#include <xec.h>
#include "stbase.h"
};

svc *service;
char title[256];

XtAppContext app_context;
Display *display;       /*  Display             */

XtWorkProcId wp   = 0;  // NULL;
char *name        = 0;
svcid *curr_id    = 0; 
long wmo_ident    = 0;  //-- 'ident' caused problems on HP
char mode         = 'n';
double north,south,east,west,threshold;
Boolean   clear   = True;
Boolean   names   = False;
MvRequest  window;
Boolean    user_defined = False;

ifstream* EPS_file = NULL;
int  current_EPS_mode = 0;
int  EPS_mode         = 0;

Icon       icon_sel   = NULL;
Icon       icon_all   = NULL;
request   *req_sel    = NULL;
request   *req_all    = NULL;

int cnt;


Boolean work(XtPointer);
Boolean work_WMO_stations();
Boolean work_EPS_stations();


class ReqId {
public:
	ReqId(const char *s,request *r)     { call_service(service,s,r,long(this)); }
	virtual void reply(request *) = 0;
	virtual ~ReqId()              {};
};

void EPS_only_callback(Widget,XtPointer,XtPointer)
{
  if( current_EPS_mode == 0 )
    {
      ++current_EPS_mode;
    }
  else
    {
      current_EPS_mode = 0;
    }
}


void help_callback(Widget w,XtPointer,XtPointer)
{
	if(w == helpw) w = form;
	show_help_page(service,XtName(w),XtName(w));
}

void clear_callback(Widget,XtPointer,XmToggleButtonCallbackStruct *cb)
{
	clear = cb->set;
}

void names_callback(Widget,XtPointer,XmToggleButtonCallbackStruct *cb)
{
	names = cb->set;
}

void plot(request *r,Icon icon)
{
  const char cTAB = '\t';
  err e;

  MvRequest in = r;
  //  in.print();

  if(window == NULL)                    //-- build display window (mapview)
    {
      MvRequest coast = "PCOAST";

      coast("SUBPAGE_FRAME_COLOUR") = "TAN";
      coast("MAP_COASTLINE_COLOUR") = "OCHRE";
      coast("MAP_GRID_COLOUR")      = "TAN";
      coast("MAP_LABEL")            = "OFF";

      MvRequest w = "MAPVIEW";
      w("COASTLINES")             = coast;
      w("PAGE_ID_LINE_USER_TEXT") = "Station database results";
      w("MAP_PROJECTION")         = "CYLINDRICAL";

      window = MvRequest(wait_service(service,"PlotMod",w,&e));
    }

  in("_CLASS") = "STATIONS";
  in("_NAME")  = DragGetIconName(drag,icon);

  MvRequest st = wait_service(service,"station",in,&e);

  const char* gpfname = marstmp();      //-- create GeoPoints file --
  ofstream gpf( gpfname );
  if( ! gpf )
    {
      marslog( LOG_EROR, "Unable to open GeoPoints work file...\n" );
      return;
    }

  gpf << "#GEO\n";
  gpf << "#FORMAT XYV\n";
  gpf << "#DATA" << endl;

  while( st )                           //-- if several stations
    {
      MvRequest one = st.justOneRequest();

      double x = one( "LONGITUDE" );
      double y = one( "LATITUDE" );

      static char fixed[ 512 ];
      strcpy( fixed, one( "NAME" ) );
      char* p = fixed;
      while( p && *p )
	{
	  if( *p == ' ' )   //-- Q&D fix to keep name together
	    *p = '_';       //-- i.e. replace spaces by underlines
	  ++p;
	}

      gpf  << x << cTAB << y << cTAB << fixed << endl;
      cout << x << cTAB << y << cTAB << fixed << endl;

      st.advance();
    }

  gpf.close();

  MvRequest req = window;                //-- display window (via mapview)
  if(clear)
    req = req + MvRequest("CLEAN");

  MvRequest gp( "GEOPOINTS" );
  gp( "PATH" ) = gpfname;

  req = req + gp;                        //-- geopoints file

  MvRequest marker = "PSYMB";
  marker("SYMBOL_TYPE") = "MARKER";
  marker("SYMBOL_INPUT_MARKER_LIST") = 15;
  marker("SYMBOL_COLOUR") = "RED";

  req = req + marker;                    //-- symbol marker visdef
	
  if(names)
    {
      MvRequest name = "PSYMB";
      name("SYMBOL_TYPE") = "TEXT";

      req = req + name;                  //-- symbol name (if requested)
    }

  //  req.print();

  call_service(service,"PlotMod",req,0); //-- let PlotMod plot geopoints
}

void plot_sel_callback(Widget,XtPointer,XtPointer)
{
	plot(req_sel,icon_sel);
}

void plot_all_callback(Widget,XtPointer,XtPointer)
{
	plot(req_all,icon_all);
}

void add(station& st)
{

	char buf[1024];

	char ns = st.pos.lat>0?'N':'S';
	char ew = st.pos.lon>0?'E':'W';

	int lat = st.pos.lat> 0 ? st.pos.lat : -st.pos.lat;
	int lon = st.pos.lon> 0 ? st.pos.lon : -st.pos.lon;


	sprintf(buf,
		"%05d  %2d.%02d%c %3d.%02d%c  %s  %s",
		st.ident,
		lat/100,lat%100,ns,
		lon/100,lon%100,ew,
		st.pos.flags,
		nice_name(st.name));

	xec_AddListItem(list_stbase,buf);
}

Boolean work(XtPointer)
{
  if( EPS_mode != 0 )
    return work_EPS_stations();
  else
    return work_WMO_stations();
}

Boolean work_WMO_stations()
{
	static datum key;  /* static because we need to keep it for subsequent searches */
	if(db == NULL) {
		open_database(mode);
		if(db == NULL) {
			xec_SetLabel(label_info,"Cannot open WMO station database");
			wp = 0;   //NULL;
			return True;
		}
		key = gdbm_firstkey(db);
		cnt = 0;
		xec_SetLabel(label_info,"Searching...");
		user_defined = False;
	}
	else key = gdbm_nextkey(db, key);

	if(key.dptr == NULL)
	{
		gdbm_close(db);
		wp = 0;  // NULL;
		db = NULL;
		if(cnt == 0)
			xec_SetLabel(label_info,"No WMO stations found");
		else
			xec_VaSetLabel(label_info,
				"%d matching record%s found, make your selection!",cnt,cnt>1?"s":"");
		return True;
	}

	datum content = gdbm_fetch(db,key);
	station st;
	memcpy(&st,content.dptr,MIN(content.dsize,sizeof(st)));

	int ok = 0;

	switch(mode)
	{
		case 'n':
			ok = (name==0) || same(name,st.name);
			break;

		case 'i':
			if(wmo_ident < 100) ok = st.ident / 1000 == wmo_ident;
			else ok = wmo_ident == st.ident;
			break;

		case 'l':
			ok = inbox(st.pos.lat/100.0,st.pos.lon/100.0,north,west,south,east);
			break;
	}

	if(ok)
	{
		add(st);
		cnt++;
		if(cnt == 1) {
			XtSetSensitive(plot_all_button,True);
			icon_all =  DragAddIcon(drag,"STATIONS",title,(void*)&req_all,0,0);
		}
	}

	return False;

}
Boolean work_EPS_stations()
{
  if( EPS_file == NULL )
    {
      if( ! open_EPS_stationfile( EPS_file ) )
	{
	  xec_SetLabel(label_info,"Cannot open EPS station file");
	  wp = 0;   // NULL;
	  return true;
	}
      cnt = 0;
      xec_SetLabel(label_info,"Searching...");
      user_defined = False;
    }

  station st;

  if( ! read_one_station( EPS_file, st ) )
    {
      //-- search is over; clean up...
      delete EPS_file;
      EPS_file = NULL;
      wp = 0;  // NULL;

      if( cnt == 0 )
	xec_SetLabel(label_info,"No EPS stations found");
      else
	xec_VaSetLabel(label_info,
		       "%d matching record%s found, make your selection!",cnt,cnt>1?"s":"");
      return True;
    }

  int ok = 0;

  switch(mode)
    {
      case 'n':
	ok = (name==0) || same(name,st.name);
	break;

      case 'i':
	if(wmo_ident < 100)
	  ok = st.ident / 1000 == wmo_ident;
	else
	  ok = wmo_ident == st.ident;
	break;

      case 'l':
	ok = inbox(st.pos.lat/100.0,st.pos.lon/100.0,north,west,south,east);
	break;
    }

  if( ok )
    {
      //-- station matches, add it...
      add( st );
      ++cnt;
      if( cnt == 1 )
	{
	  XtSetSensitive(plot_all_button,True);
	  icon_all = DragAddIcon(drag,"STATIONS",title,(void*)&req_all,0,0);
	}
    }

  return False;
}

void delete_sel_icon()
{
	if(icon_sel) DragDeleteIcon(drag,icon_sel);
	icon_sel = NULL;
	XtSetSensitive(plot_sel_button,False);
}

void delete_all_icon()
{
	if(icon_all) DragDeleteIcon(drag,icon_all);
	icon_all = NULL;
	XtSetSensitive(plot_all_button,False);
}

void stop()
{
	delete_sel_icon();
	if(db) gdbm_close(db);
	if(wp) XtRemoveWorkProc(wp);
	wp = 0; // NULL;
	db = NULL;
}

void build_list(void)
{
	stop();
	EPS_mode = current_EPS_mode;
	delete_all_icon();
	XmListDeleteAllItems(list_stbase);
	XtSetSensitive(plot_all_button,False);
	wp = XtAppAddWorkProc(app_context,work,NULL);
}

double text2num(Widget w)
{
	char *p = XmTextGetString(w);
	double n = atof(p);
	XtFree(p);
	return n;
}

void quit_callback(Widget,XtPointer,XtPointer)
{
  if(curr_id)
    send_reply(curr_id,0);

  exit(0);
}

void list_browse_callback(Widget,XtPointer,XmListCallbackStruct *cb)
{
	char *p = xec_GetString(cb->item);
	int  nameOffset = current_EPS_mode ? 25 : 29;

	delete_sel_icon();

	char id[80];

	sscanf(p,"%s",id);

	request *r = empty_request("INPUT");
	set_value(r,"NAME", "%s",p+nameOffset);
	set_value(r,"IDENT","%s",id);
	set_value(r,"WMO_BLOCK","%02d",atol(id)/1000);

	if(req_sel) free_all_requests(req_sel);
	if(user_defined) 
		req_sel = clone_all_requests(req_all);
	else
	{
		req_sel = empty_request("STATIONS");
		set_value(req_sel,"IDENT","%s",id);
		set_value(req_sel,"SEARCH_KEY","IDENT");
		set_value(req_sel,"SEARCH_STATIONS_DATABASE","YES");
	}

	if( EPS_mode != 0 )
	  set_value(req_sel,"SEARCH_STATIONS_DATABASE","EPS");

	icon_sel =  DragAddIcon(drag,"STATIONS",p+nameOffset,(void*)&req_sel,0,0);
	XtSetSensitive(plot_sel_button,True);

	if(curr_id) 
	  send_progress(curr_id,NULL,r);

	free_all_requests(r);
	XtFree(p);
}

void show(const char *p)
{
	Widget w1 = XtNameToWidget(search_menu,p);
	Widget w2 = XtNameToWidget(sub_form,p);

	if(!w1 || !w2) return;

	Arg arg;
	XtSetArg(arg,XmNmenuHistory, w1 );
	XtSetValues(search_option,&arg,1);

	XtUnmanageChild(row_pos);
	XtUnmanageChild(row_area);
	XtUnmanageChild(row_name);
	XtUnmanageChild(row_ident);
	XtUnmanageChild(row_wmo);

	XtManageChild(w2);
}

void set_text(Widget w,const char *x)
{
	XmTextSetString(w,(char*)(x?x:""));
}

void set_text(Widget w,long n)
{
	char buf[30];
	sprintf(buf,"%ld",n);
	set_text(w,buf);
}

void set_text(Widget w,double n)
{
	char buf[30];
	sprintf(buf,"%g",n);
	set_text(w,buf);
}

void new_req_all(char p)
{
	double a,b,c,d;
	int n,w,s,e;

	if(req_all) free_all_requests(req_all);
	req_all = empty_request("STATIONS");

	if( EPS_mode != 0 )
	  set_value(req_all,"SEARCH_STATIONS_DATABASE","EPS");
	else
	  set_value(req_all,"SEARCH_STATIONS_DATABASE","YES");

	switch(p)
	{
		case 'N':
			sprintf(title,"Name starts with '%s'",name);
			set_value(req_all,"NAME","%s",name);
			set_value(req_all,"SEARCH_KEY","NAME");
			break;
		
		case 'I':
			sprintf(title,"Identifier is %05d",wmo_ident);
			set_value(req_all,"IDENT","%05d",wmo_ident);
			set_value(req_all,"SEARCH_KEY","IDENT");
			break;

		case 'W':
			sprintf(title,"WMO block is %02d",wmo_ident);
			set_value(req_all,"WMO_BLOCK","%02d",wmo_ident);
			set_value(req_all,"SEARCH_KEY","WMO_BLOCK");
			break;

		case 'P':
			a = north - threshold;
			b = west  + threshold;
			n = int(fabs(a)*100);
			w = int(fabs(b)*100);
			e = int(fabs(threshold)*100);
			sprintf(title,"Position is %d.%0d%c %d.%0d%c (+-%d.%0d)",
				n/100,n%100,a>0?'N':'S',
				w/100,w%100,b>0?'E':'W',
				e/100,e%100);
			set_value(req_all,"POSITION","%g",a);
			add_value(req_all,"POSITION","%g",b);
			set_value(req_all,"THRESHOLD","%g",threshold);
			set_value(req_all,"SEARCH_KEY","POSITION");
			break;

		case 'A':
			a = north; n = int(fabs(a)*100);
			b = west ; w = int(fabs(b)*100);
			c = south; s = int(fabs(c)*100);
			d = east ; e = int(fabs(d)*100);
			sprintf(title,"Area is %d.%0d%c %d.%0d%c %d.%0d%c %d.%0d%c",
				n/100,n%100,a>0?'N':'S',
				w/100,w%100,b>0?'E':'W',
				s/100,s%100,c>0?'N':'S',
				e/100,e%100,d>0?'E':'W'
				);
			set_value(req_all,"AREA","%g",a);
			add_value(req_all,"AREA","%g",b);
			add_value(req_all,"AREA","%g",c);
			add_value(req_all,"AREA","%g",d);
			set_value(req_all,"SEARCH_KEY","AREA");
			break;
	}

}

void map_callback(Widget w,XtPointer,XtPointer)
{
}

void text_activate_callback(Widget w,XtPointer,XtPointer)
{
	char c = *XtName(w);
	char *p;

	switch(c)
	{
		case 'N':
			mode = 'n';
			p = XmTextGetString(text_name);
			strfree(name);
			name = strcache(p);
			XtFree(p);
			break;
		
		case 'I':
			mode = 'i';
			wmo_ident = (long)text2num(text_ident);
			break;

		case 'W':
			mode = 'i';
			wmo_ident = (long)text2num(text_wmo);
			break;

		case 'P':
			mode = 'l';
			threshold = text2num(text_thr);
			north     = text2num(text_lat); north += threshold;
			west      = text2num(text_lon); west  -= threshold;
			south     = text2num(text_lat); south -= threshold;
			east      = text2num(text_lon); east  += threshold;
			break;

		case 'A':
			mode = 'l';
			north  = text2num(text_n);
			west   = text2num(text_w);
			south  = text2num(text_s);
			east   = text2num(text_e);
			if(north < south)
			{
				double tmp = north;
				north      = south;
				south      = tmp;
			}
			break;
	}
	new_req_all(c);
	build_list();
}

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

void serve_input(svcid *id,request *r,void *)
{
  //if(curr_id)
  //	send_reply(curr_id,0);

	curr_id = id;
		
	if(XtIsRealized(top))
		XMapRaised(XtDisplay(top),XtWindow(top));

	print_all_requests(r);

	const char *p;
	stop();

	if(p = get_value(r,"NAME",0))
	{
		mode = 'n';
		strfree(name);
		name = strcache(p);
		set_text(text_name,p);
		show("NAME");
		new_req_all('N');
	}

	if(p = get_value(r,"IDENT",0))
	{
		mode = 'i';
		wmo_ident = atol(p);
		set_text(text_ident,p);
		show("IDENT");
		new_req_all('I');
	}

	if(p = get_value(r,"WMO_BLOCK",0))
	{
		mode = 'i';
		wmo_ident = atol(p);
		set_text(text_wmo,p);
		show("WMO_BLOCK");
		new_req_all('W');
	}

	build_list();
}


void serve_drop(svcid *id,request *r,void *)
{
	r = r->next;

	while(r)
	{
		if(strcmp(r->name,"STATIONS") == 0)
		{
			const char *p = get_value(r,"SEARCH_STATIONS_DATABASE",0);
			if(p)
			{
				if(*p == 'N')
				{
					stop();
					XmListDeleteAllItems(list_stbase);
					delete_all_icon();
					xec_SetLabel(label_info,"User defined location");
					user_defined = True;
					station st;

					p = get_value(r,"IDENT",0);
					st.ident   = p?atol(p):0;
					set_text(text_ident,(long)st.ident);

					p = get_value(r,"POSITION",0);
					st.pos.lat = long(p?atof(p)*100.0:0.0);
					set_text(text_lat,st.pos.lat/100.0);

					p = get_value(r,"POSITION",1);
					st.pos.lon = long(p?atof(p)*100.0:0.0);
					set_text(text_lon,st.pos.lon/100.0);

					p = get_value(r,"NAME",0);
					strcpy(st.name,p?p:"?");
					set_text(text_name,st.name);
					add(st);

					if(req_all) free_all_requests(req_all);
					req_all = clone_all_requests(r);
					XtSetSensitive(plot_all_button,True);
					icon_all =  DragAddIcon(drag,"STATIONS",
						mbasename(get_value(r,"_NAME",0)),(void*)&req_all,0,0);
				}
				else
				{
					p = get_value(r,"SEARCH_KEY",0);
					if(p)
					{
						char c;
						show(p);
						stop();
						switch(c = *p)
						{
						case 'N':
							mode = 'n';
							strfree(name);
							name = strcache(get_value(r,"NAME",0));
							set_text(text_name,name);
							break;

						case 'I':
							mode = 'i';
							p = get_value(r,"IDENT",0);
							set_text(text_ident,p);
							wmo_ident   = p?atol(p):0;
							break;

						case 'W':
							mode = 'i';
							p = get_value(r,"WMO_BLOCK",0);
							set_text(text_wmo,p);
							wmo_ident   = p?atol(p):0;
							break;

						case 'P':
							mode = 'l';

							p = get_value(r,"THRESHOLD",0);
							set_text(text_thr,p);
							threshold = p?atof(p):0;

							p = get_value(r,"POSITION",0);
							set_text(text_lat,p);
							north     = p?atof(p):0; north += threshold;

							p = get_value(r,"POSITION",1);
							set_text(text_lon,p);
							west      = p?atof(p):0; west  -= threshold;

							south     = north - 2*threshold;
							east      = west  + 2*threshold;
							break;

						case 'A':
							mode = 'l';
							p = get_value(r,"AREA",0);
							set_text(text_n,p);
							north  = p?atof(p):0;
							p = get_value(r,"AREA",1);
							set_text(text_w,p);
							west   = p?atof(p):0;
							p = get_value(r,"AREA",2);
							set_text(text_s,p);
							south  = p?atof(p):0;
							p = get_value(r,"AREA",3);
							set_text(text_e,p);
							east   = p?atof(p):0;
							if(north < south)
							{
								double tmp = north;
								north      = south;
								south      = tmp;
							}
							break;
						}
						new_req_all(c);
						build_list();
					}
				}
			}
		}
		else xec_SetLabel(label_info,"Cannot use this icon");

		r = r->next;
	}

	send_reply(id,NULL);
}

void dropTargetCB(Widget w,dropid *id,void*)
{
	id->action = DROP_ACTION_DEFINITION;
	id->header = empty_request("DROP");
	set_value(id->header,"SELECT","%d",w == drag);
}

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:
			for(i = 0 ; i < id->count ; i++)
			{
				add_value(r,"NAME","%s",id->cb[i].icon_name);
				add_value(r,"CLASS","%s",id->cb[i].icon_class);
			}
			break;

		case DROP_ACTION_DEFINITION:
		case DROP_ACTION_RESOLVE:
			for(i = 0 ; i < id->count ; i++)
			{
				request *s = clone_all_requests(*(request**)id->cb[i].icon_data);
				set_value(s,"_NAME","%s",id->cb[i].icon_name);
				set_value(s,"_CLASS","%s",id->cb[i].icon_class);

				if(id->action == DROP_ACTION_RESOLVE)
				{
					err e;
					request *u = wait_service(service,"station",s,&e);
					free_all_requests(s);
					s = u;
				}

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

	call_service(id->s,id->service,r,0);   //NULL);
	free_all_requests(r);
}

void reply(svcid *,request *,void*)
{
  marslog( LOG_DBUG, "...non-implemented reply method...\n" );
}

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 );
	/* build_list(); */
	XtRealizeWidget (top);
	xec_SetLabel(label_info,"Enter a search string and hit Return (or drop an icon)");

	service = RegisterService(app_context,progname());

	add_service_callback(service,"STATION",serve_station,NULL);
	add_service_callback(service,"INPUT",serve_input,NULL);
	add_service_callback(service,"DROP",serve_drop,NULL);
	add_reply_callback(service,NULL,reply,NULL);

	RegisterDropTarget(service, list_stbase, dropTargetCB,NULL);
	RegisterDropTarget(service, drag, dropTargetCB,NULL);
	RegisterDropSource(service, drag, dropSourceCB,NULL);


	XrmDatabase database = XtDatabase(display);

    char res[1024];
    sprintf(res, "%s/icons/STATIONS.icon",getenv("METVIEW_DIR_SHARE"));
    XrmPutStringResource(&database,"Metview*STATIONS.iconLargeBitmap",res);

	XtUnmanageChild(row_ident);
	XtUnmanageChild(row_wmo);
	XtUnmanageChild(row_area);
	XtUnmanageChild(row_pos);

	XtAppMainLoop (app_context);
	exit (0);
}
