/*
 *
 * socket.c -- routines for sockets
 *
 * This code is copyright 1995 by James A. Cherry, jac@doe.carleton.ca.
 * It is not to be redistributed or modified without his permission.
 *
 */

#include <ctype.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <time.h>

#include "funddefs.h"
#include "globals.h"
#include "socket.h"
#include "proto.h"

int ClientInit( host, portnum, retry_limit )
  char *host;
  int portnum, retry_limit;
/* Attempts to establish a connection with the host identified by the
   string `host' at port `portnum'.  If the connection is established,
   then returns the socket number.  Otherwise, returns -1 and places
   an error message in SocketError. */
{
    int sock, notrys;
    static struct hostent *hp;
    static struct sockaddr_in server;
    int server_avail;

    SocketError[0] = '\0';
    notrys = 0;
    while( notrys < retry_limit ) {

	sock = socket( AF_INET, SOCK_STREAM, 0 );

	if( sock < 0 ) {
	    sprintf( SocketError, "Error opening socket: %s", sys_errlist[errno] );
	    return( -1 );
	}
	server.sin_family = AF_INET;

	if( isdigit( *host ) ) {
	    server.sin_addr.s_addr = inet_addr( host );
	} else {
	    hp = gethostbyname( host );
	    if( hp == 0 ) {
		sprintf( SocketError, "%s: unknown host", host );
		return( -1 );
	    }
	    bcopy( hp->h_addr, &server.sin_addr, hp->h_length );
	}
	server.sin_port = htons( portnum );

	server_avail = connect( sock, (struct sockaddr *) &server, sizeof( server ) );
	if( server_avail ) {
	    close( sock );
	    sprintf( SocketError, "Connection error: %s", sys_errlist[errno] );
	    notrys++;
	    if( errno == EINTR ) return( -1 );
	    if( notrys < retry_limit )
		sleep( 5 );
	} else return( sock );
    }

    sprintf( SocketError, "Can't seem to connect to server -- giving up" );
    return( -1 );
}

int Check_for_data( fd )
  int fd;
/* Checks the socket descriptor fd to see if any incoming data has
   arrived.  If yes, then returns 1.  If no, then returns 0.
   If an error, returns -1 and stores the error message in SocketError. */
{
    int status;		    /* return code from Select call. */
    struct fd_set wait_set; /* A set representing the connections that
                               have been established. */
    struct timeval tm;      /* A timelimit of zero for polling for new
                               connections. */
    FD_ZERO( &wait_set );
    FD_SET( fd, &wait_set );

    tm.tv_sec = 0;
    tm.tv_usec = 0;
    status = select( FD_SETSIZE, &wait_set, (fd_set *) 0, (fd_set *) 0, &tm );

    if( status < 0 )
	sprintf( SocketError, "Error in select: %s", sys_errlist[errno] );

    return( status );
}

int DoRead( filedes )
  int filedes;
/* Reads from file descripter filedes, transferring the data into
   ReadBuf[].Returns 0 on success or a negative error code
   otherwise. */
{
    int data_avail;	 /* true if data is available on the socket. */
    int n;		 /* number of bytes transferred. */

    data_avail = Check_for_data( filedes );
    if( data_avail < 0 )
	return( READ_SYSERR );
    else if( data_avail == 0 )
	return( READ_NODATA );

    n = read( filedes, RBIn, RBLast - RBIn );

    if( n == 0 )
	return( READ_EOF );
    else if( ( n < 0 ) && ( errno == EWOULDBLOCK ) )
	return( READ_NODATA );
    else if( n < 0 )
	return( READ_SYSERR );

    RBIn += n;
    return( 0 );
}

int DoWrite( filedes )
  int filedes;
/* Write the contents of WriteBuf to file descriptor filedes, adding a
   newline character first.  If successful, return 1, else return 0. */
{
    int n, cc;

    for( n = 0; WriteBuf[n] != '\n'; n++ );
    WriteBuf[++n] = '\0';
    LPrint( "-> %s", WriteBuf ); 
    cc = write( filedes, WriteBuf, n );

    return( ( cc < n ) ? 0 : 1 );
}

void add_received_string( nl )
  char *nl;
/* Add a received string to the queue. */
{
    recstr *newptr, *ptr1;

    LPrint( "<- %s", nl ); 
    newptr = (recstr *) malloc( RS_SIZE );
    if( newptr == NULL ) {
	LPrint( "*** Error: no memory left in add_received_string()" );
	ExitWindow();
	exit( 21 );
    }
    newptr->next = NULL;
    newptr->string = nl;
    
    ptr1 = RecHead;
    if( ptr1 == NULL ) RecHead = newptr;
    else {
	while( ptr1->next != NULL )
	    ptr1 = ptr1->next;
	ptr1->next = newptr;
    }
}

void RemoveReceivedString()
/* Remove the string from the head of the received queue. */
{
    recstr *ptr1;

    if( RecHead == NULL ) return;
    free( (char *) RecHead->string );
    ptr1 = RecHead->next;
    free( (recstr *) RecHead );
    RecHead = ptr1;
}

void AddTagEntry( ttype, data, name, board, form )
    int ttype;
    recstr *data;
    char *name, *board, form;
/* Add an entry to the queue of tagged commands. */
{
    tagent *newptr, *ptr1;

    newptr = (tagent *) malloc( TE_SIZE );
    if( newptr == NULL ) {
	LPrint( "*** Error: no memory left in AddTagEntry()" );
	ExitWindow();
	exit( 23 );
    }
    newptr->next = NULL;
    newptr->data = data;
    newptr->ttype = ttype;
    newptr->name[0] = '\0';
    newptr->board[0] = '\0';
    strcpy( newptr->name, name );
    strcpy( newptr->board, board );
    newptr->form = form;
    ptr1 = TagHead;
    if( ptr1 == NULL ) TagHead = newptr;
    else {
	while( ptr1->next != NULL )
	    ptr1 = ptr1->next;
	ptr1->next = newptr;
    }
}

void remove_tag_entry( clear_strings )
    int clear_strings;
/* Remove entry at head of tag queue.  If clear_string is 1, also free up
   received data list. */
{
    tagent *p1;
    recstr *p2, *p3;

    p1 = TagHead->next;
    if( clear_strings == 1 ) {
	p2 = TagHead->data;
	while( p2 != NULL ) {
	    p3 = p2->next;
	    free( (char *) p2->string );
	    free( (recstr *) p2 );
	    p2 = p3;
	}
    }
    free( (tagent *) TagHead );
    TagHead = p1;
}

void start_tagging()
/* Called upon reception of a `[BEGIN]' string. */
{
    if( TagHead == NULL ) {
	LPrint( "Uh oh: got a tagged block when not expecting one.\n" );
    } else {
	LPrint( "Found [BEGIN] tag: expecting region type %d\n",
		TagHead->ttype );
	HasValidLog2 = 1;
	Log2GameInProgress = 1;
	MeInLog2 = 0;
	TheyInLog2 = 0;
	EmailAddr = NULL;
    }
}

void add_tagged_data( str )
    char *str;
/* Put a string of data at the end of the data list on TagHead->data. */
{
    recstr *newptr, *p1;

    newptr = (recstr *) malloc( RS_SIZE );
    if( newptr == NULL ) {
	LPrint( "*** Error: no memory left in ProcessReadData()\n" );
	ExitWindow();
	exit( 24 );
    }
    newptr->next = NULL;
    newptr->string = (char *) malloc( strlen( str ) + 1 );
    if( newptr->string == NULL ) {
	LPrint( "*** Error: no memory left in ProcessReadData()\n" );
	ExitWindow();
	exit( 25 );
    }
    strcpy( newptr->string, str );
    p1 = TagHead->data;
    if( p1 == NULL ) TagHead->data = newptr;
    else {
	while( p1->next != NULL )
	    p1 = p1->next;
	p1->next = newptr;
    }
}

int write_log2()
{
    FILE *log2fp;
    char log2fn[40];
    int rbytes;
    recstr *p1;

    sprintf( log2fn, "%s%s.%d.%d", LOG2_DIR, PlrName, getpid(), FileCount );
    log2fp = fopen( log2fn, "w" );
    if( log2fp == NULL ) return( 0 );
    rbytes = fprintf( log2fp, "%s %s %s\n",
		      TagHead->board, TagHead->name, EmailAddr );
    if( rbytes == EOF ) return( 0 );
    for( p1 = TagHead->data; p1 != NULL; p1 = p1->next ) {
	rbytes = fprintf( log2fp, "%s", p1->string ); /* \n is already there */
	if( rbytes == EOF ) return( 0 );
    }
    rbytes = fprintf( log2fp, "\n" );
    if( rbytes == EOF ) return( 0 );
    fclose( log2fp );
    FileCount++;
    return( 1 );
}

void process_tagged_log2( str )
    char *str;
/* Deal with a string inside a tagged log2 block. */
{
    int ok;

    if( str == NULL ) {
/* Found end of log2; see if it's OK to critique */
	ok = 1;
	if( HasValidLog2 == 0 ) {
	    sprintf( WriteBuf, "%c%s `%s' doesn't seem to have a log.\n", TagHead->form, TagHead->name, TagHead->board );
	    ok = 0;
	}
	if( Log2GameInProgress == 1 ) {
	    if( TheyInLog2 == 1 ) {
		sprintf( WriteBuf, "%c%s You cannot critique a game in progress where you're a player.\n", TagHead->form, TagHead->name );
		ok = 0;
	    } else if( MeInLog2 == 1 ) {
		sprintf( WriteBuf, "%c%s You cannot critique a game in progress where I'm a player.\n", TagHead->form, TagHead->name );
		ok = 0;
	    }
	}
	if( ok == 1 ) {
/* Keep log2 information already obtained in the new entry */
	    AddTagEntry( TTYPE_LOOK, TagHead->data, TagHead->name,
			 TagHead->board, TagHead->form );
	    sprintf( WriteBuf, "look -tagged %s", TagHead->name );
	    remove_tag_entry( 0 );
	} else {
	    remove_tag_entry( 1 );
	}
	QueueCommand( WriteBuf, 0 );
	return;
    }
    if( TagHead->data == NULL ) {
/* First line checks */
	if( strstr( str, "has no log" ) != NULL ) HasValidLog2 = 0;
	if( strstr( str, "I don't know anything" ) != NULL ) HasValidLog2 = 0;
	if( strstr( str, "The log is empty" ) != NULL ) HasValidLog2 = 0;
	if( strstr( str, "too many players" ) != NULL ) HasValidLog2 = 0;
	if( strstr( str, PlrName ) != NULL ) MeInLog2 = 1;
	if( strstr( str, TagHead->name ) != NULL ) TheyInLog2 = 1;
    } else {
/* Check if it's a complete game; the presence of brackets indicates this */
	if( strchr( str, '(' ) != NULL ) Log2GameInProgress = 0;
    }
/* Add received data to TagHead */
    add_tagged_data( str );
}

void process_tagged_look( str )
    char *str;
/* Deal with results of `look' command. */
{
    int ok, size;
    char *astart, *aend;

    if( str == NULL ) {
/* See if we found their email address */
	ok = 1;
	if( EmailAddr == NULL ) {
	    sprintf( WriteBuf, "%c%s I can't seem to find your email address.", TagHead->form, TagHead->name );
	    ok = 0;
	} else {
	    ok = write_log2();
	    if( ok == 1 ) {
		sprintf( WriteBuf, "%c%s I'll email a critique of the game on `%s' to %s when it's done.",
			 TagHead->form, TagHead->name,
			 TagHead->board, EmailAddr );
	    } else {
		sprintf( WriteBuf, "%c%s Sorry, I couldn't get the critiquer to
work.",
			 TagHead->form, TagHead->name );
	    }
	    free( (char *) EmailAddr );
	}
	remove_tag_entry( 1 );
	QueueCommand( WriteBuf, 0 );
	return;
    }
/* See if we can find their email address */
/* Note: doesn't do full RFC836 address processing yet */
    if( strncmp( str, "In real life:", 13 ) == 0 ) {
	astart = strchr( str, '<' ); aend = strchr( str, '>' );
	if( astart != NULL && aend != NULL ) {
	    size = (int) ( aend - astart );
	    if( size > 1 ) {
		EmailAddr = (char *) malloc( size );
		strncpy( EmailAddr, astart + 1, size - 1 );
		EmailAddr[size - 1] = '\0';
	    }
	}
    }
}

void process_tagged_log( str )
    char *str;
/* Deal with stuff from a `log' command. */
{
    if( str == NULL ) {
	remove_tag_entry( 0 );
	AddTagEntry( TTYPE_TRACK, NULL, "", "", ' ' );
	sprintf( WriteBuf, "track -tagged" );
	QueueCommand( WriteBuf, 0 );
	return;
    }
    if( strstr( str, TagHead->name ) != NULL ) {
	ProcessLogLine( str );
    }
}

void process_tagged_track( str )
    char *str;
/* Deal with stuff from a `track' command. */
{
    if( str == NULL ) {
	remove_tag_entry( 0 );
	ClearLFB();
	EndTaggedTrack();
	return;
    }
    ProcessTrackLine( str );
}

void process_tagged_block( nl )
    char *nl;
/* Deal with string inside tagged block; NULL means end of tagged block. */
{
    if( nl != NULL ) LPrint( "<- %s", nl );
    else LPrint( "Found [END] tag\n" );
    if( TagHead == NULL ) return;
    if( TagHead->ttype == TTYPE_LOG2 ) {
	process_tagged_log2( nl );
    } else if( TagHead->ttype == TTYPE_LOOK ) {
	process_tagged_look( nl );
    } else if( TagHead->ttype == TTYPE_LOG ) {
	process_tagged_log( nl );
    } else if( TagHead->ttype == TTYPE_TRACK ) {
	process_tagged_track( nl );
    }
}

void ProcessReadData()
/* Process the string received. */
{
    char c, *i, *lr, *j;
    char *nl;
    int l;

    lr = ReadBuf;
    *RBIn = '\0';
    for( i = ReadBuf; i < RBIn; i++ ) {
	c = *i;
	if( c == '\n' ) {
	    l = 0;
	    j = lr;
	    while( *j != '\n' )
		if( *j++ != '\015' ) l++;
	    nl = (char *) malloc( l + 2 );
	    if( nl == NULL ) {
		LPrint( "*** Error: no memory left in ProcessReadData()\n" );
		ExitWindow();
		exit( 22 );
	    }

	    j = lr;
	    l = 0;
	    while( *j != '\n' ) {
		if( *j != '\015' ) nl[l++] = *j;
		j++;
	    }
	    nl[l++] = '\n';
	    nl[l] = '\0';
	    if( strcmp( nl, "[BEGIN]\n" ) == 0 ) {
		start_tagging();
		InTaggedBlock = 1;
	    } else if( strcmp( nl, "[END]\n" ) == 0 ) {
		process_tagged_block( NULL );
		InTaggedBlock = 0;
	    } else if( InTaggedBlock == 1 ) {
		process_tagged_block( nl );
	    } else {
		add_received_string( nl );
	    }
	    lr = i + 1;
	}
    }
    for( l = 0; lr < RBIn; lr++ )
	ReadBuf[l++] = *lr;
    RBIn = ReadBuf + l;
}
