#include <stdio.h>
#include <strings.h>
#include <signal.h>

#include "callbacks.h"
#include "draw.h"
#include "game.h"
#include "main.h"

#include <X11/Xatom.h>

#include "icon.xbm"
#include "icon_mask.xbm"

#define SIGSYS       29   /* Neat Linux Fix */
#define SIGLOST      31   /* Ditto          */



static Atom Protocol_atom;
static Atom Kill_atom;
static Atom Save_atom;


XtAppContext application_context;
Display *display;
static int screen;
int depth;
Widget toplevel;
MyWidget canvas_widget;
Pixmap canvas;
Widget roomname_widget;
Widget itemstaken_widget;
Widget lives_widget;
Widget tripswitch_widget;
Widget discovery_widget;
Widget play_button;
MyWidget time_widget;


int time_ascent;
int scroll_ascent;
int scroll_w;
int scroll_h;
GC gc_scroll;
GC gc_string;
GC gc_time;
GC gc_time_white;
GC gc_black;
GC gc_white;
GC gc_copyplane;
GC gc_xorplane[4];
GC gc_and[4];
GC gc_zero;
GC gc_three;
GC gc_xorchar;
GC gc_dash[4];
GC gcs[NBCOLORS];
Colormap colormap;
unsigned long pixels[NBCOLORS];
int color_display;
int private_colormap;

#if 0
Widget slider_val;
Widget slider;
#endif


int time_widget_w, time_widget_h;


char *myname;
static char *app_class = "Jet-Set Willy for Linux";

int mono = 0;

#if 0
int debugging = 0;
#endif


int find_top_in_tab (unsigned long *tab, int tablen)
{
    int i;
    int cnt;
    int top;

    for (i=tablen-4, cnt=0; i>=0 && cnt!=4; i--) {
	int j;
	top = tab[i] & ~3;
	cnt = 1;
	for (j=i+1; j<tablen && cnt!=4; j++)
	    if ((tab[j] & ~3) == top)
		cnt++;
    }
    return (cnt == 4) ? top : -1;
}


int main (argc, argv)
    int argc;
    char **argv;
{
    {
	char *ptr = rindex (*argv, '/');
	if (ptr) ptr++; else ptr = *argv;
	myname = (char *) malloc (strlen (ptr) + 1);
	strcpy (myname, ptr);
    }

    {
	int i;
	for (i=1; i<argc; i++) {
	    if (!strcmp (argv[i], "-version")) {
		fprintf (stderr, "Jet-Set Willy, by <Florent.Guillaume@ens.fr>\nLinux port by John M Dow\n");
		fprintf (stderr, "Version 1.0 -- 26 Feb 1994\n");
		exit (0);
	    } else if (   !strcmp (argv[i], "-bw")
		       || !strcmp (argv[i], "-mono")
		       || !strcmp (argv[i], "-monochrome")) {
		mono = 1;
	    } else if (!strcmp (argv[i], "-s")) {
		print_scores (0);
		exit (0);
	    } else if (!strcmp (argv[i], "-S")) {
		print_scores (1);
		exit (0);
	    } else if (   !strcmp (argv[i], "-h")
		       || !strcmp (argv[i], "-H")
		       || !strcmp (argv[i], "-?")
		       || !strcmp (argv[i], "-help")
		       || !strcmp (argv[i], "--help")
		       || !strcmp (argv[i], "-usage")) {
		fprintf (stderr, "usage: %s [-s|-S] [-monochrome]\n", myname);
		fprintf (stderr, "\t-s\t\tgive 10 best scores\n");
		fprintf (stderr, "\t-S\t\tgive all scores\n");
		fprintf (stderr, "\t-version\tshow version number\n");
		fprintf (stderr, "\t-bw\n");
		fprintf (stderr, "\t-mono\n");
		fprintf (stderr, "\t-monochrome\tforce monochrome mode\n");
		exit (0);
	    }
	}
    }


#if 0
    if (getuid () == something)
	debugging = 1;
#endif

    {
	Widget form;
	Widget button_box;
	Widget quit_button;
	Widget draw_button;
	Widget sl_form;
	Widget itemlab;
	Widget liveslab;
	int realuid = getuid ();
	int realgid = getgid ();
	int gamesuid = geteuid ();
	int gamesgid = getegid ();

	XtToolkitInitialize ();
	application_context = XtCreateApplicationContext ();

	/* Change uids, because when doing authentication by Xauthority,
	   Xlib seems to want euid=ruid correctly set */
	setreuid (-1, realuid);	/* keep gamesuid in save-set */
	setregid (-1, realgid);	/* keep gamesgid in save-set */
	display = XtOpenDisplay (application_context, NULL, myname,
				 app_class, NULL, 0, &argc, argv);
	setreuid (realuid, gamesuid);
	setregid (realgid, gamesgid);

	if (display == NULL) {
	    fprintf (stderr, "Couldn't open display %s.\n",
		     XDisplayName (NULL));
	    exit (2);
	}

	screen = DefaultScreen (display);
	toplevel = XtAppCreateShell (myname, app_class,
				     applicationShellWidgetClass,
				     display, NULL, 0);

	XtGetApplicationResources (toplevel, NULL, NULL, 0, NULL, 0);
    
	form = XtVaCreateManagedWidget
	    ("form", formWidgetClass, toplevel, NULL);

	button_box = XtVaCreateManagedWidget
	    ("buttonbox", formWidgetClass, form,
	     XtNorientation, (XtArgVal) XtorientVertical,
	     XtNborderWidth, (XtArgVal) 0,
	     XtNinternalHeight, (XtArgVal) 0,
	     XtNinternalWidth, (XtArgVal) 0,
	     NULL);
    
	quit_button = XtVaCreateManagedWidget
	    ("quitb", commandWidgetClass, button_box,
	     XtNlabel, (XtArgVal) "Quit",
	     NULL);
	XtAddCallback (quit_button, XtNcallback,
		       (XtCallbackProc) quit_button_cb,
		       (XtPointer) NULL);

	play_button = XtVaCreateManagedWidget
	    ("playb", commandWidgetClass, button_box,
	     XtNfromHoriz, (XtArgVal) quit_button,
	     XtNlabel, (XtArgVal) "Play",
	     NULL);
	XtAddCallback (play_button, XtNcallback,
		       (XtCallbackProc) play_button_cb,
		       (XtPointer) NULL);

	itemlab = XtVaCreateManagedWidget
	    ("itemlab", labelWidgetClass, button_box,
	     XtNfromVert, (XtArgVal) quit_button,
	     XtNborderWidth, (XtArgVal) 0,
	     XtNresize, (XtArgVal) False,
	     XtNlabel, (XtArgVal) "items : ",
	     NULL);


	itemstaken_widget = XtVaCreateManagedWidget
	    ("itemstaken", labelWidgetClass, button_box,
	     XtNfromHoriz, (XtArgVal) itemlab,
	     XtNfromVert, (XtArgVal) quit_button,
	     XtNborderWidth, (XtArgVal) 0,
	     XtNresize, (XtArgVal) True,
	     XtNjustify, (XtArgVal) XtJustifyRight,
	     XtNlabel, (XtArgVal) "     ",
	     NULL);
	    
	liveslab = XtVaCreateManagedWidget
	    ("liveslab", labelWidgetClass, button_box,
	     XtNfromVert, (XtArgVal) itemlab,
	     XtNborderWidth, (XtArgVal) 0,
	     XtNresize, (XtArgVal) False,
	     XtNlabel, (XtArgVal) "lives : ",
	     NULL);


	lives_widget = XtVaCreateManagedWidget
	    ("itemstaken", labelWidgetClass, button_box,
	     XtNfromHoriz, (XtArgVal) liveslab,
	     XtNfromVert, (XtArgVal) itemlab,
	     XtNborderWidth, (XtArgVal) 0,
	     XtNresize, (XtArgVal) True,
	     XtNjustify, (XtArgVal) XtJustifyRight,
	     XtNlabel, (XtArgVal) "     ",
	     NULL);
	    
	{
	    int dir;
	    int descent;
	    XCharStruct overall;

	    XQueryTextExtents (display,
			       XLoadFont (display, "10x20"),
			       "00:00:00", 8, &dir,
			       &time_ascent, &descent, &overall);
	    time_widget_w = overall.width + 5;
	    time_ascent += 2;
	    time_widget_h = time_ascent+descent;

	    time_widget = (MyWidget) XtVaCreateManagedWidget
		("time", myWidgetClass, button_box,
		 XtNfromVert, (XtArgVal) liveslab,
		 XtNborderWidth, (XtArgVal) 0,
		 XtNwidth, (XtArgVal) time_widget_w,
		 XtNheight, (XtArgVal) time_widget_h,
		 NULL);
	}

	tripswitch_widget = XtVaCreateManagedWidget
	    ("tripswitch", labelWidgetClass, button_box,
	     XtNfromVert, (XtArgVal) time_widget,
	     XtNborderWidth, (XtArgVal) 0,
	     XtNjustify, (XtArgVal) XtJustifyLeft,
	     XtNresize, (XtArgVal) False,
	     XtNlabel, (XtArgVal) "               ",
	     NULL);

	discovery_widget = XtVaCreateManagedWidget
	    ("discovery", commandWidgetClass, button_box,
	     XtNfromVert, (XtArgVal) tripswitch_widget,
	     XtNborderWidth, (XtArgVal) 0,
	     XtNjustify, (XtArgVal) XtJustifyCenter,
	     XtNresize, (XtArgVal) False,
	     XtNlabel, (XtArgVal) "Enter Discovery Mode",
	     XtNsensitive, (XtArgVal) False,
	     NULL);
	XtAddCallback (discovery_widget, XtNcallback,
		       (XtCallbackProc) discovery_cb,
		       (XtPointer) NULL);

#if 0
	if (debugging) {
	    draw_button = XtVaCreateManagedWidget
		("playb", commandWidgetClass, button_box,
		 XtNfromHoriz, (XtArgVal) quit_button,
		 XtNlabel, (XtArgVal) "Draw",
		 NULL);
	    XtAddCallback (draw_button, XtNcallback,
			   (XtCallbackProc) draw_button_cb,
			   (XtPointer) NULL);
    
	    sl_form = XtVaCreateManagedWidget
		("slform", formWidgetClass, button_box,
		 XtNfromVert, (XtArgVal) discovery_widget,
		 XtNborderWidth, (XtArgVal) 0,
		 XtNinternalHeight, (XtArgVal) 0,
		 XtNinternalWidth, (XtArgVal) 0,
		 NULL);
	     
	    slider = XtVaCreateManagedWidget
		("slider", scrollbarWidgetClass, sl_form,
		 XtNorientation, (XtArgVal) XtorientHorizontal,
		 XtNhorizDistance, (XtArgVal) 0,
		 XtNlength, (XtArgVal) SLIDERSIZE+SLIDERTHUMB,
		 XtNminimumThumb, (XtArgVal) SLIDERTHUMB,
		 NULL);
	    XtAddCallback (slider, XtNscrollProc,
			   (XtCallbackProc) slider_scroll_cb,
			   NULL);
	    XtAddCallback (slider, XtNjumpProc,
			   (XtCallbackProc) slider_jump_cb,
			   NULL);
	    {	
		float top = 0.0;
		XawScrollbarSetThumb (slider, top, -1.0);
	    }

	    slider_val = XtVaCreateManagedWidget
		("slval", labelWidgetClass, sl_form,
		 XtNfromHoriz, (XtArgVal) slider,
		 XtNborderWidth, (XtArgVal) 0,
		 XtNresize, (XtArgVal) False,
		 XtNjustify, (XtArgVal) XtJustifyRight,
		 XtNlabel, (XtArgVal) "  0",
		 NULL);
	}
#endif /* 0 */
	
	canvas_widget = (MyWidget) XtVaCreateManagedWidget
	    ("game", myWidgetClass, form,
	     XtNwidth, (XtArgVal) 512,
	     XtNheight, (XtArgVal) 256,
	     XtNfromHoriz, (XtArgVal) button_box,
	     NULL);

	roomname_widget = XtVaCreateManagedWidget
	    ("roomname", labelWidgetClass, form,
	     XtNfromVert, (XtArgVal) canvas_widget,
	     XtNfromHoriz, (XtArgVal) button_box,
	     XtNborderWidth, (XtArgVal) 0,
	     XtNwidth, (XtArgVal) 512,
	     XtNjustify, (XtArgVal) XtJustifyCenter,
	     XtNresize, (XtArgVal) True,
	     XtNlabel, (XtArgVal) "  ",
	     NULL);

    }



    /*
     * Realize.
     */
    XtRealizeWidget (toplevel);
    canvas = canvas_widget->my.store;
    {
	XWindowAttributes watr;
	XGetWindowAttributes (display, XtWindow (canvas_widget), &watr);
	depth = watr.depth;
    }




    /*
     * Colormap.
     */
    {
	int type;
	unsigned long plane_masks[1];
#define NBTRY 256
	unsigned long tab[NBTRY];
	int top;
	int curnb;
	int i;

	type = 2;
	if (mono)
	    goto done;

	/*
	 * Try defaut colormap
	 */
	colormap = DefaultColormap (display, screen);
	top = -1;
	for (curnb=4; curnb<NBTRY; curnb++) {
	    if (XAllocColorCells (display, colormap, False,
				  plane_masks, 0, tab, curnb)) {
		top = find_top_in_tab (tab, curnb);
		if (top != -1)
		    break;
		XFreeColors (display, colormap, tab, curnb, 0);
	    }
	}
	if (top != -1) {		/* found colors in standard colormap */
	    if (curnb > 4) {				/* free extra colors */
		int j = 0;
		for (i=0; i<curnb; i++) {
		    if ((tab[i] & ~3) != top) {
			tab[j++] = tab[i];
		    }
		}
		XFreeColors (display, colormap, tab, curnb-4, 0);
	    }
	    type = 0; /* standard colormap */
	    goto done;
	}

	/*
	 * Try private colormap
	 */
	colormap = XCreateColormap
	    (display, DefaultRootWindow (display),
	     DefaultVisual (display, DefaultScreen(display)),
	     AllocNone);
	if (colormap == 0)
	    goto done;
	for (i=NBTRY; i>=16; i/=2) {
	    XColor cols[NBTRY];
	    if (XAllocColorCells (display, colormap, False,
				  plane_masks, 0, tab, i)) {
		int j;

		top = find_top_in_tab (tab, i);
		if (top == -1) {
		    XFreeColors (display, colormap, tab, i, 0);
		    break;
		}
		/* Copy all the colors we can */
		for (j=0; j<i; j++) {
		    cols[j].pixel = tab[j];
		    cols[j].flags = DoRed | DoGreen | DoBlue;
		}
		XQueryColors (display, DefaultColormap (display, screen),
			      cols, i);
		XStoreColors (display, colormap, cols, i);
		type = 1; /* private colormap */
		goto done;
	    }
	}
	XFreeColormap (display, colormap);
    done:

	switch (type) {
	case 0:
	case 1: {
	    XColor colorcells[NBCOLORS];
	    XColor col;

	    for (i=0; i<4; i++)
		pixels[i] = top | i;
	    for (i=0; i<NBCOLORS; i++) {
		col.pixel = pixels[i];
		col.flags = DoRed | DoGreen | DoBlue;
		col.red = col.green = col.blue = 0;
		colorcells[i] = col;
	    }
	    XStoreColors (display, colormap, colorcells, NBCOLORS);
	    XSetWindowColormap (display, XtWindow (canvas_widget), colormap);
	    color_display = 1;
	    private_colormap = (type == 1);
	    break;
	}
	default:
	    pixels[0] = BlackPixel (display, screen);
	    for (i=1; i<NBCOLORS; i++)
		pixels[i] = WhitePixel (display, screen);
	    color_display = 0;
	    private_colormap = 0;
	    break;
	}
    }



    /*
     * Get atoms, to react to WM_PROTOCOLS.
     */
    {
	Atom atomlist[2];

	Protocol_atom = XInternAtom (display, "WM_PROTOCOLS", False);
	Kill_atom = XInternAtom (display, "WM_DELETE_WINDOW", False);
	Save_atom = XInternAtom (display, "WM_SAVE_YOURSELF", False);
	atomlist[0] = Kill_atom;
	atomlist[1] = Save_atom;
	XSetWMProtocols (display, XtWindow (toplevel), atomlist, 2);
    }


    /*
     * Window name & size & icon.
     */
    {
	XWMHints wm_hints;
	XSizeHints size_hints;
	XClassHint class_hints;
	XTextProperty windowName;
	Pixmap icon_pixmap, icon_mask;
	Arg args[2];
	int n;

	icon_pixmap = XCreateBitmapFromData (display, XtWindow (toplevel),
					     icon_bits,
					     icon_width, icon_height);
	icon_mask = XCreateBitmapFromData (display, XtWindow (toplevel),
					   icon_mask_bits,
					   icon_mask_width, icon_mask_height);
	XStringListToTextProperty (&myname, 1, &windowName);
	n = 0;
	XtSetArg (args[n], XtNwidth, NULL); n++;
	XtSetArg (args[n], XtNheight, NULL); n++;
	/*XtGetValues (toplevel, args, n);*/
	size_hints.width = args[0].value;
	size_hints.height = args[1].value;
	size_hints.min_width = size_hints.width;
	size_hints.max_width = size_hints.width;
	size_hints.min_height = size_hints.height;
	size_hints.max_height = size_hints.height;
	size_hints.flags = PSize | PMinSize | PMaxSize;
	wm_hints.initial_state = NormalState;
	wm_hints.input = True;
	wm_hints.icon_pixmap = icon_pixmap;
	wm_hints.icon_mask = icon_mask;
	wm_hints.flags = StateHint | InputHint | IconPixmapHint | IconMaskHint;
	class_hints.res_name = myname;
	class_hints.res_class = app_class;
	XSetWMProperties (display, XtWindow (toplevel),
			  &windowName, &windowName,
			  argv, argc,
			  &size_hints, &wm_hints, &class_hints);
    }


    /*
     * Initialize GCs
     */
    {
	XGCValues gcv;
	int i;

	gcv.graphics_exposures = 0;
	gcv.foreground = BlackPixel (display, screen);
	gc_black = XCreateGC (display, canvas,
			      GCForeground | GCGraphicsExposures, &gcv);

	gcv.foreground = WhitePixel (display, screen);
	gc_white = XCreateGC (display, canvas,
			      GCForeground | GCGraphicsExposures, &gcv);

	gc_copyplane = XCreateGC (display, canvas,
				  GCGraphicsExposures, &gcv);

	gcv.function = GXxor;
	gcv.background = 0;
	for (i=0; i<4; i++) {
	    gcv.foreground = pixels[i] ^ pixels[0];
	    gc_xorplane[i] = XCreateGC (display, canvas,
					GCFunction | GCGraphicsExposures
					| GCForeground | GCBackground, &gcv);
	}

	gc_xorchar = XCreateGC (display, canvas,
				GCFunction | GCGraphicsExposures
				| GCForeground | GCBackground, &gcv);

	gcv.background = pixels[0];
	for (i=0; i<NBCOLORS; i++) {
	    gcv.foreground = pixels[i];
	    gcs[i] = XCreateGC (display, canvas,
				GCForeground | GCBackground
				| GCGraphicsExposures, &gcv);
	}

	gcv.background = 0;
	gcv.function = GXand;
	for (i=0; i<4; i++) {
	    gcv.foreground = i;
	    gc_and[i] = XCreateGC (display, canvas,
				   GCForeground | GCBackground
				   | GCFunction
				   | GCGraphicsExposures, &gcv);
	}

	gcv.background = 0;
	gcv.foreground = 0;
	gc_zero = XCreateGC (display, canvas,
			     GCForeground | GCBackground
			     | GCGraphicsExposures, &gcv);

	gcv.background = 0;
	gcv.foreground = 3;
	gc_three = XCreateGC (display, canvas,
			      GCForeground | GCBackground
			      | GCGraphicsExposures, &gcv);

	gcv.foreground = pixels[1];
	gcv.background = pixels[0];
	gcv.dashes = 8;
	gcv.line_width = 0;
	gcv.line_style = LineDoubleDash;
	for (i=0; i<4; i++) {
	    char dash_list[1] = {8};
	    gcv.dash_offset = 4*i;
	    gc_dash[i] = XCreateGC (display, canvas,
				    GCGraphicsExposures
				    | GCLineWidth | GCLineStyle
				    | GCForeground | GCBackground
				    | GCDashList | GCDashOffset,
				    &gcv);
	    /* redo this, because of buggy Sun XNeWS server... */
	    XSetDashes (display, gc_dash[i], gcv.dash_offset,
			dash_list, 1);
	}

	gcv.foreground = pixels[3];
	gcv.background = pixels[0];
	gcv.font = XLoadFont (display, "10x20");
	gc_string = XCreateGC (display, canvas,
			       GCForeground | GCBackground
			       | GCGraphicsExposures | GCFont, &gcv);

	gcv.foreground = BlackPixel (display, screen);
	gcv.background = WhitePixel (display, screen);
	gcv.font = XLoadFont (display, "10x20");
	gc_time = XCreateGC (display, canvas,
			     GCForeground | GCBackground
			     | GCGraphicsExposures | GCFont, &gcv);

	gcv.foreground = WhitePixel (display, screen);
	gcv.background = BlackPixel (display, screen);
	gcv.font = XLoadFont (display, "10x20");
	gc_scroll = XCreateGC (display, canvas,
			       GCForeground | GCBackground
			       | GCGraphicsExposures | GCFont, &gcv);

	{
	    XFontStruct *overall;

	    overall = XLoadQueryFont(display, "10x20");
	    scroll_ascent = overall->max_bounds.ascent;
	    scroll_w = overall->max_bounds.rbearing
		- overall->min_bounds.lbearing;
	    scroll_h = overall->max_bounds.ascent
		+ overall->max_bounds.descent;
	}

	gcv.foreground = WhitePixel (display, screen);
	gc_time_white = XCreateGC (display, canvas,
				   GCForeground | GCGraphicsExposures, &gcv);


    }


#ifdef NO_RAND48
    srand (time (NULL));
#else
    srand48 (time (NULL));
#endif
    draw_init ();

    global_state = STATE_PRESENT;
    {
	extern void emergency (int);
	extern int errorhandler ();
	extern int ioerrorhandler ();
	signal (SIGHUP, emergency);
	signal (SIGINT, emergency);
	signal (SIGQUIT, emergency);
	signal (SIGILL, emergency);
	signal (SIGABRT, emergency);
	signal (SIGFPE, emergency);
	signal (SIGBUS, emergency);
	signal (SIGSEGV, emergency);
	signal (SIGSYS, emergency);
	signal (SIGPIPE, emergency);
	signal (SIGALRM, emergency);
	signal (SIGTERM, emergency);
	signal (SIGXCPU, emergency);
	signal (SIGXFSZ, emergency);
	signal (SIGVTALRM, emergency);
	signal (SIGLOST, emergency);
	XSetErrorHandler (errorhandler);
	XSetIOErrorHandler (ioerrorhandler);
    }


    load_game ();

    set_timeout ();
    if (global_state == STATE_PRESENT)
	present ();


    while (1) {
	XEvent event;

	XtAppNextEvent (application_context, &event);
	if (   event.type == ClientMessage
	    && event.xclient.message_type == Protocol_atom
	    && (   event.xclient.data.l[0] == Kill_atom
		|| event.xclient.data.l[0] == Save_atom)) {
	    emergency (0);
	}
	XtDispatchEvent (&event);
    }
}
