#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Table.h>
#include <X11/Wc/WcCreate.h>
#include <string.h>

#include "defs.h"
#include "xznol.h"
#include "zephyr.h"
#include "info.h"
#include "adduser.h"
#include "wcl_init.h"
#include "AriRegAll.h"

#define RELOCATE_TIMEOUT 60
#define STATUS_TIMEOUT 30

XtResource appres[] = {
   {"statusWidget", "StatusWidget", XtRWidget, sizeof(Widget),
       XtOffset(defaults *, status), XtRString, "*status"},
};

#define appres_cnt sizeof(appres)/sizeof(XtResource)

defaults defs;
static XtAppContext app;
static XtIntervalId status_id = 0;
static void setstatus(char *str);

/*ARGSUSED*/
void status_timer(XtPointer client_data, XtIntervalId *timer)
{
   if (defs.status)
      XtVaSetValues(defs.status,
		    XtNlabel, "Waiting for Input",
		    NULL);
}

static void setstatus(char *str)
{
   if (status_id)
      XtRemoveTimeOut(status_id);

   if (defs.status)
      XtVaSetValues(defs.status,
		    XtNlabel, str,
		    NULL);

   if (status_id)
      status_id = XtAppAddTimeOut(app, STATUS_TIMEOUT*1000, status_timer,
				  NULL);
}

Widget name_to_widget(Widget base, char *name)
{
   XrmValue from, to;

   from.addr = name;
   from.size = strlen(name)+1;

   to.addr = NULL;

   if (!XtConvertAndStore(base, XtRString, &from, XtRWidget, &to))
      return(NULL);

   return(*((Widget *) to.addr));
}

/*ARGSUSED*/
static void ungrab(Widget w, XtPointer client, XtPointer call)
{
   XtUngrabPointer(w,CurrentTime);
}

/* This is a replacement wrapper for XtPopupSpringLoaded() */
static void ButtonPressPopUpMenu(Widget w, XEvent *ev, String *params,
				 Cardinal *num_params)
{
/* Possible Xt bug?  This is necessary, because the server implies a
   grab from the window which was clicked in, so no motion events actually
   get to the menu window.  The XtGrabPointer is necessary so events will
   go to the window, even if the pointer isn't in it.  All in all, I'm
   pretty convinced something is wrong.  This should only be necessary if
   the menu was popped up by a ButtonPress event.

   Solution (I think):  have XtPopupSpringLoaded do the following:
   1) Ungrab the pointer to lose the automatic active grab
   2) Do what it does now, ending with XMapRaising the menu window
   3) XtGrabPointer on the menu
   4) install a callback on popdown so the pointer is ungrabbed
*/

   Widget popup;

   if (*num_params < 1) return;

   popup = name_to_widget(w, params[0]);

   if (ev->type != ButtonPress) {
      XtPopupSpringLoaded(popup);
   } else {
      if (XtDisplay(popup) == ev->xcrossing.display)
	XUngrabPointer(ev->xcrossing.display,CurrentTime);

      XtPopupSpringLoaded(popup);
      XtGrabPointer(popup,		/* menu grabs pointer */
		    False,		/* all events go to menu */
		    PointerMotionMask|ButtonReleaseMask|EnterWindowMask|
		    LeaveWindowMask,	/* these events are important */
		    GrabModeAsync,	/* process other events async */
		    GrabModeAsync,
		    0,			/* don't confine */
		    /* I'd like XC_top_left_arrow, but this is easiest */
		    None,		/* cursor */
		    CurrentTime);	/* grab now */

      XtAddCallback(popup,XtNpopdownCallback,ungrab,NULL);
   }
}

/*ARGSUSED*/
static void RecheckAll(Widget w, XEvent *event, String *params,
		       Cardinal *num_params)
{
   locate_all_users(1);
}

/*ARGSUSED*/
static void ResubAll(Widget w, XEvent *event, String *params,
		     Cardinal *num_params)
{
   sub_all_users();
}

static Widget stored_widget = NULL;

/*ARGSUSED*/
static void StoreZnol(Widget w, XEvent *event, String *params,
		      Cardinal *num_params)
{
   stored_widget = (*num_params < 1)?w:name_to_widget(w, params[0]);
}

/*ARGSUSED*/
static void RecheckZnol(Widget w, XEvent *event, String *params,
			Cardinal *num_params)
{
   if (stored_widget)
      locate_table(widget_to_table(stored_widget));
}

/*ARGSUSED*/
static void ResubZnol(Widget w, XEvent *event, String *params,
		      Cardinal *num_params)
{
   if (stored_widget)
      sub_table(widget_to_table(stored_widget));
}

static user *stored_user = NULL;

/*ARGSUSED*/
static void StoreUser(Widget w, XEvent *event, String *params,
		      Cardinal *num_params)
{
   XawTableReturnStruct trs;

   if (XawTableGetClicked(w, event, XawTableReturnLine, &trs))
      stored_user = find_user(trs.line->element);
   else
      stored_user = NULL;
}

/*ARGSUSED*/
static void RecheckUser(Widget w, XEvent *event, String *params,
			Cardinal *num_params)
{
   if (stored_user)
      locate_user(stored_user);
}

/*ARGSUSED*/
static void ResubUser(Widget w, XEvent *event, String *params,
		      Cardinal *num_params)
{
   if (stored_user)
      sub_user(stored_user);
}

/*ARGSUSED*/
static void Quit(Widget w, XEvent *event, String *params,
		 Cardinal *num_params)
{
   zephyr_cleanup();

   exit(0);
}

/*ARGSUSED*/
static void StatusAct(Widget w, XEvent *event, String *params,
		      Cardinal *num_params)
{
   if (*num_params > 0)
      setstatus(params[0]);
}

/*ARGSUSED*/
static void SaveInfo(Widget w, XEvent *event, String *params,
		     Cardinal *num_params)
{
   Widget text;

   if (*num_params < 1) return;

   text = name_to_widget(w, params[0]);

   save_user_table_text(text);
}

/*ARGSUSED*/
static void AddUser(Widget w, XEvent *event, String *params,
		    Cardinal *num_params)
{
   Widget text;
   int save = 0;

   if (*num_params < 1) return;

   text = name_to_widget(w, params[0]);

   if (*num_params >= 2)
      save = (strcasecmp(params[1],"SAVE") == 0);

   create_user(text, save);
}

XtActionsRec app_actions[] = {
   {"MyPopupMenu", ButtonPressPopUpMenu},
   {"RecheckAll", RecheckAll},
   {"ResubAll", ResubAll},
   {"StoreZnol", StoreZnol},
   {"RecheckZnol", RecheckZnol},
   {"ResubZnol", ResubZnol},
   {"StoreUser", StoreUser},
   {"ResubUser", ResubUser},
   {"RecheckUser", RecheckUser},
   {"Quit", Quit},
   {"Status", StatusAct},
   {"SaveInfo", SaveInfo},
   {"AddUser", AddUser},
};

#define num_app_actions sizeof(app_actions)/sizeof(XtActionsRec)

/* Wcl WcConstructor */
/* creates an AsciiText widget for the saved user and table */

Widget user_table_text_constructor(Widget parent, char *name)
{
   return(make_user_table_text(parent, name,
			       stored_user,
			       widget_to_table(stored_widget)));
}

Widget adduser_text_constructor(Widget parent, char *name)
{
   return(make_adduser_text(parent, name, widget_to_table(stored_widget)));
}

/*ARGSUSED*/
void set_label_to_field(Widget w, XtPointer client_data, XtPointer call_data)
{
   char *label;

   if (stored_user && client_data)
      label = get_user_field_text(stored_user, (char *) client_data);
   else
      label = "(no field specified)";

   XtVaSetValues(w, XtNlabel, label, NULL);
}

/* the next several functions implement a modal zephyr input loop used by
   Z_WaitForNotice, while still processing X event */

void process_xinput()
{
   /* check if there are any X events pending and process them, since those
    * are "most important" to making the application feel like it's working.
    * While there are actually events around, this may look like a loop,
    * rather than a select, but it shouldn't ever needlessly waste cycles.
    */

   while(XtAppPending(app) & XtIMXEvent)
      XtAppProcessEvent(app, XtIMXEvent);
}
   
static XtIntervalId zwfn_id = 0;
static int zinput;
static int ztimeout;

/*ARGSUSED*/
void zwfn_timer(XtPointer client_data, XtIntervalId *timer)
{
   zwfn_id = 0;
   ztimeout = 1;
}

void add_zwfn_timeout(int timeout)
{
   zwfn_id = XtAppAddTimeOut(app, timeout*1000, zwfn_timer, NULL);
}

int app_zwfn()
{
   ztimeout = 0;
   zinput = 0;

   while(!ztimeout && !zinput) {
      process_xinput();

      /* No X events pending.  Deal with timeouts and/or zephyr packets
	 (If an X event manages to sneak in here, and the queue manager
	 wants to process it, fine. Nothing will break.) */
      XtAppProcessEvent(app, XtIMAll);
   }

   return(ztimeout);
}

void remove_zwfn_timeout()
{
   XtRemoveTimeOut(zwfn_id);
   zwfn_id = 0;
   ztimeout = 0;
}

static XtIntervalId relocate_id;

/*ARGSUSED*/
void relocate_timer(XtPointer client_data, XtIntervalId *timer)
{ 
   locate_all_users(0);

   relocate_id = XtAppAddTimeOut(app, RELOCATE_TIMEOUT*1000,
				 relocate_timer, NULL);
}

/*ARGSUSED*/
void fd_input(XtPointer client_data, int *source, XtInputId *id)
{
   if (zwfn_id)
      zinput = 1;
   else
      zephyr_input(0);
}

void xtinit(int *pargc, char *argv[])
{
   Widget appshell;
   int zfd;

   appshell = XtAppInitialize(&app, "XZnol", NULL, 0, pargc, argv,
			      NULL, NULL, 0);

   zfd = zephyr_init();

   XpRegisterAthena(app);

   wcl_init(app);

   WcWidgetCreation(appshell);

   XtGetApplicationResources(appshell, (XtPointer) &defs,
			     appres, appres_cnt, NULL, 0);

   XtAppAddInput(app, zfd, (XtPointer) XtInputReadMask, fd_input, NULL);

   XtAppAddActions(app, app_actions, num_app_actions);

   XtRealizeWidget(appshell);

   /* Now that the window is mapped, set up initial zephyr state */

   locate_all_users(1);
   sub_all_users();

   /* set up recurring actions */

   relocate_id = XtAppAddTimeOut(app, RELOCATE_TIMEOUT*1000,
				 relocate_timer, NULL);
   status_id = XtAppAddTimeOut(app, STATUS_TIMEOUT*1000,
			       status_timer, NULL);

   /* this event loop gives preference to X events, if multiple input is
      available */

   while(1) {
      process_xinput();
      XtAppProcessEvent(app, XtIMAll);
   }
}

/* AddPathToSearchPath is stolen from xscreensaver:
 *
 * Copyright (c) 1989 by Jonathan Kamens.  This code may be
 * distributed freely as long as this notice is kept intact in its
 * entirety and every effort is made to send all corrections and
 * improvements to the code back to the author.  Also, don't try to
 * make any money off of it or pretend that you wrote it.
 */

#ifdef APPDIR
static void
AddPathToSearchPath(path)
char *path;
{
     char *old, *new;

     old = (char *) getenv("XFILESEARCHPATH");
     if (old) {
#if defined(mips) || defined(hpux) || defined(sun) || \
	  (defined(ibm) && defined(SYSV) && defined(i386))
	  /* +1 for =, +2 for :, +3 for null */
	  new = XtMalloc((Cardinal) (strlen("XFILESEARCHPATH") +
				     strlen(old) +
				     strlen(path) + 3));
	  (void) strcpy(new, "XFILESEARCHPATH");
	  (void) strcat(new, "=");
	  (void) strcat(new, old);
	  (void) strcat(new, ":");
	  (void) strcat(new, path);
	  putenv(new);
#else
	  /* +1 for colon, +2 for null */
	  new = XtMalloc((Cardinal) (strlen(old) + strlen(path) + 2));
	  (void) strcpy(new, old);
	  (void) strcat(new, ":");
	  (void) strcat(new, path);
	  setenv("XFILESEARCHPATH", new, 1);
#endif
     }
     else {
#if defined(mips) || defined(hpux) || defined(sun) || \
	  (defined(ibm) && defined(SYSV) && defined(i386))
	  new = XtMalloc((Cardinal) (strlen("XFILESEARCHPATH") +
				     strlen(path) + 2));
	  (void) strcpy(new, "XFILESEARCHPATH");
	  (void) strcat(new, "=");
	  (void) strcat(new, path);
	  putenv(new);
#else
	  setenv("XFILESEARCHPATH", path, 1);
#endif
     }
}
#endif

main(int argc, char *argv[])
{
#ifdef APPDIR
   AddPathToSearchPath(APPDIR);
#endif

   xtinit(&argc,argv);
}
