/*
 *                          C O N N E C T . C
 *
 *  Build connection to empire server.  The connection is automatically
 *  closed when for some period no transfer is done.  So building the
 *  connection is done more than once.  But the initialization should
 *  only be done once.
 *
 *  Version      : $Revision: 1.5 $
 *
 *  Created      : Thu May 19 23:49:43 1994
 *  Author       : Ulrich Drepper <drepper@mydec>
 *
 *  Last modified: Thu Jul 14 19:49:27 1994
 *  Author       : Ulrich Drepper <drepper@mydec>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 1, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#if !defined(lint)
static const char *vcid = "$Id: connect.c,v 1.5 1994/07/15 00:48:15 drepper Exp $";
#endif /* lint */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#if defined(sgi)
#include <fcntl.h>
#include <sys/endian.h>
#endif /* sgi */

#include "proto.h"
#include "empire.h"

#if defined(MIPSEL) || defined(sun)
/* missing prototypes in system headers */
extern int connect(int sock, struct sockaddr *serv_addr, int addrlen);
extern int socket(int family, int type, int protocol);
#endif /* MIPSEL || sun */

#ifdef sun
int on_exit(void(*procp)(int,caddr_t), caddr_t arg);
#define atexit(fct) on_exit((void(*)(int,caddr_t))fct, NULL)
char *strerror (int errnoval);
#endif

/*
 * exported variables
 */
Bool connected = False;          /* connected to server ? */
int  empSocket = -1;             /* socket of connection  */

/*
 * module local variables
 */
static struct sockaddr_in sin;   /* socket for server connection */
static const char *user;         /* user name */
static const char *country;      /* country name */
static const char *player;       /* player name (password) */

/*
 * prototypes for local functions
 */
static Bool resolveHostname(struct sockaddr_in *s, const char *host);
static Bool resolvePort(struct sockaddr_in *s, const char *port);

/*
 * exported functions
 */
Bool
setConnectionData(const char *host, const char *port,
		  const char *usr, const char *ctry, const char *plyr)
{
    const char *hostTable[]  = { host, "" /*XAppDefault*/, DEFEMPHOST, NULL };
    const char **serverNames = hostTable;
    const char *portTable[]  = { port, "" /*XAppDefault*/, DEFEMPPORT, NULL }; 
    const char **serverPorts = portTable;

    assert(connected == 0);

    /*
     * get INET address of empire host 
     */
    while (serverNames[0] != NULL && !resolveHostname(&sin, serverNames[0])) {
	if (serverNames[0][0]) {
	    message(WARN, "no such host: %s", serverNames[0]);
	}
	serverNames++;
    }
    if (serverNames[0] == NULL) return False;

    /*
     * get port of empire service on server 
     */
    while (serverPorts[0] != NULL && !resolvePort(&sin, serverPorts[0])) {
	if (serverPorts[0][0]) {
	    message(WARN, "no such service: %s", serverPorts[0]);
	}
	serverPorts++;
    }
    if (serverPorts[0] == NULL) return False;

    /* set socket family (Internet Domain Socket) */
    sin.sin_family = AF_INET;

    /* ensure that socket is closed on exit (once it is opened) */
    atexit(closeConnection);

    /*
     * Ready to connect to the server:  a call to `doConnect' now has
     * all information it needs to connect in `sin' and `empSocket'.
     * 
     * Now only the login information must be set.
     */

    if ((user = usr) == NULL || usr[0] == '\0') {
	message(FATAL, "no user name given");
    }
    if ((country = ctry) == NULL || ctry[0] == '\0') {
	message(FATAL, "no country name given");
    }
    if ((player = plyr) == NULL || plyr[0] == '\0') {
	message(FATAL, "no player name given");
    }
    
    return True;
}

Bool
doConnect(void)
{
    char *buf;                    /* buffer for version test */
    char *cp;                     /* temporary pointer */
    ServerAnswer answer;          /* answer type from server */

    /* possibly the connection is still established */
    if (connected) return True;

    /*
     * create socket
     */
    empSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (empSocket < 0) 	message(FATAL, "cannot create socket");

    /*
     * During the init phase the protocol is as follows:
     *
     *          client                server
     *         connect ------------>                 (1)
     *                 <------------  INIT  
     *       user name ------------>                 (2)
     *                 <------------  OK
     *    country name ------------>                 (3)
     *                 <------------  OK
     *     player name ------------>                 (4)
     *                 <------------  OK
     *            PLAY ------------>                 (5)
     *                 <------------  INIT/version
     */

    /* (1)  connect to server */
    if (connect(empSocket, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
	close(empSocket);
	return False;
    }
    /* yes, we are connected */
    connected = True;
    if (!expectAnswer(empSocket, C_INIT)) {
	close(empSocket);
	connected = False;
	return False;
    }

    /* (2)  give user name */
    sendCmd(empSocket, USER, user);
    if (!expectAnswer(empSocket, C_CMDOK)) {
	close(empSocket);
	connected = False;
	return False;
    }

    /* (3)  give country name */
    sendCmd(empSocket, COUN, country);
    if (!expectAnswer(empSocket, C_CMDOK)) {
	close(empSocket);
	connected = False;
	return False;
    }

    /* (4)  give password */
    sendCmd(empSocket, PASS, player);
    if (!expectAnswer(empSocket, C_CMDOK)) {
	close(empSocket);
	connected = False;
	return False;
    }

    /* (5)  start playing/check protocol version */
    sendCmd(empSocket, PLAY, NULL);
    if (!getEmpLineD(empSocket, &answer, &buf) ||
	answer != C_INIT) {
	message(ERROR, "cannot start playing\nserver says: %s", buf);
	close(empSocket);
	connected = False;
	return False;
    }
    while (isspace(*buf)) buf++;
    cp = strchr(buf, '\n');
    if (cp != NULL) cp[0] = '\0';
    /* compare with own protocol */
    if (atoi(buf) != CLIENTPROTO) {
	message(WARNREP, "empire client out of date; get a new version\n"
		         "  client version: %d <-> server version: %d",
		CLIENTPROTO, atoi(buf));
    }

    /* let Xt listen on the socket */
    inputId = XtAppAddInput(appContext, empSocket, (XtPointer)XtInputReadMask,
			    inputReady, NULL);
    
    /* now start the timer */
    if (cmdQueue == NULL) {
	timerId = XtAppAddTimeOut(appContext, timerDelay, connectTimeOut,
				  NULL);
    }

    /* wait for server prompt */
    do {
	if (!getEmpLineD(empSocket, &answer, &buf)) {
	    message(FATAL, "server gives no prompt");
	    /* NOTREACHED */
	}
    } while (answer != C_PROMPT);

    /*
     * the client has to work asynchron.  So reading the socket must
     * not block.
     */
    if (fcntl(empSocket, F_SETFL, FNDELAY) == -1) {
	message(FATAL, "socket fcntl: %s", strerror(errno));
    }

    /* heureka, we are connected */
    XtVaSetValues(
	connIcon,
	XtNbitmap, iconConnected,
	NULL);

    return True;
}

void
closeConnection(void)
{
    if (connected) {
	/* signal server connection dismantling */
	sendCmd(empSocket, QUIT, NULL);

	/* close socket */
	close(empSocket);

	/* set flag according to that */
	connected = False;

	/* and show status in menu line */
	XtVaSetValues(
	    connIcon,
	    XtNbitmap, iconNotConnected,
	    NULL);
    }
}

/*
 * local functions 
 */
static Bool
resolveHostname(struct sockaddr_in *s, const char *host)
{
    if (host == NULL || host[0] == '\0') return False;

    if (isdigit(host[0])) {
	s->sin_addr.s_addr = inet_addr(host);
    } else {
	struct hostent *hp = gethostbyname(host);
	if (hp == NULL) {
	    return False;
	} else {
	    memcpy(&s->sin_addr, hp->h_addr, sizeof(s->sin_addr));
	}
    }

    return True;
}


static Bool
resolvePort(struct sockaddr_in *s, const char *port)
{
    if (port == NULL || port[0] == '\0') return False;

    if (isdigit(port[0])) {
	s->sin_port = htons(atoi(port));
    } else {
	struct servent *sp = getservbyname(port, "tcp");
	if (sp == NULL) {
	    return False;
	} else {
	    s->sin_port = sp->s_port;
	}
    }

    return True;
}

/*
 * Local Variables:
 *  mode:c
 *  c-indent-level:4
 *  c-continued-statement-offset:4
 *  c-continued-brace-offset:0
 *  c-brace-offset:0
 *  c-imaginary-offset:0
 *  c-argdecl-indent:4
 *  c-label-offset:-2
 * End:
 */
