/*
 *                    C O M M U N I C A T I O N . C
 *
 *  Functions for the communication with the server.  Basically they
 *  provide functionality for sending/receiving strings but some higher
 *  level functions are also included.
 *
 *  Version      : $Revision: 1.3 $
 *
 *  Created      : Fri May 20 11:18:07 1994
 *  Author       : Ulrich Drepper <drepper@mydec>
 *
 *  Last modified: Mon Jul  4 14:08:51 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: communication.c,v 1.3 1994/07/04 13:09:00 drepper Exp $";
#endif /* lint */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>

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

#if defined(MIPSEL) || defined(sun)
extern int recv(int sock, void *buf, int len, unsigned int flags);
#endif /* MIPSEL || sun */

#ifdef sun
int tolower(int);
char *strerror (int errnoval);
#endif 

/*
 * constants for local use
 */
#define MAXBUFSIZE  4095    /* this large can be a single string */

/*
 * prototypes for module local functions
 */
static Bool putLine(int sock, const char *fmt, ...)
    __attribute__ ((format (printf,2,3)));
static Bool putLineV(int sock, const char *fmt, va_list ap);

/*
 * exported functions
 */
Bool
getEmpLineD(int sock, ServerAnswer *answerCode, char **buf)
{
    Bool ret;

    do {
	ret = getLine(sock, answerCode, buf);
    } while (ret && *buf == NULL);

    return ret;
}

Bool
expectAnswer(int sock, ServerAnswer answer)
{
    ServerAnswer real;
    char *dummy;

    return getEmpLineD(sock, &real, &dummy) && answer == real;
}

Bool
sendCmd(int sock, ServerCommand cmd, const char *arg)
{
    return putLine(sock, "%s %s\n", empCommand[cmd].text, 
		   arg ? : "");
}

Bool
sendCmdStr(Bool (*fct)(Bool, Bool, char *, void *), void *data,
	   long *serialR, const char *fmt, ...)
{
    static long serial = 0;
    Bool ret;
    va_list ap;

    /* clear message line */
    messageStr[0] = '\0';
    showMessageLine();
    
    va_start(ap, fmt);

    if (enqueueCmd(serial, fct, data) == False) return False;

/*vfprintf(stdout, fmt, ap);*/
    if (!putLineV(empSocket, fmt, ap)) {
	dequeueLastCmd();
	return False;
    }

    if (serialR) *serialR = serial;

    serial++;                         /* increment anyway */

    va_end(ap);

    return True;
}

/* getLine:  returns a \n terminated line read from the given socket.
 *           On success the return value is True.  When *buf != NULL,
 *           the string starts at *buf, else no more data is available.
 *           When the return value is False an error occured
 */
Bool
getLine(int sock, ServerAnswer *answerCode, char **buf)
{
    static char  buffer[MAXBUFSIZE+1];
    static char *bufptr = buffer;
    static int  size    = MAXBUFSIZE;
    char *cp;
    int n, nn;

#if 0
    alarm(10);
#endif

    do {
	n = recv(sock, bufptr, size, MSG_PEEK);

	/* n <= 0  ->  error occured or no data */
	if (n <= 0) {
#if 0
	    alarm(0);
#endif
	    if (n == 0 || errno == EWOULDBLOCK) {
		*buf = NULL;
		return True;
	    }

	    message(WARN, "error while reading from socket (recv): %s",
		    strerror(errno));
	    return False;
	}

	size -= n;

	/* look for end of line */
	bufptr[n] = '\0';
	if ((cp = strchr(bufptr, '\n')) != NULL) {
	    n = 1 + cp - bufptr;
	    bufptr[n] = '\0';
	}

	/* read available data */
	nn = read(sock, bufptr, n);
	if (nn < 0) {
#if 0
	    alarm(0);
#endif
	    message(ERROR, "read returned error (getEmpLine): %s",
		    strerror(errno));
	    return False;
	}
	if (nn != n) {
#if 0
	    alarm(0);
#endif
	    message(ERROR, "short read (getEmpLine): %s",
		    strerror(errno));
	    return False;
	}
	
	bufptr += n;
    } while (cp == NULL);       /* -> '\n' is found */

#if 0
    alarm(0);
#endif

    /* next time start a new string */
    bufptr = buffer;
    size   = MAXBUFSIZE;

    /* test for consistent received text */
    if (!isxdigit(buffer[0])) {
	message(WARN, "expecting hex-digit as command code\n"
		      "got %s",
                buffer);
	return False;
    }

    /* convert to command code */
    if (isdigit(buffer[0])) {
	*answerCode = buffer[0] - '0';
    } else {
	*answerCode = 10 + tolower(buffer[0]) - 'a';
    }

    *buf = &buffer[2];

    return True;
}

/*
 * module local functions 
 */

static Bool
putLine(int sock, const char *fmt, ...)
{
    Bool ret;
    va_list ap;

    /* wrap open argument list to va_list */
    va_start(ap, fmt);
    ret = putLineV(sock, fmt, ap);
    va_end(ap);
    return ret;
}

static Bool
putLineV(int sock, const char *fmt, va_list ap)
{
    static char buffer[MAXBUFSIZE+1];
    char *cp = buffer;
    int n, nn;

    /* each command must be terminated with a \n */
    assert(fmt[strlen(fmt)-1] == '\n');

    if (!connected && !doConnect()) {
	message(ERROR, "cannot reconnect to server");
	return FALSE;
    }

    vsprintf(buffer, fmt, ap);
    n = strlen(buffer);
    assert(n <= MAXBUFSIZE);

    /* send string, perhaps in parts */
    while (n > 0) {
	nn = write(sock, cp, n);
	if (nn < 0) {
	    message(ERROR, "error while sending to server: %s",
		    strerror(errno));
	    return False;
	}

	n  -= nn;
	cp += nn;
    }

    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:
 */
