

/*  Copyright (c) 1993 by Kenneth Duda <kkkken@athena.mit.edu>.  
 *  All rights reserved.
 *
 *  This program is distributed in the hope that it will be useful.
 *  Use and copying of this software and preparation of derivative works
 *  based upon this software are permitted, so long as the following
 *  conditions are met:
 *       o credit to the author is acknowledged 
 *       o no fees or compensation are charged for use, copies, or
 *         access to this software
 *       o this copyright notice is included intact.
 *  This software is made available AS IS, and no warranty is made about 
 *  the software or its performance. 
 * 
 *  Bug descriptions, use reports, comments or suggestions are welcome.
 *  Send them to <kkkken@athena.mit.edu>.
 */

#include <ctype.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/MenuButton.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/SmeLine.h>
#include "Goban.h"
#include "AsciiTty.h"
#include "games.h"
#include "kgo.h"
#include "output.h"
#include "patchlevel.h"

#define KIBITZ_WIDTH 580
#define KIBITZ_HEIGHT 150
#define GOBAN_WIDTH 380
#define GOBAN_HEIGHT 380

typedef struct {			/* One of these per goban in its client data */
    Widget popup, goban, form;
    Widget label, gamesButton, kibitz;
    Widget whiteClock, blackClock;	/* just labels */
    Widget whiteCaptures, blackCaptures;
    Widget handicapMenuButton;
    struct game *game;			/* NULL if this window isn't observing or playing */
} Wrapper;


typedef struct game {
    int number;				
    int active;				/* Does this game number exist on the server? */
    char white[MAXUSERNAME];
    char black[MAXUSERNAME];
    char whiteRank[MAXRANK];
    char blackRank[MAXRANK];
    int move;
    int bsize;
    int hcap;
    int komi;				/* without the .5! */
    int numObserving;			/* ### */
    char whiteTimeStr[20];
    char blackTimeStr[20];
    int whiteTime;			
    int blackTime;
    int whiteByoStones;
    int blackByoStones;
    Wrapper *wrapper;			/* NULL if not being displayed */
    long last_resync;			/* Time when we last asked for a full list */
} Game;

Game games[MAXGAMES];
int gamesDirty;
Game *lastGameIndicated = NULL;
int numPopups = 0;

Widget gamesMenu = NULL;
Wrapper *gamesChosenFrom;
Wrapper *unGame;




static void make_popup_label (g, buf)
     Game *g;
     char *buf;
{
    sprintf (buf, "Game %d: %s [%s] vs %s [%s]",
	     g->number, g->white, g->whiteRank, g->black, g->blackRank);
}

Wrapper *GetGobanWrapper(goban)
     Widget goban;
{
    static Wrapper *ret;
    static Arg args[] = { XtNclientData, (XtArgVal) &ret };
    XtGetValues (goban, args, XtNumber(args));
    return ret;
}

Wrapper *GetPopupWrapper (popup)
     Widget popup;
{
    return GetGobanWrapper (XtNameToWidget(popup, "gobanForm.goban"));
}


void GamesButtonNotify (w, closure, call_data)
     Widget w;
     XtPointer closure;
     XtPointer call_data;
{
    Widget goban = (Widget) closure;
    gamesChosenFrom = GetGobanWrapper(goban);
}


void FirstNotify (w, closure, call_data)
     Widget w;
     XtPointer closure;
     XtPointer call_data;
{
    Widget goban = (Widget) closure;
    XGobanFirst(goban);
}

void BackwardNotify (w, closure, call_data)
     Widget w;
     XtPointer closure;
     XtPointer call_data;
{
    Widget goban = (Widget) closure;
    XGobanBackward(goban);
}

void CurrentNotify (w, closure, call_data)
     Widget w;
     XtPointer closure;
     XtPointer call_data;
{
    Widget goban = (Widget) closure;
    XGobanCurrent(goban);
}

void ForwardNotify (w, closure, call_data)
     Widget w;
     XtPointer closure;
     XtPointer call_data;
{
    Widget goban = (Widget) closure;
    XGobanForward(goban);
}

void LastNotify (w, closure, call_data)
     Widget w;
     XtPointer closure;
     XtPointer call_data;
{
    Widget goban = (Widget) closure;
    XGobanLast(goban);
}

static void UnobserveGame (g)
     Game *g;
{
    char buf[40];
    if (g->wrapper) {			/* Only if game is being observed */
	if (!unGame)
	    unGame = g->wrapper;
	g->wrapper->game = NULL;
	g->wrapper = NULL;
	sprintf (buf, "observe %d\n", g->number);
	QueueForServer(buf);
    }
}

void PopdownGoban (w, closure, call_data)
     Widget w;
     XtPointer closure;
     XtPointer call_data;
{
    Wrapper *wr = GetPopupWrapper(w);
    CancelOutputSink (wr->kibitz);
    if (wr->game)
	UnobserveGame(wr->game);
    if (unGame == wr)			/* Did we just nuke the ungame? */
	unGame = NULL;
    free (wr);
    if (!--numPopups)			/* Exit when no more windows left */
	exit(0);
}

Widget FindGameKibitz()
{
    Game *g = games;
    int n;
    
    if (unGame) return unGame->kibitz;
    for (n = MAXGAMES; n; n--, g++) 
	if (g->wrapper)
	    return g->wrapper->kibitz;
    return NULL;
}

	    
	    

void SetWrapperLabel (wr, buf)
     Wrapper *wr;
     char *buf;
{
    static Arg arg = { XtNlabel, 0 };
    arg.value = (XtArgVal) buf;
    XtSetValues (wr->label, &arg, 1);
}

void SetWrapperSize (wr, size)
     Wrapper *wr;
     int size;
{
    static Arg arg = { XtNboardSize, 0 };
    arg.value = (XtArgVal) size;
    XtSetValues (wr->goban, &arg, 1);
}

static void ObserveNothing (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Wrapper *wr = (Wrapper *) closure;
    if (wr->game) {
	UnobserveGame (wr->game);
	XGobanResetGame (wr->goban, GobanScrewingAround);
	SetWrapperLabel (wr, "ggggo for it!");
    }
}

static void DoListUsers (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    QueueForServer("who\n");
}

static void GetGameList (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    QueueForServer("games\n");
}

static void RefreshGame (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Wrapper *wr = (Wrapper *) closure;
    static int mode;
    static Arg args[] = { XtNmode, (XtArgVal) &mode };
    char buf[30];
    
    if (!wr->game) return;
    XtGetValues (wr->goban, args, XtNumber(args));
    XGobanResetGame (wr->goban, mode);
    sprintf (buf, "moves %d\n", wr->game->number);
    wr->game->last_resync = time(0);	/* maybe avoid a race condition */
    QueueForServer(buf);
}

static void DoNew (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Wrapper *wr = (Wrapper *) closure;
    XGobanNew (wr->goban);
}

static void DoDone (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Wrapper *wr = (Wrapper *) closure;
    XGobanDone (wr->goban);
}

static void DoClear (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Wrapper *wr = (Wrapper *) closure;
    XGobanClear (wr->goban);
}

static void DoQuit (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Wrapper *wr = (Wrapper *) closure;
    XGobanQuit (wr->goban);
}

static void DoPass (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Wrapper *wr = (Wrapper *) closure;
    XGobanPass (wr->goban);
}

static void DoUndo (gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Wrapper *wr = (Wrapper *) closure;
    XGobanUndo (wr->goban);
}

typedef struct handicapClosure {
    int handicap;
    Wrapper *wr;
} HandicapClosure;


static void ChooseHandicap(gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    HandicapClosure *hc = (HandicapClosure *) closure;
    XGobanHandicap (hc->wr->goban, hc->handicap);
}


Wrapper *PopupGoban(g, name, bsize)
     Game *g;
     char *name;
     int bsize;
{
    Wrapper *wr = (Wrapper *) malloc(sizeof(Wrapper));
    Widget button;
    char buf[130];
    XtTranslations trans = XtParseTranslationTable ("\
               	<EnterWindow>:	highlight()\n\
                <LeaveWindow>:	reset()\n\
 	        <BtnDown>:	reset() set() notify() reset() PopupMenu()\n");
#   define buttonWidth 150
    
    if (g) {
	bsize = g->bsize;		/* Ignore bogus sizes */
	if (!name) {
	    make_popup_label (g, buf);
	    name = buf;
	}
    } else {
	if (!unGame)
	    unGame = wr;
    }
    wr->popup = XtCreatePopupShell("gobanShell", topLevelShellWidgetClass, toplevel, 0, 0);
    XtAddCallback (wr->popup, XtNdestroyCallback, PopdownGoban, NULL);

    wr->form = XtCreateManagedWidget("gobanForm", formWidgetClass, wr->popup, 0, 0);
		   
    {
	static Arg args[] = { XtNshell, 0,
				  XtNboardSize, 0,
				  XtNleft, XawChainLeft,
				  XtNright, XawChainRight,
				  XtNtop, XawChainTop,
				  XtNbottom, XawChainBottom,
				  XtNwidth, GOBAN_WIDTH,
				  XtNheight, GOBAN_HEIGHT,
				  XtNclientData, 0,
		       };
	args[0].value = (XtArgVal) wr->popup;
	args[1].value = bsize;
	args[8].value = (XtArgVal) wr;
	wr->goban = XtCreateManagedWidget("goban", gobanWidgetClass, wr->form, args, XtNumber(args));
    }
    {
	static Arg args[] = { XtNlabel, (XtArgVal) "Games",
				  XtNleft, XawChainRight,
				  XtNright, XawChainRight,
				  XtNfromHoriz, 0,
				  XtNtop, XawChainTop,
				  XtNbottom, XawChainTop,
				  XtNmenuName, (XtArgVal) "games",
				  XtNtranslations, 0,
				  XtNwidth, buttonWidth,
			      };
	args[3].value = (XtArgVal) wr->goban;
	args[7].value =  (XtArgVal) trans;
	wr->gamesButton = XtCreateManagedWidget ("gamesMenuB", menuButtonWidgetClass, wr->form, args, XtNumber(args));
	XtAddCallback (wr->gamesButton, XtNcallback, GamesButtonNotify, wr->goban);
    }
    {
	static Arg args[] = { XtNlabel, 0,
				  XtNleft, XawChainLeft,
				  XtNright, XawChainRight, 
				  XtNtop, XawChainBottom, 
				  XtNbottom, XawChainBottom, 
				  XtNfromVert, 0,
				  XtNwidth, KIBITZ_WIDTH, 
			      };
	args[0].value  = (XtArgVal) name;
	args[5].value = (XtArgVal) wr->goban;
	wr->label = XtCreateManagedWidget ("gamesLabel", labelWidgetClass, wr->form, args, XtNumber(args));
    }
    {
	static  Arg args[] = { XtNlabel, 0,
				   XtNleft, XawChainRight,
				   XtNright, XawChainRight,
				   XtNfromVert, 0,
				   XtNtop, XawChainTop,
				   XtNbottom, XawChainTop,
				   XtNfromHoriz, 0,
				   XtNwidth, buttonWidth,
			       };
	struct button {
	    char *text;
	    void (*function)();
	} *m;
	int n;
	static struct button buttons[] = {
	    "Get game list", GetGameList,
	    "List users", DoListUsers,
	    "Observe nothing", ObserveNothing,
	    "Refresh game", RefreshGame,
	    "Undo last move", DoUndo,
	    "Pass", DoPass,
	    "Done removing", DoDone,
	    "New Goban", DoNew,
	    "Clear Goban", DoClear,
	    "Quit Goban", DoQuit,
	};
	args[3].value = (XtArgVal) wr->gamesButton;
	args[6].value = (XtArgVal) wr->goban;
	for (n = 0, m = buttons; n < XtNumber(buttons); n++, m++) {
	    button = XtCreateManagedWidget (m->text, commandWidgetClass, wr->form, args, XtNumber(args));
	    args[3].value = (XtArgVal) button;
	    XtAddCallback (button, XtNcallback, m->function, wr);
	}
    }
    {
	static Arg args[] = { XtNleft, XawChainRight,
				  XtNright, XawChainRight, 
				  XtNtop, XawChainTop, 
				  XtNbottom, XawChainTop, 
				  XtNfromVert, 0,
				  XtNfromHoriz, 0,
				  XtNlabel, (XtArgVal) "Set Handicap",
				  XtNmenuName, (XtArgVal) "handicap",
				  XtNwidth, buttonWidth,
			      };
	static XtCallbackRec cb[2] = { { ChooseHandicap, NULL } , { NULL, NULL } };
	static Arg args2[] = { XtNcallback, (XtArgVal) cb };
	static Widget handicapMenu = NULL;
	static HandicapClosure hc[8];
	char buf[15];
	int n;
	args[4].value = (XtArgVal) button;
	args[5].value = (XtArgVal) wr->goban;
	wr->handicapMenuButton = XtCreateManagedWidget("handicapMenuButton", menuButtonWidgetClass,
						       wr->form, args, XtNumber(args));
	if (!handicapMenu) {
	    handicapMenu = XtCreatePopupShell ("handicap", simpleMenuWidgetClass, toplevel, NULL, 0);
	    for (n = 2; n < 10; n++) {
		sprintf (buf, "Handicap %d", n);
		hc[n-2].handicap = n;
		hc[n-2].wr = wr;
		cb[0].closure = (caddr_t) (hc + n - 2);
		XtCreateManagedWidget (buf, smeBSBObjectClass, handicapMenu, args2, XtNumber(args2));
	    }
	}
    }    
    {
	static Pixmap backwardBitmap = NULL;
	static Pixmap currentBitmap = NULL;
	static Pixmap forwardBitmap = NULL;
	static  Arg args[] = { XtNbitmap, 0,
				   XtNleft, XawChainRight,
				   XtNright, XawChainRight, 
				   XtNtop, XawChainTop, 
				   XtNbottom, XawChainTop, 
				   XtNfromVert, 0,  
				   XtNfromHoriz, 0, 
			       };
#       include "first.xbm"
#       include "backward.xbm"
#       include "current.xbm"
#       include "forward.xbm"
#       include "last.xbm"
	static struct but {
	    char *name;
	    XtCallbackProc proc;
	    Pixmap bmap;
	    char *bits;
	    int width, height;
	} buts[] = {
	    "first", FirstNotify, 0, first_bits, first_width, first_height,
	    "backward", BackwardNotify, 0, backward_bits, backward_width, backward_height,
	    "current", CurrentNotify, 0, current_bits, current_width, current_height,
	    "forward", ForwardNotify, 0, forward_bits, forward_width, forward_height,
	    "last", LastNotify, 0, last_bits, last_width, last_height,
	};
	int n;
	struct but *b;
	
	args[5].value = (XtArgVal) wr->handicapMenuButton;
	args[6].value = (XtArgVal) wr->goban;
	if (!buts[0].bmap) {
	    Display *dpy = XtDisplay(wr->popup);
	    Window win = RootWindow (dpy, DefaultScreen(dpy)); /* Guess. */
	    for (n = XtNumber(buts), b = buts; n; n--, b++) {
		b->bmap = XCreateBitmapFromData (dpy, win, b->bits, b->width, b->height);
	    }
	}
	for (n = XtNumber(buts), b = buts; n; n--, b++) {
	    args[0].value = (XtArgVal) b->bmap;
	    button = XtCreateManagedWidget (name, commandWidgetClass, wr->form, args, XtNumber(args));
	    XtAddCallback (button, XtNcallback, b->proc, wr->goban);
	    args[6].value = (XtArgVal) button;
	}
    }
	
    {
	static Arg args0[] = { XtNleft, XawChainRight,
				   XtNright, XawChainRight, 
				   XtNtop, XawChainTop, 
				   XtNbottom, XawChainTop, 
				   XtNfromVert, 0,
				   XtNfromHoriz, 0,
				   XtNborderWidth, 0,
				   XtNlabel, 0,
			       };
	static Arg args1[] = { XtNleft, XawChainRight,
				   XtNright, XawChainRight, 
				   XtNtop, XawChainTop, 
				   XtNbottom, XawChainTop, 
				   XtNfromVert, 0,
				   XtNfromHoriz, 0,
				   XtNlabel, (XtArgVal) "99:99 / 99",
			};
	static   Arg args2 [] = { XtNleft, XawChainRight,
				      XtNright, XawChainRight, 
				      XtNtop, XawChainTop, 
				      XtNbottom, XawChainTop, 
				      XtNfromVert, 0,
				      XtNfromHoriz, 0,
				      XtNlabel, (XtArgVal) " 0",
				  };
	Widget label;

	args0[4].value = (XtArgVal) button;
	args1[4].value = (XtArgVal) button;
	args2[4].value = (XtArgVal) button;
	args0[5].value = (XtArgVal) wr->goban;
	args0[7].value = (XtArgVal) "White:";
	label = XtCreateManagedWidget ("whiteLabel", labelWidgetClass, wr->form, args0, XtNumber(args0));
	args1[5].value = (XtArgVal) label;
	wr->whiteClock = XtCreateManagedWidget ("whiteClock", labelWidgetClass, wr->form, args1, XtNumber(args1));
	args2[5].value = (XtArgVal) wr->whiteClock;
	wr->whiteCaptures = XtCreateManagedWidget ("whiteCaptures", labelWidgetClass, wr->form, args2, XtNumber(args2));
	args0[4].value = (XtArgVal) label;
	args1[4].value = (XtArgVal) label;
	args2[4].value = (XtArgVal) label;
	args0[7].value = (XtArgVal) "Black:";
	label = XtCreateManagedWidget ("blackLabel", labelWidgetClass, wr->form, args0, XtNumber(args0));
	args1[5].value = (XtArgVal) label;
	wr->blackClock = XtCreateManagedWidget ("blackClock", labelWidgetClass, wr->form, args1, XtNumber(args1));
	args2[5].value = (XtArgVal) wr->blackClock;
	wr->blackCaptures = XtCreateManagedWidget ("blackCaptures", labelWidgetClass, wr->form, args2, XtNumber(args2));
    }
    {
	static Arg args[] = { XtNwhiteCapturesLabel, 0,
				  XtNblackCapturesLabel, 0,
			      };
	args[0].value = (XtArgVal) wr->whiteCaptures;
	args[1].value = (XtArgVal) wr->blackCaptures;
	XtSetValues (wr->goban, args, XtNumber(args));
    }
    {
	static  Arg args[] = { XtNheight, KIBITZ_HEIGHT,
				   XtNleft, XawChainLeft,
				   XtNright, XawChainRight,
				   XtNtop, XawChainBottom,
				   XtNbottom, XawChainBottom, 
				   XtNfromVert, 0,
				   XtNscrollVertical, XawtextScrollAlways,
				   XtNscrollHorizontal, XawtextScrollWhenNeeded,
				   XtNwidth, KIBITZ_WIDTH, 
			       };
	args[5].value = (XtArgVal) wr->label;
	wr->kibitz = XtCreateManagedWidget ("kibitz", asciiTtyWidgetClass, wr->form, args, XtNumber(args));
    }
			   
    wr->game = g;
    if (g) g->wrapper = wr;
    XtRealizeWidget (wr->popup);
    XtPopup (wr->popup, XtGrabNone);
    SetOutputSink (wr->kibitz);
    numPopups++;
    return wr;
}

     
void AssignWrapperGame (wr, g)
     Wrapper *wr;
     Game *g;
{
    char buf[130];

    if (wr->game == g) return;
    if (wr == unGame) unGame = NULL;
    make_popup_label (g, buf);
    XGobanResetGame (wr->goban, GobanObserving);
    SetWrapperLabel (wr, buf);
    SetWrapperSize (wr, g->bsize);
    wr->game = g;
    g->wrapper = wr;
}

void ObserveGame (g, wr)
     Game *g;
     Wrapper *wr;
{
    char buf[30];

    if (g->wrapper) return;		/* Already being observed */
    if (wr->game) 
	UnobserveGame (wr->game);
    AssignWrapperGame (wr, g);
    sprintf (buf, "observe %d\nrefresh %d\n", g->number, g->number);
    QueueForServer(buf);
}


void PopupGame (g)		/* Somehow the server thinks we're involved */
     Game *g;
{					/* we must have been observing it and forgot!  ;-) */
    if (unGame) {
	AssignWrapperGame (unGame, g);
    } else {
	PopupGoban (g, NULL, 0);
    }
}


void PopupUnGame ()
{
    char buf[30];

    sprintf (buf, "Welcome to kgo %s", PATCHLEVEL);
    PopupGoban (NULL, buf, 19);
}

static void MaybeDrawClock (clock, time, byoStones, str)
     Widget clock;
     int time;
     int byoStones;
     char *str;
{
    static char buf[20];

    if (byoStones >= 0)
	sprintf (buf, "%2d:%02d / %2d", time / 60, time % 60, byoStones);
    else
	sprintf (buf, "%2d:%02d", time / 60, time % 60);
    if (strcmp(buf, str)) {
	static Arg args[] = { XtNlabel, (XtArgVal) buf };
	strcpy (str, buf);
	XtSetValues (clock, args, XtNumber(args));
    }
}

static void DrawClocks (wr)
     Wrapper *wr;
{
    Game *g = wr->game;
    MaybeDrawClock (wr->whiteClock, g->whiteTime, g->whiteByoStones, g->whiteTimeStr);
    MaybeDrawClock (wr->blackClock, g->blackTime, g->blackByoStones, g->blackTimeStr);
}


void BumpAllClocks (closure, id)
     XtPointer closure;
     XtIntervalId *id;
{
    int n;
    Game *g = games;

    XtAppAddTimeOut (appcon, 1000, BumpAllClocks, NULL);
    for (n = MAXGAMES; n; n--, g++) {
	if (g->wrapper) {
	    int *clock = (g->move & 1 ? &g->whiteTime : &g->blackTime);
	    if (*clock) {
		(*clock)--;		
		DrawClocks (g->wrapper); /* lazy */
	    }
	}
    }
}
	    



static void ChooseGame(gw, closure, call_data)
     Widget gw;
     caddr_t closure;
     caddr_t call_data;
{
    Game *g = (Game *) closure;
    
    if (g->wrapper) {		/* Already being displayed; help user find it */
	Widget popup = g->wrapper->popup;
	Display *dpy = XtDisplay (popup);
	Window win = XtWindow (popup);
	XRaiseWindow (dpy, win);
	XWarpPointer (dpy, 0, win, 0, 0, 0, 0, 175, 220);
    } else {
	ObserveGame (g, gamesChosenFrom);
    }
}


void CleanupGames ()
{
    static XtCallbackRec cb[2] = { { ChooseGame, NULL } , { NULL, NULL } };
    static Arg arg = { XtNcallback, (XtArgVal) cb } ;
    static Arg insense = { XtNsensitive, (XtArgVal) 0 }; 
    int n;
    Game *g;

    if (!gamesDirty) return;
    for (n = MAXGAMES, g = games; n; n--, g++) {
	if (g->active == 2) {
	    g->active = 0;
	    if (g->wrapper) {
		SetWrapperLabel (g->wrapper, "Game Disappeared");
		g->wrapper->game = NULL;
		g->wrapper = NULL;
	    }
	}
    }
    gamesDirty = 0;
    if (gamesMenu) XtDestroyWidget(gamesMenu);
    gamesMenu = XtCreatePopupShell ("games", simpleMenuWidgetClass, toplevel, NULL, 0);
    XtCreateManagedWidget ("Num   White            Black         Move Sz H Komi ###",
			   smeBSBObjectClass, gamesMenu, &insense, 1);
    XtCreateManagedWidget ("line1", smeLineObjectClass, gamesMenu, 0, 0);
    for (n = MAXGAMES, g = games; n; n--, g++) {
	if (g->active) {
	    char buf[100];
	    cb[0].closure = (caddr_t) g;
	    sprintf (buf, "%2d %10s %4s   %10s %4s  %3d %2d %d %2d.5 %3d",
		     g->number, g->white, g->whiteRank, g->black, g->blackRank,
		     g->move, g->bsize, g->hcap, g->komi, g->numObserving);
	    XtCreateManagedWidget (buf, smeBSBObjectClass, gamesMenu, &arg, 1);
	}
    }
}

void InstallTimer()
{
    XtAppAddTimeOut (appcon, 1000, BumpAllClocks, NULL);
}

void ClearGames()
{
    int n;
    for (n = 0; n < MAXGAMES; n++) {
	games[n].number = n;
	games[n].active = 0;
	games[n].wrapper = NULL;
    }
    gamesDirty = 1;
    CleanupGames();
    QueueForServer ("games\n");
}

void SetGameInfo (n, wname, wrank, bname, brank, move, size, hcap, komi, obs)
     int n;
     char *wname;
     char *wrank;
     char *bname;
     char *brank;
     int move;
     int size;
     int hcap;
     int komi;
     int obs;
{
    Game *g;
    int i;
    int oldsize;
    
    if (!gamesDirty) {
	for (i = MAXGAMES, g = games; i; i--, g++) {
	    if (g->active == 1)
		g->active = 2;		/* On trial */
	}
    }
    g = games + n;
    oldsize = g->bsize;
    g->active = 1;
    strcpy (g->white, wname);
    strcpy (g->black, bname);
    strcpy (g->whiteRank, wrank);
    strcpy (g->blackRank, brank);
    g->move = move;
    g->bsize = size;
    g->hcap = hcap;
    g->komi = komi;
    g->numObserving = obs;
    g->last_resync = 0;
    if (g->wrapper) {
	char buf[130];
	make_popup_label (g, buf);
	SetWrapperLabel (g->wrapper, buf);
	if (oldsize != size) {
	    static Arg arg = { XtNboardSize, 0 };
	    arg.value = size;
	    XtSetValues (g->wrapper->goban, &arg, 1);
	}
    }
    gamesDirty = 1;
}

void ReceivedUndo ()
{
    if (XGobanReceivedUndo(lastGameIndicated->wrapper->goban))
	lastGameIndicated->move--;
}

	

void ReceivedMove (game, move, row, col)
     int game;
     int move;
     int row;
     int col;
{
    Game *g = games + game;
    int ok;
    Wrapper *wr;

    if (!g->wrapper) {			/* we got a move for a game we're not displaying */
	PopupGame (g);
    }
    wr = g->wrapper;
    if (row == -1) {
	ok = XGobanReceivedHandicap (wr->goban, move, col);
    } else if (row == -2) {
	ok = XGobanReceivedPass (wr->goban, move);
    } else {
	ok = XGobanReceivedMove (wr->goban, move, col - 1, g->bsize - row);
    }
    if (ok) {
	g->move = move + 1;
    } else {
	char buf[30];
	long now = time(0);
	if (g->last_resync + 20 < now) {
	    g->last_resync = now;
	    XGobanResetGame (g->wrapper->goban, GobanObserving);
	    sprintf (buf, "moves %d\n", game);
	    QueueForServer(buf);
	}
    }
}

void ReceivedRemove (game, row, col)
     int game;
     int row;
     int col;
{
    Game *g = games + game;
    int ok;
    Wrapper *wr;

    if (!g->wrapper) {			/* we got a move for a game we're not displaying */
	PopupGame (g);
    }
    wr = g->wrapper;
    ok = XGobanReceivedRemove (wr->goban, col - 1, g->bsize - row);
    if (!ok) {
					/* There's not much we can do.  most commands don't work here */
    }
}

void ReceivedGameIndicator (game, wname, bname, wtime, btime, wstones, bstones)
     int game;
     char *wname;
     char *bname;
     int wtime;
     int btime;
     int wstones;
     int bstones;
{
    Game *g;
    g = games + game;
    lastGameIndicated = g;
    if (!g->active) {
					/* We got a move for a game we don't know about */
					/* we have to ignore it for now */
					/* we don't know the board size */
	strcpy (g->white, wname);
	strcpy (g->black, bname);
	g->bsize = 19;			/* but let's guess it's 19 for now */
	QueueForServer ("games\n");
	return;
    }
    if (!g->wrapper) {			/* we got a move for a game we're not displaying */
	PopupGame (g);
    }
    g->whiteTime = wtime;
    g->blackTime = btime;
    g->whiteByoStones = wstones;
    g->blackByoStones = bstones;
    DrawClocks (g->wrapper);
}

void ScoredBoardReceived (h, line)
     int h;
     char *line;
{
    Game *g = lastGameIndicated;
    static int table[] = { BlackPiece, WhitePiece, NoPiece, NoPiece, WhiteTerritory, BlackTerritory,
			   MarkerPiece, MarkerPiece, MarkerPiece };
    int v;

    h = h - 1;
    for (v = 0; isdigit(*line); v++, line++) {
	XGobanSetPiece (g->wrapper->goban, h, v, table[*line - '0']);
    }
			
}
		     

void InsertKibitzString (wr, text)
     Wrapper *wr;
     char *text;
{
    OutputToSink (wr->kibitz, text);
}
    

void ReceivedKibitz (game, kname, krank, text)
     int game;
     char *kname;
     char *krank;
     char *text;
{
    Game *g = games + game;
    char buf[100];
    
    if (!g->wrapper)
	PopupGame(g);
    sprintf (buf, "%s [%s]: ", kname, krank);
    InsertKibitzString (g->wrapper, buf);
    InsertKibitzString (g->wrapper, text);
}

int BeginPlaying ()
{
    Game *g = games;
    int n = MAXGAMES;
    static Arg args[] = { XtNmode, GobanPlaying };
	
    QueueForServer("games\n");		/* Get games list to find out size */
    if (!lastGameIndicated) {
	QueueForServer("refresh\n");	/* Figure out what game we're playing */
	return 0;			/* Maybe client has been off */
    }
    if (!lastGameIndicated->wrapper)
	PopupGame(lastGameIndicated);
    while (n--) {
	if (g != lastGameIndicated &&
	    g->wrapper) {
	    XtDestroyWidget(g->wrapper->popup);	/* Blam!   nuke games under observation */
	}
	g++;
    }
    XtSetValues (lastGameIndicated->wrapper->goban, args, XtNumber(args));
    return 1;
}

void EndPlaying()
{
    static Arg args[] = { XtNmode, GobanObserving };
    
    XtSetValues (lastGameIndicated->wrapper->goban, args, XtNumber(args));
}

