/* code to read and manipulate the file of sites.
 * N.B. we only read the file once. users of this code rely on this.
 */

#include <stdio.h>
#include <ctype.h>
#include <math.h>

#if defined(__STDC__)
#include <stdlib.h>
typedef const void * qsort_arg;
#else
typedef void * qsort_arg;
extern void *malloc(), *realloc();
#endif

#include <X11/Xlib.h>
#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xm/Separator.h>
#include <Xm/SelectioB.h>
#include <Xm/List.h>
#include <Xm/TextF.h>

#include "P_.h"
#include "astro.h"
#include "circum.h"
#include "preferences.h"
#include "sites.h"

extern Widget	toplevel_w;
extern char	*myclass;
#define XtD     XtDisplay(toplevel_w)

extern FILE *fopenh P_((char *name, char *how));
extern char *syserrstr P_((void));
extern void mm_setsite P_((double slat, double slng, double selev,char *sname,
    int update));
extern void prompt_map_cb P_((Widget w, XtPointer client, XtPointer call));
extern void set_xmstring P_((Widget w, char *resource, char *text));
extern void xe_msg P_((char *msg, int app_modal));

static int compstr P_((char *str, char *comp));
static void create_sq_w P_((void));
static void sites_fillsl P_((Widget sl_w));
static int sites_read_file P_((void));
static sites_cmpf P_((qsort_arg v1, qsort_arg v2));
static void set_selected P_((void));
static void sq_ok_cb P_((Widget w, XtPointer client, XtPointer call));
static void sq_cancel_cb P_((Widget w, XtPointer client, XtPointer call));
static void sq_tf_cb P_((Widget w, XtPointer client, XtPointer call));
static void sq_actlist_cb P_((Widget w, XtPointer client, XtPointer call));

static char sitefdef[] = "xephem_sites"; /* default name of sites file */
#define	MAXSITELLEN	256	/* maximum site file line length */
static Site **sitesp;		/* malloced list of malloced sites read file */
static int nsitesp;		/* number of entries in sitesp array */
static Widget sq_w;		/* sites query form dialog */
static Widget sql_w;		/* sites query scrolled list */

/* if sitesp has been set return nsitesp, else try again to read a sites file.
 * if still no luck, return -1.
 * also, if sippp != NULL set *sippp to sitesp.
 */
int
sites_get_list (sippp)
Site ***sippp;
{
	if (!sitesp && sites_read_file() < 0)
	    return (-1);

	if (sippp)
	    *sippp = sitesp;
	return (nsitesp);
}

/* let user choose a site from a scrolled list, if there is one */
void
sites_query()
{
	if (!sitesp && sites_read_file() < 0)
	    return;

	if (!sq_w) {
	    create_sq_w();
	    sites_fillsl (sql_w);
	}

	XtManageChild (sq_w);
}

/* search the sitesp list for first one which contains str, ignoring case and
 *   whitespace. start at the given position.
 * if found, return index into sites[], else return -1.
 */
int
sites_search (startpos, str)
int startpos;
char *str;
{
	char s1[MAXSITELLEN];
	int s1l;
	int i, n;

	if (!sitesp && sites_read_file() < 0)
	    return (-1);

	/* make copy of str in s1[] without whitespace and all upper case */
	s1l = compstr (str, s1);

	/* after doing the same for each name in sites[], check each
	 * possible offset location starting at startpos.
	 */
	for (n = 0, i = startpos; n < nsitesp; n++, i = (i+1)%nsitesp) {
	    Site *sip = sitesp[i];
	    char s2[MAXSITELLEN];
	    int s2l;
	    int dl;

	    s2l = compstr (sip->si_name, s2);

	    for (dl = s2l - s1l; dl >= 0; dl--)
		if (strncmp (s2+dl, s1, s1l) == 0)
		    return (i);
	}

	return (-1);
}

/* copy str to comp, removing all whitespace and converting all lower case
 * to uppercase.
 * return length of comp (not counting final \0, which we insure).
 */
static int
compstr (str, comp)
char *str;
char *comp;
{
	int i;
	char c;

	for (i = 0; (c = *str++); ) {
	    if (isspace(c))
		continue;
	    if (islower(c))
		c = toupper(c);
	    if (i < MAXSITELLEN-1)
		comp[i++]  = c;
	}
	comp[i] = '\0';

	return (i);
}

/* make the site selection dialog */
static void
create_sq_w()
{
	Widget cf_w, sp_w, stf_w;
	Widget w;
	Arg args[20];
	int n;
	
	/* create outter form dialog */

	n = 0;
	XtSetArg (args[n], XmNdefaultPosition, False);  n++;
	XtSetArg (args[n], XmNverticalSpacing, 7); n++;
	XtSetArg (args[n], XmNwidth, 300);
	sq_w = XmCreateFormDialog(toplevel_w, "Sites", args, n);
	XtAddCallback (sq_w, XmNmapCallback, prompt_map_cb, NULL);

	/* set some stuff in the parent DialogShell.
	 * setting XmNdialogTitle in the Form didn't work..
	 */
	n = 0;
	XtSetArg (args[n], XmNtitle, "xephem Site Selection"); n++;
	XtSetValues (XtParent(sq_w), args, n);

	/* make the controls at the bottom */
	n = 0;
	XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
	XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM); n++;
	XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++;
	XtSetArg (args[n], XmNfractionBase, 15); n++;
	XtSetArg (args[n], XmNverticalSpacing, 5); n++;
	cf_w = XmCreateForm (sq_w, "SitesCF", args, n);
	XtManageChild (cf_w);

	    n = 0;
	    XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM); n++;
	    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
	    XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
	    XtSetArg (args[n], XmNleftPosition, 3); n++;
	    XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
	    XtSetArg (args[n], XmNrightPosition, 6); n++;
	    w = XmCreatePushButton (cf_w, "Ok", args, n);
	    XtAddCallback (w, XmNactivateCallback, sq_ok_cb, NULL);
	    XtManageChild (w);

	    n = 0;
	    XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM); n++;
	    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
	    XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
	    XtSetArg (args[n], XmNleftPosition, 9); n++;
	    XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
	    XtSetArg (args[n], XmNrightPosition, 12); n++;
	    w = XmCreatePushButton (cf_w, "Cancel", args, n);
	    XtAddCallback (w, XmNactivateCallback, sq_cancel_cb, NULL);
	    XtManageChild (w);

	/* make a text field above the sep to enter the search string */
	n = 0;
	XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
	XtSetArg (args[n], XmNbottomWidget, cf_w); n++;
	XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM); n++;
	XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++;
	stf_w = XmCreateTextField (sq_w, "SitesTF", args, n);
	XtAddCallback (stf_w, XmNactivateCallback, sq_tf_cb, NULL);
	XtManageChild (stf_w);

	/* make a prompt above the text field */
	n = 0;
	XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
	XtSetArg (args[n], XmNbottomWidget, stf_w); n++;
	XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM); n++;
	sp_w = XmCreateLabel (sq_w, "SitesSL", args, n);
	set_xmstring (sp_w, XmNlabelString, "Search:");
	XtManageChild (sp_w);

	/* make the scrolled list at the top */
	n = 0;
	XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM); n++;
	XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
	XtSetArg (args[n], XmNbottomWidget, sp_w); n++;
	XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM); n++;
	XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++;
	XtSetArg (args[n], XmNselectionPolicy, XmBROWSE_SELECT); n++;
	XtSetArg (args[n], XmNvisibleItemCount, 10); n++;
	sql_w = XmCreateScrolledList (sq_w, "SiteSL", args, n);
	XtAddCallback (sql_w, XmNdefaultActionCallback, sq_actlist_cb, NULL);
	XtManageChild (sql_w);
}

/* read the sites file and set sitesp and nsitesp if successful to the list
 *   sorted by name.
 * return 0 if ok, else write to xe_msg() and return -1.
 * N.B. we assume this is never called again once a good file is found.
 */
static int
sites_read_file ()
{
	char buf[MAXSITELLEN];
	char msg[128];
	char *fn;
	FILE *fp;

	/* open the sites file */
	fn = XGetDefault (XtD, myclass, "SITESFILE");
	if (!fn)
	    fn = sitefdef;
	fp = fopenh (fn, "r");
	if (!fp) {
	    (void) sprintf(msg, "Can not open %.75s: %.25s", fn, syserrstr());
	    xe_msg (msg, 1);
	    return (-1);
	}

	/* read each entry, building up list */
	while (fgets (buf, sizeof(buf), fp) != NULL) {
	    char name[MAXSITELLEN];
	    int latd, latm, lats;
	    int lngd, lngm, lngs;
	    char latNS, lngEW;
	    double ele;
	    int nf;

	    if (!isalpha(buf[0]) && !isdigit(buf[0]))
		continue;

	    nf = sscanf (buf, "%[^;]; %3d %2d %2d %c   ; %3d %2d %2d %c   ;%lf",
					    name, &latd, &latm, &lats, &latNS,
					    &lngd, &lngm, &lngs, &lngEW, &ele);
	    if (nf == 10) {
		double lt, lg;
		char *newmem;
		Site *sp;
		int l;

		/* strip trailing blanks */
		for (l = strlen (name); --l >= 0; )
		    if (isspace(name[l]))
			name[l] = '\0';
		    else
			break;

		/* crack location */
		lt = degrad (latd + latm/60.0 + lats/3600.0);
		if (latNS == 'S')
		    lt = -lt;
		lg = degrad (lngd + lngm/60.0 + lngs/3600.0);
		if (lngEW == 'W')
		    lg = -lg;

		/* malloc and fill a new Site record */
		sp = (Site *) malloc (sizeof(Site) + strlen(name));
		if (!sp) {
		    (void) sprintf(msg, "Out of memory -- partial sites only.");
		    xe_msg (msg, 1);
		    break;
		}
		sp->si_lat = lt;
		sp->si_lng = lg;
		sp->si_elev = ele;
		(void) strcpy (sp->si_name, name);

		/* extend sitesp array */
		if (sitesp)
		    newmem= realloc((char *)sitesp, (nsitesp+1)*sizeof(Site *));
		else
		    newmem = malloc (sizeof(Site *));
		if (!newmem) {
		    (void) sprintf(msg, "Out of memory -- partial sites only.");
		    free ((void *)sp);
		    xe_msg (msg, 1);
		    break;
		}
		sitesp = (Site **) newmem;
		sitesp[nsitesp++] = sp;
	    }
	}

	(void) fclose (fp);

	/* sort by name */
	if (nsitesp > 0)
	    qsort ((void *)sitesp, nsitesp, sizeof(Site *), sites_cmpf);

	return (0);
}

/* fill the given ScrolledList with the set of sites.
 * N.B. we assume this is only ever called once.
 */
static void
sites_fillsl (sl_w)
Widget sl_w;
{
	XmString *xms;
	int i;

	if (!sitesp)
	    return;

	/* build array of XmStrings for fast updating of the ScrolledList */
	xms = (XmString *) XtMalloc (nsitesp * sizeof(XmString));
	for (i = 0; i < nsitesp; i++)
	    xms[i] = XmStringCreateSimple (sitesp[i]->si_name);
	XmListAddItems (sl_w, xms, nsitesp, 0);

	/* finished with the XmStrings table */
	for (i = 0; i < nsitesp; i++)
	    XmStringFree (xms[i]);
	XtFree ((void *)xms);
}

/* compare name portions of two pointers to pointers to Sites in qsort fashion.
 */
static int
sites_cmpf (v1, v2)
qsort_arg v1;
qsort_arg v2;
{
	char *name1 = (*(Site **)v1)->si_name;
	char *name2 = (*(Site **)v2)->si_name;

	return (strcmp (name1, name2));
}

/* find the current list selection, if exactly one, and set main.
 */
static void
set_selected ()
{
	int *pos, npos;

	/* register the selected location */
	if (XmListGetSelectedPos (sql_w, &pos, &npos)) {
	    if (npos == 1 && pos[0] >= 0 && pos[0] < nsitesp) {
		Site *sip = sitesp[pos[0]-1]; /* pos is 1-based */
		mm_setsite(sip->si_lat,sip->si_lng,sip->si_elev,sip->si_name,0);
	    } else
		xe_msg ("Please select one site to load.", 1);
	    XtFree((void *)pos);
	} else
	    xe_msg ("Site disappeared!", 0);
}

/* called when the Ok button is hit on the sites query prompt */
static void
sq_ok_cb (w, client, call)
Widget w;
XtPointer client;
XtPointer call;
{
	set_selected ();
	XtUnmanageChild (sq_w);
}

/* called when the Cancel button is hit on the sites query prompt */
static void
sq_cancel_cb (w, client, call)
Widget w;
XtPointer client;
XtPointer call;
{
	XtUnmanageChild (sq_w);
}

/* called when CR is hit in the search text field */
static void
sq_tf_cb (w, client, call)
Widget w;
XtPointer client;
XtPointer call;
{
	static int lastpos;
	char *str;
	int pos;

	str = XmTextFieldGetString (w);
	pos = sites_search (lastpos, str);
	if (pos >= 0) {
	    XmListSetPos (sql_w, pos+1);		/* scroll it to top */
	    XmListSelectPos (sql_w, pos+1, True);	/* highlight it */
	    lastpos = (pos + 1)%nsitesp;;
	} else
	    xe_msg ("No matching site found.", 1);

	XtFree (str);
}

/* called when an item in the scrolled list is double-clicked */
static void
sq_actlist_cb (w, client, call)
Widget w;
XtPointer client;
XtPointer call;
{
	set_selected ();
	XtUnmanageChild (sq_w);
}
