

/*  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 <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/keysym.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <ctype.h>

#include "games.h"
#include "users.h"
#include "server.h"
#include "kgo.h"
#include "output.h"

int server_sock;
int waitingAtPrompt = 0;

static char colNames[] = "ABCDEFGHJKLMNOPQRST";
static Boolean inFile = FALSE;
static Boolean playing = FALSE;

/*
 * shamelessly stolen from xigc
 */

enum {
	UNKNOWN	=  0,
      	PROMPT	=  1,	/* A Prompt (never)  (NEVER?! -kkkken)	*/
	BEEP	=  2, 	/* \7 telnet 		*/
	BOARD	=  3,	/* Board being drawn 	*/
	DOWN	=  4,	/* The server is going down */
	ERROR	=  5,	/* An error reported	*/
      	FIL	=  6,	/* File being sent	*/
	GAMES	=  7,	/* Games listing	*/
      	HELP	=  8,	/* Help file		*/
	INFO	=  9,	/* Generic info		*/
	LAST	= 10,	/* Last command		*/
      	KIBITZ	= 11,	/* Kibitz strings	*/
	LOAD	= 12,	/* Loading a game	*/
	LOOK	= 13,	/* Look 		*/
      	MESSAGE	= 14,	/* Message lising	*/
      	MOVE	= 15,	/* Move #:(B) A1	*/
	OBSERVE	= 16,	/* Observe report	*/
	REFRESH	= 17,	/* Refresh of a board	*/
      	SAVED	= 18,	/* Stored command	*/
      	SAY	= 19,	/* Say string		*/
      	SCORE	= 20,	/* Score report		*/
      	SHOUT	= 21,	/* Shout string		*/
      	STATUS	= 22,	/* Current Game status	*/
	STORED	= 23,	/* Stored games		*/
      	TELL	= 24,	/* Tell string		*/
	THIST	= 25,	/* Thist report		*/
	TIM	= 26,	/* times command	*/
	WHO	= 27,	/* who command		*/
	UNDO	= 28	/* Undo report		*/
};


enum {
	LOGON		= 0,
	PASSWORD	= 1,
	PASSWD_NEW	= 2,
	PASSWD_CONFIRM	= 3, 
	REGISTER	= 4, 
	WAITING		= 5,
	PLAYING		= 6,
	SCORING		= 7,
	OBSERVING	= 8
};



/*
 * end shamelessly stolen code
 */



int OpenServer (hostname, port)
     char *hostname;
     int port;
{
    struct hostent *hp;
    struct sockaddr_in in;
    int s;
    
    in.sin_addr.s_addr = inet_addr(hostname);
    if (in.sin_addr.s_addr == ~0) {
	hp = gethostbyname(hostname);
	if (!hp) {
	    char buf[200];
	    sprintf (buf, "Could not resolve hostname %s", hostname);
	    Fatal(buf);
	    return -1;
	}
	bcopy (hp->h_addr_list[0], &in.sin_addr.s_addr, 4); /* Dang it, internet addresses are 4 bytes! */
    }
    in.sin_port = htons(port);
    in.sin_family = AF_INET;
    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
	Fatal ("Could not get socket!");
	return -1;
    }
    if (connect(s, &in, sizeof(in))) {
	Fatal ("Unable to connect to server");
	return -1;
    }
    return s;
}

typedef struct template {
    int op;
    void *arg;
} Template;

enum {					/* legal values for op */
    SkipOver,
    GetString,
    GetNumber,
};

static int Parse (c, template, templateLen)
     char *c;
     Template *template;
     int templateLen;
{
    char *s;
    int len;
    
    while (templateLen--) {
	switch (template->op) {
	  case SkipOver:
	    len = strlen((char *) template->arg); 
	    while (*c && strncmp(c, (char *) template->arg, len))
		c++;
	    if (!*c) return 0;
	    c += len;
	    break;
	  case GetString:
	    while (isspace(*c)) c++;
	    s = c;
	    *((char **) template->arg) = c;
	    while (isalnum(*c) || *c == '*') c++;
	    if (s == c) return 0;
	    *c++ = 0;
	    break;
	  case GetNumber:
	    while (isspace(*c)) c++;
	    if (*c != '-' && !isdigit(*c)) return 0;
	    *((int *) template->arg) = atoi(c);
	    while (isdigit(*c)) c++;
	    *c++ = 0;			/* for good measure */
	    break;
	}
	template++;
    }
    return 1;
}

char *thePassword = NULL;
char thePasswordBuf[40];


void PasswordHandler (w, closure, event, cont)
     Widget w;
     XtPointer closure;
     XEvent *event;
     Boolean *cont;
{
    KeySym ksym;
    
    *cont = False;
    if (event->type != KeyPress) return;
    ksym = XKeycodeToKeysym(XtDisplay(w), event->xkey.keycode, event->xkey.state);
    switch (ksym) {
      case XK_BackSpace:
      case XK_Delete:
	if (thePassword > thePasswordBuf)
	    thePassword--;
	break;
      case XK_Return:
	*thePassword++ = '\n';
	*thePassword = 0;
	SendServerNoEcho (thePasswordBuf);
	XUngrabKeyboard (XtDisplay(w), CurrentTime);
	XtDestroyWidget ((Widget) closure);
	break;
      default:
	if (ksym >= 32 && ksym < 127 &&
	    thePassword - thePasswordBuf + 2 < sizeof(thePasswordBuf))
	    *thePassword++ = ksym;
    }
}

void ReadSendPassword (labelText)
     char *labelText;
{
    Widget shell, label;
    
    if (password) {
	SendServerNoEcho (password);
	SendServer ("\n");
	password = NULL;
	return;
    } 
    {
	Display *dpy = XtDisplay(toplevel);
	int screen = DefaultScreen (dpy);
	static Arg args[] = { XtNx, 0,
				  XtNy, 0,
				  XtNwidth, 400,
				  XtNheight, 100,
			      };
	args[0].value = DisplayWidth(dpy, screen) / 2 - 200;
	args[1].value = DisplayHeight(dpy, screen) / 2 - 50;
	shell = XtCreatePopupShell ("passwordEntry", overrideShellWidgetClass, toplevel, args, XtNumber(args));
    }
    {
	static Arg args[] = { XtNlabel, 0, 
				  XtNborderWidth, 0,
			      };
	args[0].value = (XtArgVal) labelText;
	label = XtCreateManagedWidget ("label", labelWidgetClass, shell, args, XtNumber(args));
    }
    XtPopup (shell, XtGrabExclusive);
    XtInsertEventHandler (shell, KeyPressMask, False, PasswordHandler, (XtPointer) shell, XtListHead);
    XGrabKeyboard (XtDisplay(shell), XtWindow(shell), False, GrabModeSync, GrabModeAsync, CurrentTime);
    thePassword = thePasswordBuf;
}



static void HandleGame (c)
     char *c;
{
    static int game;
    static char *wname, *bname, *wrank, *brank;
    static int move, size, hcap, komi, obs;
    
    static Template template[] = {
	{ SkipOver, "[" },
	{ GetNumber, &game },
	{ GetString, &wname },
	{ SkipOver, "[" },
	{ GetString, &wrank },
	{ SkipOver, "vs." },
	{ GetString, &bname },
	{ SkipOver, "[" },
	{ GetString, &brank },
	{ SkipOver, "(" },
	{ GetNumber, &move },
	{ GetNumber, &size },
	{ GetNumber, &hcap },
	{ GetNumber, &komi },
	{ SkipOver, "(" },
	{ GetNumber, &obs } };
    
    if (Parse(c, template, sizeof(template)/sizeof(Template))) {
	SetGameInfo (game, wname, wrank, bname, brank, move, size, hcap, komi, obs);
    }
}

static int lastMoveGame = -1;

static void HandleMove (c)
     char *c;
{
    static int w1, w2, w3, b1, b2, b3;
    static char *wname, *bname;
    static char wnamebuf[MAXUSERNAME], bnamebuf[MAXUSERNAME];
    int move, row, col;
    
    static Template template[] = {
	{ SkipOver, "Game" },
	{ GetNumber, &lastMoveGame },
	{ GetString, &wname },
	{ SkipOver, "(" },
	{ GetNumber, &w1 },
	{ GetNumber, &w2 },
	{ GetNumber, &w3 },
	{ SkipOver, "vs" },
	{ GetString, &bname },
	{ SkipOver, "(" },
	{ GetNumber, &b1 },
	{ GetNumber, &b2 },
	{ GetNumber, &b3 } };
    
    char *colptr;

    if (!strncmp(c, "Game ", 5)) {
	if (!Parse(c, template, sizeof(template)/sizeof(Template))) {
	    lastMoveGame = -1;
	} else {
	    strcpy (wnamebuf, wname);
	    strcpy (bnamebuf, bname);
	}
	ReceivedGameIndicator (lastMoveGame, wname, bname, w2, b2, w3, b3);
    } else {
	if (lastMoveGame == -1) {		/* We are confused if we haven't seen a game line yet. */
	    return;
	}
	if (!isdigit(*c)) return;
	move = atoi(c);
	c = (char *) index(c, ' '); if (!c) return;
	if (!strncmp(c, " Handicap", 9)) {
	    row = -1;
	    col = atoi(c+10);
	    if (!col) return;
	} else if (!strncmp(c, " Pass", 5)) {
	    ServerOutput ("\n *** Pass\n");	/* Little changes on the goban... so... */
	    row = -2;
	    col = 0;
	} else {
	    colptr = (char *) index(colNames, c[1]); if (!colptr) return;
	    col = colptr - colNames + 1;	/* Row and Col start from 1... for mysterious reasons... */
	    row = atoi(c + 2);
	    if (!row) return;
	}
	ReceivedMove (lastMoveGame, move, row, col);
    }
}

static void HandleUndo (c)
     char *c;
{
    ServerOutput ("\n *** Undo\n");	/* Little changes on the goban... so... */
    ReceivedUndo ();
}

static void HandleKibitz (c)
     char *c;
{
    static char knameBuf[MAXUSERNAME];
    static char krankBuf[MAXRANK];
    static int game = -1;
    
    if (!strncmp(c, "Kibitz ", 7)) {
	static char *kname, *krank;
	static Template template[] = {
	    { SkipOver, "Kibitz" },
	    { GetString, &kname },
	    { SkipOver, "[" },
	    { GetString, &krank },
	    { SkipOver, "[" },
	    { GetNumber, &game },
	};
	if (!Parse(c, template, sizeof(template)/sizeof(Template))) {
	    game = -1;
	    return;
	}
	strcpy (knameBuf, kname);
	strcpy (krankBuf, krank);
    } else {
	if (game > 0) {
	    ReceivedKibitz (game, knameBuf, krankBuf, c);
	}
    }
}

static void HandleInfo (c)	/* Some of these are more than merely informational */
     char *c;
{
    if (!strncmp(c, "Removing @", 10)) {
	char *colptr;
	int row, col;
	c = (char *) index(c, '@');
	colptr = (char *) index(colNames, c[2]); if (!colptr) return;
	col = colptr - colNames + 1;	/* Row and Col start from 1... for mysterious reasons... */
	row = atoi(c + 3);
	ServerOutput (c);
	ReceivedRemove (lastMoveGame, row, col);
    } else {
	ServerOutput (c);
    }
}

static void HandleStatus (c)
     char *c;
{
    int h;
    
    h = atoi(c);
    while (isdigit(*c)) c++;
    if (*c != ':') return;
    ScoredBoardReceived (h + 1, c + 2);
}

static int getgamenum (g)
     char *g;
{
    if (*g == '-') return 0;
    if (*g == ' ') g++;
    return (isdigit(*g)) ? atoi(g) : -1;
}


static void Handle1Who (c)
     char *c;
{
    int state, observing, playing;
    char *username, *idle, *rank, *t;
    
    if (c[2] == '!') state = UserLooking;
    else if (c[2] == 'X') state = UserClosed;
    else state = UserOpen;
    observing = getgamenum(c + 4);
    playing = getgamenum(c + 9);
    if (playing) state = UserPlaying;
    if (observing < 0 || playing < 0) return;
    username = c + 12;
    idle = c + 23;
    rank = c + 30;
    idle[3] = 0;
    rank[4] = 0;
    t = (char *) index(username, ' ');
    if (t)
	*t = 0;
    else
	username[MAXUSERNAME - 1] = 0;
    AddUser (username, rank, idle, state, observing, playing);
}

static void HandleWho (c)
     char *c;
{
    char *bar;
    if (!strncmp (c, " Info", 5)) {
	ClearUsers();
	return;
    }
    if (!strncmp (c, "                *", 17)) {
	DoneGettingUsers();
	return;
    }
    bar = (char *) index(c, '|');
    if (bar) {
	*bar = 0;
	Handle1Who (bar + 2);
    }
    Handle1Who (c);
}


typedef struct msgqueue {
    char text[100];
    struct msgqueue *next;
} MsgQueue;

MsgQueue *msgQueue = NULL;

void SendServerNoEcho (msg)
     char *msg;
{
    write (server_sock, msg, strlen(msg));
    waitingAtPrompt = 0;
}

void SendServer (msg)
     char *msg;
{
    ServerOutput (msg);
    SendServerNoEcho (msg);
}

void QueueForServer (txt)
     char *txt;
{
    MsgQueue *n;
    MsgQueue *last;

    if (waitingAtPrompt) {
	SendServer(txt);
	return;
    }
    n = (MsgQueue *) malloc(sizeof(MsgQueue));
    strcpy (n->text, txt);
    n->next = NULL;
    if (!msgQueue)
	msgQueue = n;
    else {
	for (last = msgQueue; last->next; last = last->next);
	last->next = n;
    }
}


static void HandleLine (line)
     char *line;
{
    int code = atoi(line);
    char *c = line;
    char *start;

    if (code) {
	while (isdigit(*c)) c++;
    }
    if ((code == INFO ||
	 code == MESSAGE ||
	 code == THIST) &&
	(!strcmp(c, " File\n"))) {
	inFile = !inFile;
	return;
    }
    if (inFile) {
	ServerOutput(line);
	return;
    }
    start = c + 1;			/* skip space too */
    while (isspace(*c)) c++;
    if (debug) printf("--- %s", line);
    switch (code) {
      case PROMPT:			/* the server has quiesced.  do useful stuff. */
	code = atoi(c);
	switch (code) {
	  case PASSWORD:
	    ReadSendPassword("Please enter your password");
	    break;
	  case PASSWD_NEW:
	    password = NULL;
	    ReadSendPassword("You're a new user; please choose a password");
	    break;
	  case PASSWD_CONFIRM:
	    ReadSendPassword("Please re-enter your password");
	    break;
	  case WAITING:
	  case PLAYING:
	  case SCORING:
	  case OBSERVING:
	    waitingAtPrompt = 1;
	    switch(code) {
	      case WAITING:
		ServerOutput ("#> ");
		if (playing) {
		    EndPlaying();
		    playing = 0;
		}
		break;
	      case PLAYING:
		ServerOutput ("Play> ");
		if (!playing) {
		    playing = BeginPlaying();
		}
		break;
	      case SCORING:
		ServerOutput ("Remove dead groups> ");
		break;
	      case OBSERVING:
		ServerOutput ("#> ");
	    }
	    if (msgQueue && waitingAtPrompt) {
		MsgQueue *next = msgQueue->next;
		SendServer (msgQueue->text);
		free (msgQueue);
		msgQueue = next;
	    }	    
	    CleanupGames();
	}
	break;
      case MOVE: HandleMove (c); break;
      case UNDO: HandleUndo (c); break;
      case GAMES: ServerOutput(start); HandleGame(c); break;
      case KIBITZ: HandleKibitz(c); break;
      case STATUS: HandleStatus(c); break;
      case WHO: ServerOutput(start); HandleWho(start); break;
      case INFO:
	HandleInfo (start);
	break;
	
      default:
	if (start[0])			/* Suppress blank lines */
	    ServerOutput(start);
    }
}

void HandleServer (txt, len)
     char *txt;
     int len;
{
    static int linelen = 0;
    static int partial = 0;
    static char line[4096];
    char c;
    
    while (len--) {
	c = *txt++;
	if (c == '\007') {
	    XBell (XtDisplay(toplevel), 0);
	} else if (c < 127 && (c >= 32 || c == '\t' || c == '\n')) {
	    if (c == '\n') {
		line[linelen++] = '\n';
		line[linelen] = 0;
		if (partial) {
		    ServerOutput(line);
		    partial = 0;
		} else {
		    HandleLine (line);
		}
		linelen = 0;
	    } else {
		if (linelen < 4094)
		    line[linelen++] = c;
	    }
	}
    }
    if (linelen) {
	line[linelen] = 0;
	if (!atoi(line)) {	/* Handle partial lines */
	    ServerOutput (line);	/* (probably prompts) */
	    if (!strcmp(line, "Login: ") && userName) {
		SendServer (userName);
		SendServer ("\n");
		userName = NULL;
	    } else if (!strcmp(line, "Password: ")) {
	        ReadSendPassword("Please enter your password");
	    } else if (!strcmp(line, "Enter Your Password Again: ")) {
		ReadSendPassword("Please re-enter your password");
	    } else if (!strcmp(line, "#> ")) {
		SendServer ("toggle client 1\n");
	    }
	    linelen = 0;
	    partial = 1;
	}
    }
}

