/*
 * xarchie : An X browser interface to Archie
 *
 * This file is sort of a mishmash of functions, some of which might get
 * sorted out later. Its primary purpose is to establish the X connection,
 * create the application's widgets, call any other other necessary
 * initialization routines, and enter XtAppMainLoop(). It also provides
 * the callback procedure selectItem() that makes the List widgets work
 * like a browser (in collaboration with the display routines in db.c).
 * Finally, it provides the generic setText(), setLabel(), and status*()
 * routines which could go just about anywhere.
 *
 * George Ferguson, ferguson@cs.rochester.edu, 12 Nov 1991.
 *
 */
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>	
#include <X11/Xaw/Paned.h>	
#include <X11/Xaw/Viewport.h>	
#include <X11/Xaw/List.h>	
#include <X11/Xaw/Form.h>	
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Label.h>	
#include <X11/Xaw/AsciiText.h>	
#include <X11/Xaw/Command.h>	
#include <X11/Xaw/Cardinals.h>	
#include "types.h"
#include "appres.h"
#include "classnames.h"
#include "db.h"
#include "actions.h"
#include "settings.h"
#include "patchlevel.h"

/*	-	-	-	-	-	-	-	-	*/
/*
 * Functions defined in this file:
 */
int main();
void initWidgetsFromString();
void displayHostInfo(), clearHostInfo();
void displayLocationInfo(), clearLocationInfo();
void displayFileInfo(), clearFileInfo();
void setText(), setLabel();
void status0(), status1(), status2();
void fail0(), fail1();

static void initGraphics(), initWidgets(), initErrorHandlers();
static void selectItem();
static void syntax();

/*
 * Global graphics data
 */
Display *display;
Screen *screen;
Window root;

/*
 * Global widget data
 */
XtAppContext appContext;
Widget toplevel;
Widget hostList,locationList,fileList;
Widget searchText;
Widget queryButton,abortButton;		/* global since changes sensitivity */

static Widget statusText;
static Widget hostText,locationText,fileText,sizeText,modesText,dateText;

/*
 * Other global data
 */
Database *db;
char *progname;

/*
 * The application resources
 */
AppResources appResources;

/*
 * Non-widget resources obtained from resource manager
 */
static XtResource resources[] = {
    { "widgets", "Widgets", XtRString, sizeof(String),
      XtOffset(AppResources *,widgets), XtRImmediate, "" },
    { "menus", "Menus", XtRString, sizeof(String),
      XtOffset(AppResources *,menus), XtRImmediate, "" },
    { "searchType", "SearchType", GfRSearchType, sizeof(SearchType),
      XtOffset(AppResources *,searchType), XtRImmediate, (XtPointer)GfExact },
    { "sortType", "SortType", GfRSortType, sizeof(SortType),
      XtOffset(AppResources *,sortType), XtRImmediate, (XtPointer)GfDefault },
    { "archieHost", "ArchieHost", XtRString, sizeof(String),
      XtOffset(AppResources *,archieHost), XtRImmediate, "archie.sura.net" },
    { "maxHits", "MaxHits", XtRInt, sizeof(int),
      XtOffset(AppResources *,maxHits), XtRImmediate, (XtPointer)99 },
    { "offset", "Offset", XtRInt, sizeof(int),
      XtOffset(AppResources *,offset), XtRImmediate, (XtPointer)0 },
    { "timeout", "Timeout", XtRInt, sizeof(int),
      XtOffset(AppResources *,timeout), XtRImmediate, (XtPointer)0 },
    { "retries", "Retries", XtRInt, sizeof(int),
      XtOffset(AppResources *,retries), XtRImmediate, (XtPointer)0 },
    { "niceLevel", "NiceLevel", XtRInt, sizeof(int),
      XtOffset(AppResources *,niceLevel), XtRImmediate, (XtPointer)0 },
    { "ftpDir", "FtpDir", XtRString, sizeof(String),
      XtOffset(AppResources *,ftpDir), XtRImmediate, "." },
    { "ftpType", "FtpType", XtRString, sizeof(String),
      XtOffset(AppResources *,ftpType), XtRImmediate, "binary" },
    { "debugLevel", "DebugLevel", XtRInt, sizeof(int),
      XtOffset(AppResources *,debugLevel), XtRImmediate, (XtPointer)0 },
    { "settingsWidgets", "SettingsWidgets", XtRString, sizeof(String),
      XtOffset(AppResources *,settingsWidgets), XtRImmediate, "" },
    { "saveFormatOneLine", "SaveFormatOneLine", XtRBoolean, sizeof(Boolean),
      XtOffset(AppResources *,saveFormatOneLine), XtRImmediate,
							(XtPointer)False },
    { "expert", "Expert", XtRBoolean, sizeof(Boolean),
      XtOffset(AppResources *,expert), XtRImmediate, (XtPointer)False },
};

/*
 * Non-widget resources set on command line.
 */
static XrmOptionDescRec options[] = {
    { "-search",  ".searchType", XrmoptionSepArg, (XtPointer)"exact" },
    { "-e",	  ".searchType", XrmoptionNoArg,  (XtPointer)"exact" },
    { "-s",	  ".searchType", XrmoptionNoArg,  (XtPointer)"substr" },
    { "-c",	  ".searchType", XrmoptionNoArg,  (XtPointer)"subcase" },
    { "-r",	  ".searchType", XrmoptionNoArg,  (XtPointer)"regexp" },
    { "-es",	  ".searchType", XrmoptionNoArg,  (XtPointer)"exactSubstr" },
    { "-ec",	  ".searchType", XrmoptionNoArg,  (XtPointer)"exactSubcase" },
    { "-er",	  ".searchType", XrmoptionNoArg,  (XtPointer)"exactRegexp" },
    { "-sort",    ".sortType",	 XrmoptionSepArg, (XtPointer)"default" },
    { "-t",	  ".sortType",	 XrmoptionNoArg,  (XtPointer)"invdate" },
    { "-host",	  ".archieHost", XrmoptionSepArg,
					(XtPointer)"archie.sura.net" },
    { "-maxhits", ".maxHits",	 XrmoptionSepArg, (XtPointer)"99" },
    { "-offset",  ".offset",	 XrmoptionSepArg, (XtPointer)"0" },
    { "-nice",    ".niceLevel",	 XrmoptionSepArg, (XtPointer)"0" },
    { "-N",       ".niceLevel",	 XrmoptionSepArg, (XtPointer)"0" },
    { "-debug",	  ".debugLevel", XrmoptionSepArg, (XtPointer)"1" },
    { "-D",	  ".debugLevel", XrmoptionSepArg, (XtPointer)"1" },
    { "-expert",  ".expert",     XrmoptionNoArg,  (XtPointer)"True" },
};

/*
 * Widget and non-widget resources if the application defaults
 * file can't be found.
 * Generated automatically from Xarchie.ad by "ad2c".
 * Comment out the include line (but not the NULL) if you don't want
 * any resources compiled in.
 */
static String fallbackResources[] = {
#include "Xarchie.ad.h"
    NULL
};

/*	-	-	-	-	-	-	-	-	*/

main(argc,argv)
int argc;
char **argv;
{
    char buf[64];

    progname = argv[0];
    initGraphics(&argc,argv);
    if (argc > 1) {
	syntax(argc,argv);
	XtDestroyApplicationContext(appContext);
	exit(1);
    }
    initErrorHandlers();
    initSettingsDefaults();
    initWidgets();
    XtRealizeWidget(toplevel);
    /* Set window title to indicate version */
    sprintf(buf,"xarchie %d.%d%s",xarchieMajorVersion,
				  xarchieMinorVersion,
				  xarchieExtraVersion);
    XStoreName(display,XtWindow(toplevel),buf);
    sprintf(buf,"Welcome to xarchie %d.%d%s",xarchieMajorVersion,
					     xarchieMinorVersion,
					     xarchieExtraVersion);
    status0(buf);
    /* get the data structure for responses */
    db = newDb();
    /* do it */
    XtAppMainLoop(appContext);
    /*NOTREACHED*/
}

/*	-	-	-	-	-	-	-	-	*/
/* Initialization routines */

static void
initGraphics(argcp,argv)
int *argcp;
char **argv;
{
    toplevel = XtAppInitialize(&appContext, "Xarchie",
			       options, XtNumber(options),
			       argcp,argv,fallbackResources,NULL,ZERO);
    initConverters(appContext);
    XawSimpleMenuAddGlobalActions(appContext);
    XtAppAddActions(appContext,actionTable,XtNumber(actionTable));
    XtGetApplicationResources(toplevel,(XtPointer)&appResources,
                              resources,XtNumber(resources),NULL,ZERO);
    display = XtDisplay(toplevel);
    screen = XtScreen(toplevel);
    root = RootWindowOfScreen(screen);
}

/*
 * initWidgets: Initialize the widgets given in the .widgets resource,
 *	check for required widgets, and set globals vars.
 */
static void
initWidgets()
{
    initWidgetsFromString(appResources.widgets,".widgets");
    /* check for the necessary widgets and set the global variables */
    if ((hostList=XtNameToWidget(toplevel,"*hostList")) == NULL)
	fail0("didn't create widget \"hostList\"");
    if ((locationList=XtNameToWidget(toplevel,"*locationList")) == NULL)
	fail0("didn't create widget \"locationList\"");
    if ((fileList=XtNameToWidget(toplevel,"*fileList")) == NULL)
	fail0("didn't create widget \"fileList\"");
    if ((searchText=XtNameToWidget(toplevel,"*searchText")) == NULL)
	fail0("didn't create widget \"searchText\"");
    /* set globals for optional widgets */
    statusText = XtNameToWidget(toplevel,"*statusText");
    hostText = XtNameToWidget(toplevel,"*hostText");
    locationText = XtNameToWidget(toplevel,"*locationText");
    fileText = XtNameToWidget(toplevel,"*fileText");
    sizeText = XtNameToWidget(toplevel,"*sizeText");
    modesText = XtNameToWidget(toplevel,"*modesText");
    dateText = XtNameToWidget(toplevel,"*dateText");
    queryButton = XtNameToWidget(toplevel,"*queryButton");
    abortButton = XtNameToWidget(toplevel,"*abortButton");
    if (abortButton != NULL)
	XtSetSensitive(abortButton,False);
    /* make the lists behave like a browser should */
    XtAddCallback(hostList,XtNcallback,selectItem,(XtPointer)1);
    XtAddCallback(locationList,XtNcallback,selectItem,(XtPointer)2);
    XtAddCallback(fileList,XtNcallback,selectItem,(XtPointer)3);
    /* since Xaw is so bloody stupid... */
    clearList(hostList);
    clearList(locationList);
    clearList(fileList);
    /* Some people crash when the EzMenu's are created earlier (puke-ola) */
    initWidgetsFromString(appResources.menus,".menus");
}

#define ISSPACE(c)	((c) == ' ' || (c) == '\t' || (c) == '\n')
#define NUL		'\0'

/*
 * initWidgetsFromString : Create the widgets specified in resourceStr as
 *	"parent class name" triples. The resourceName is used for
 *	error messages.
 */
void
initWidgetsFromString(resourceStr,resourceName)
char *resourceStr,*resourceName;
{
    char name[32],class[32],parent[256];
    char *s,*t;
    Boolean isShell;
    WidgetClass wc;
    Widget pw;

    if ((s=resourceStr) == NULL)
	fail1("no widgets specified in %s resource!",resourceName);
    while (*s) {
	/* skip leading whitespace */
        while (ISSPACE(*s))
            s += 1;
	if (!*s)
	    break;
	/* Gather the parent widget name */
        t = parent;
        while (*s && !ISSPACE(*s))
            *t++ = *s++;
        *t = NUL;
	/* skip whitespace */
        while (ISSPACE(*s))
            s += 1;
	if (!*s)
	    fail1("missing widget class and name in %s resource",resourceName);
	/* Gather the class name */
        t = class;
        while (*s && !ISSPACE(*s))
            *t++ = *s++;
        *t = NUL;
	/* skip whitespace */
        while (ISSPACE(*s))
            s += 1;
	if (!*s)
	    fail1("missing widget name in %s resource",resourceName);
	/* Gather the widget's name */
        t = name;
        while (*s && !ISSPACE(*s))
            *t++ = *s++;
        *t = NUL;
	/* convert class name to WidgetClass */
        if ((wc=classNameToWidgetClass(class,&isShell)) == NULL)
	    fail1("can't convert string \"%s\" to widgetClass",class);
	/* convert parent name to Widget */
	if (strcmp(parent,"toplevel") == 0)
	    pw = toplevel;
	else if ((pw=XtNameToWidget(toplevel,parent)) == NULL)
	    fail1("can't convert string \"%s\" to widget",parent);
	/* finally create the widget */
	if (isShell)
            (void)XtCreatePopupShell(name,wc,pw,NULL,ZERO);
        else
            (void)XtCreateManagedWidget(name,wc,pw,NULL,ZERO);
    }
}

/*	-	-	-	-	-	-	-	-	*/
/* The following functions attempt to provide information in the event of
 * a crash. If you have trouble compiling them because of UNIX-isms in
 * the signal handlers, then add -DDONT_CATCH_ERRORS to the definition
 * of DEFINES in the Imakefile and re-make (or just #define it here).
 */
#ifndef DONT_CATCH_ERRORS
static void crashHandler();
#endif /* DONT_CATCH_ERRORS */

static void
initErrorHandlers()
{
#ifndef DONT_CATCH_ERRORS
    signal(SIGBUS,crashHandler);
    signal(SIGSEGV,crashHandler);
#endif /* DONT_CATCH_ERRORS */
}

#ifndef DONT_CATCH_ERRORS
static void
crashHandler(sig)
int sig;
{
    char *s;

    switch (sig) {
	case SIGBUS: s = "SIGBUS"; break;
	case SIGSEGV: s = "SIGSEGV"; break;
	default: s = "UNKNOWN";
    }
    fprintf(stderr,"%s: caught a %s signal!\n",progname,s);
    fprintf(stderr,"If a backtrace (using dbx or gdb or something) shows that the crash is\n");
    fprintf(stderr,"is occurring in XtNameToWidget() or something similar, then before\n");
    fprintf(stderr,"reporting an error, please check that your X distribution is up to date.\n");
    fprintf(stderr,"Xarchie may not run under versions of X that are not at least X11R4\n");
    fprintf(stderr,"with all fixes applied. Your copy of \".../include/X11/Intrinsic.h\"\n");
    fprintf(stderr,"should have XConsortium revision number at least 1.139.\n");
    fprintf(stderr,"If you still want to report an error, please indicate your hardware type,\n");
    fprintf(stderr,"operating system, compiler, and your version of X and include a\n");
    fprintf(stderr,"backtrace. Thanks.\n");
    abort();
}

#endif /* DONT_CATCH_ERRORS */

/*	-	-	-	-	-	-	-	-	*/

/* Callback procedures */

/*ARGSUSED*/
static void
selectItem(w,closure,call_data)
Widget w;
XtPointer closure,call_data;
{
    XawListReturnStruct *item = (XawListReturnStruct*)call_data;

    switch ((int)closure) {
	case 1 :
	    if (selectedHostEntry &&
		findHostIndexFromEntry(selectedHostEntry) == item->list_index) {
		XawListUnhighlight(hostList);
		clearFileInfo();
		clearList(fileList);
		clearLocationInfo();
		clearList(locationList);
		clearHostInfo();
		selectedHostEntry = HOST_NULL;
	    } else {
		selectedHostEntry = findHostEntryFromIndex(item->list_index,
							   db);
		displayHostInfo(selectedHostEntry);
		displayLocs(selectedHostEntry);
	    }
	    break;
	case 2 :
	    if (selectedLocEntry &&
		findLocIndexFromEntry(selectedLocEntry) == item->list_index) {
		XawListUnhighlight(locationList);
		clearFileInfo();
		clearList(fileList);
		clearLocationInfo();
		selectedLocEntry = LOC_NULL;
	    } else {
		selectedLocEntry = findLocEntryFromIndex(item->list_index,
							 selectedHostEntry);
		displayLocationInfo(selectedLocEntry);
		displayFiles(selectedLocEntry);
	    }
	    break;
	case 3 :
	    if (selectedFileEntry &&
		findFileIndexFromEntry(selectedFileEntry) == item->list_index) {
		XawListUnhighlight(fileList);
		clearFileInfo();
		selectedFileEntry = FILE_NULL;
	    } else {
		selectedFileEntry = findFileEntryFromIndex(item->list_index,
							   selectedLocEntry);
		displayFileInfo(selectedFileEntry);
	    }
	    break;
    }
}

/*	-	-	-	-	-	-	-	-	*/

void
displayHostInfo(hostp)
HostEntry *hostp;
{
    if (hostText != NULL)
	setText(hostText,hostp->hostname);
}

void
clearHostInfo()
{
    if (hostText != NULL)
	setText(hostText,"");
}

void
displayLocationInfo(locp)
LocEntry *locp;
{
    if (locationText != NULL)
	setText(locationText,locp->linkpath);
}

void
clearLocationInfo()
{
    if (locationText != NULL)
	setText(locationText,"");
}

void
displayFileInfo(filep)
FileEntry *filep;
{
    char buf[16];

    if (fileText != NULL)
	setText(fileText,filep->name);
    if (sizeText != NULL) {
	sprintf(buf,"%d",filep->size);
	setText(sizeText,buf);
    }
    if (modesText != NULL)
	setText(modesText,filep->modes);
    if (dateText != NULL)
	setText(dateText,filep->date);
}

void
clearFileInfo()
{
    if (fileText != NULL)
	setText(fileText,"");
    if (sizeText != NULL)
	setText(sizeText,"");
    if (modesText != NULL)
	setText(modesText,"");
    if (dateText != NULL)
	setText(dateText,"");
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * setText() : Set the given Text item's value to the given string.
 */
void
setText(item,text)
Widget item;
char *text;
{
    Arg args[1];

    if (item != NULL) {
	XtSetArg(args[0],XtNstring,text);
	XtSetValues(item,args,ONE);
    }
}

/*
 * setLabel() : Set the given Label item's value to the given string.
 */
void
setLabel(item,text)
Widget item;
char *text;
{
    Arg args[1];

    if (item != NULL) {
	XtSetArg(args[0],XtNlabel,text);
	XtSetValues(item,args,ONE);
    }
}

/*	-	-	-	-	-	-	-	-	*/

void
status0(str)
char *str;
{
    if (statusText != NULL)
	setText(statusText,str);
}

void
status1(fmt,arg)
char *fmt,*arg;
{
    char buf[256];

    sprintf(buf,fmt,arg);
    status0(buf);
}

void
status2(fmt,arg1,arg2)
char *fmt,*arg1,*arg2;
{
    char buf[256];

    sprintf(buf,fmt,arg1,arg2);
    status0(buf);
}

/*	-	-	-	-	-	-	-	-	*/

void
fail0(str)
char *str;
{
    fprintf(stderr,"%s: %s\n",progname,str);
    XtDestroyApplicationContext(appContext);
    exit(1);
}

void
fail1(fmt,arg)
char *fmt,*arg;
{
    char buf[256];

    sprintf(buf,fmt,arg);
    fail0(buf);
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * syntax() : Print whatever caused the error and the usage message.
 */
static void
syntax(argc,argv)
int argc;
char **argv;
{
    char *program;

    program = *argv;
    argv += 1;
    if (argc > 2 || (strcmp(*argv,"-help") != 0 && strcmp(*argv,"-?") != 0)) {
	fprintf(stderr,"%s: bad argument(s): ",program);
	while (--argc)
	    fprintf(stderr,"%s ",*argv++);
	fprintf(stderr,"\n");
    }
    fprintf(stderr,"Valid options (in addition to X Toolkit options) are:\n");
    fprintf(stderr,"  -search type\tset query type\n");
    fprintf(stderr,"  -e\t\tset query type to exact\n");
    fprintf(stderr,"  -c\t\tset query type to subcase\n");
    fprintf(stderr,"  -s\t\tset query type to substr\n");
    fprintf(stderr,"  -r\t\tset query type to regexp\n");
    fprintf(stderr,"  -ec\t\tset query type to exactSubcase\n");
    fprintf(stderr,"  -es\t\tset query type to exactSubstr\n");
    fprintf(stderr,"  -er\t\tset query type to exactRegexp\n");
    fprintf(stderr,"  -sort type\tset sort mode\n");
    fprintf(stderr,"  -t\t\tset sort mode to invdate\n");
    fprintf(stderr,"  -host host\tconnect to Archie at host\n");
    fprintf(stderr,"  -maxHits num\tset maximum number of matches per query\n");
    fprintf(stderr,"  -offset off\tset Prospero offset\n");
    fprintf(stderr,"  -[nice|N] level\tset query niceness\n");
    fprintf(stderr,"  -[debug|D] level\tset Prospero debugging level\n");
    fprintf(stderr,"  -help\t\tprint this message\n");
    fprintf(stderr,"  -xrm 'resource: value'  pass arbitrary resources\n");
    fprintf(stderr,"Options can be abbreviated to their shortest unique prefix.\n");
}
