/*
 * func.c,v 1.4 1994/01/30 11:42:25 franktor Exp
 */

#include "client.h"
#include <errno.h>

/*
 * Address and port number of the highlevel client-server
 */

char *		hclient_address		= DEFAULT_HCLIENT_ADDRESS;
int		hclient_port		= DEFAULT_HCLIENT_PORT;

/*
 * File descriptor used to connect to the high-level client-server
 */

int		server_fd		= (-1);

/*
 * The errno value which HC_Select() will set "by hand" if it fails.
 * It is EBADF if the connection is not initialized, or if it is closed by
 * the user with HC_Close().
 */

int		server_errno		= EBADF;

/*
 * Global pointer to packet ready to be fetched:
 */

HC_Packet *	incoming_packet		= (HC_Packet *) NULL;

/*
 * Global variable to help the struct-codec library:
 */

Boolean		selectOnWrite		= False;

/*
 * Local variable incremented for each OpenRequest
 */

static int	nextUniqueClientRef	= 0;



static void close_server ( LogLevel logLevel, const char *logMessage )
{
  free_socket (server_fd);
  close (server_fd);
  if (logMessage)
    LOG (facHigh, logLevel, "HC_Close: closed socket %d.", server_fd);
  server_fd = -1;
}

/*
 * HC_Initialise():
 *
 * Connects to the high-level client server.
 *
 * Returns True on success, otherwise False.
 */

Boolean HC_Initialise (void)
{
  connectionParameters conn;

  if (server_fd != (-1))
  {
    LOG(facHigh, llevExceptions, "HC_Initialise when connection were initialized.");
    return True;
  }
  /*
   * Read tcp/ip address of high-level client-server from configuration
   * file.  For now, I'm just setting the variable directly.
   */
  conn.method		= connect_tcpip;
  conn.u.ip.address	= hclient_address;
  conn.u.ip.port	= hclient_port;

  if ((server_fd = OpenConnection(&conn)) == (-1)) {
    LOG(facHigh, llevExceptions, "Failed to connect to high-level client-server.");
    server_errno = EBADF;
    return False;
  }

  server_errno = EIO;	/* If an error happens later, it is an I/O error */
  return True;
}

/*
 * HC_Close():
 *
 * Terminates connection to the high-level client-server.
 * All underlying connections are lost.
 *
 * Returns False if the connection not initialised.
 */

Boolean HC_Close (void)
{
  /*
   * More code will be added here later.
   */
  if (server_fd != (-1))
    closeConnection (server_fd);
  else if (server_errno != EBADF)
    LOG(facHigh, llevTrace, "HC_Close of bad (lost) connection.");
  else
  {
    LOG(facHigh, llevExceptions, "HC_Close of uninitialised connection.");
    server_errno = EBADF;
    return False;
  }
  server_fd = (-1);
  server_errno = EBADF;
  return True;
}

/*
 * HC_Send():
 *
 * Sends a packet to the high-level client-server.
 * The "ref" variable in struct HC_Packet specifies which connection further
 * from the client-server we want to send the packet through.
 * HC_Send() fills in "ref" when you send an hcOpenRequest, you should
 * use the resulting value in further communication on that connection.
 *
 * Returns True on success, otherwise False, with errno as from HC_Select().
 */

Boolean HC_Send (HC_Packet *pk)
{
  if (server_fd == (-1))
  {
    LOG(facHigh, llevExceptions, "HC_Send when connection not initialized.");
    errno = server_errno;
    return False;
  }
  if ( pk->type == hcOpenRequest )
    pk->ref = ++nextUniqueClientRef;
  if ( send_struct (server_fd, (void *) pk, &selectOnWrite) == sockStatOK )
    return True;

  /* Assume server_fd is no longer useable. */
  close_server (llevExceptions, "Failed to send packet, closing connection.");
  errno = server_errno = ECONNABORTED;
  return False;
}

/*
 * HC_Receive():
 *
 * Receives a packet from the high-level client-server, blocking until
 * one arrives.
 *
 * Returns True on success, otherwise False, with errno as from HC_Select().
 *
 * TODO:
 *   Maybe add a time-out value argument, to specify timeout on block.
 */

Boolean HC_Receive (HC_Packet **pk)
{
  if (server_fd == -1)
  {
    LOG(facHigh, llevExceptions, "HC_Receive when connection not initialized.");
    errno = server_fd;
    return False;
  }

  if (incoming_packet == (HC_Packet *) NULL)
  {
    /*
     * Block until a packet arrives.
     */
    while (HC_Select (0, NULL, NULL, NULL, NULL) == -1 && errno == EINTR);
    if (incoming_packet == (HC_Packet *) NULL)
      return False;
  }

  *pk = incoming_packet;
  incoming_packet = (HC_Packet *) NULL;
  return True;
}

/*
 * HC_Select():
 *
 * Waits for activity on specified file descriptors OR until a packet
 * is ready to be fetched by HC_Receive().
 * A positive return value indicates the number of ready descriptors
 * among those supplied.
 * 0 indicates that the time limit has expired.
 * -1 indicates failure, and that errno is set to indicate the error.
 * See manual page for select() for possible errno values.  In addition,
 * these errno values are used to indicate failure to read a packet:
 *	EIO			I/O error
 *	EBADF			Bad file number (ie uninitialised socket)
 *	ECONNABORTED		Software caused connection abort
 * -2 indicates that a packet has arrived from the high-level client-server,
 * and that HC_Receive() now can safely be called to fetch it without
 * risking a block.
 *
 * PROBLEMS:
 *   VMS select() does not handle write and exception fds, only read.
 *
 * BUGS:
 *   Handles errors poorly; has trouble distinguishing between lost connection,
 *   I/O error, bad user input, or a read<->select loop.
 *   In the last case I exit after 50 times just because I am nervous.   -hbf
 *
 *   Check if (i-1)<<2 is correct
 */

int HC_Select (int width, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
               struct timeval *timeout)
{
  fd_set readtmp, writetmp, excepttmp;
  int i, sel;
  int sfd_read_set, sfd_write_set, sfd_except_set;
  int tries_left;

  if (incoming_packet != (HC_Packet *) NULL)
  {
    LOG(facHigh, llevDebug, "HC_Select() when a packet already was ready.");
    return (-2);
  }

  if (server_fd <= 0)
  {
    errno = server_errno;
    return (-1);
  }

#ifdef VMS
  if (writefds || exceptfds)
  {
    LOG(facHigh, llevExceptions, "HC_Select(): VMS select does not handle read/write fds!");
    fprintf ( stderr, "HC_Select(): VMS select does not handle read/write fds!\n" );
    errno = EINVAL;
    return -1;
  }
#define writefds ((fd_set *)0)
#define exceptfds ((fd_set *)0)
#endif /* VMS */

  /* Make sure there is room for server_fd in the fd_sets */
  if (width <= server_fd)
  {
    if (readfds)   for (i = width; i <= server_fd; i++) FD_CLR (i, readfds);
    if (writefds)  for (i = width; i <= server_fd; i++) FD_CLR (i, writefds);
    if (exceptfds) for (i = width; i <= server_fd; i++) FD_CLR (i, exceptfds);
    width = server_fd + 1;
  }
  else if (writefds)
    FD_CLR (server_fd, writefds); /* Prevent the user from confusing us */
  

  for (tries_left = 50;  --tries_left >= 0; ) /* Should be for(;;) */
  {
    if (readfds)   readtmp   = *readfds;   else FD_ZERO (&readtmp);
    if (writefds)  writetmp  = *writefds;  else FD_ZERO (&writetmp);
    if (exceptfds) excepttmp = *exceptfds; else FD_ZERO (&excepttmp);

    FD_SET (server_fd, &readtmp);
    if (selectOnWrite)
      FD_SET (server_fd, &writetmp);
#ifndef VMS
    FD_SET (server_fd, &excepttmp);
#endif

    sel = select(width,
		 &readtmp,
		 ((writefds || selectOnWrite) ? &writetmp : NULL),
		 &excepttmp,
		 timeout);

    /* Remove server_fd from the returned fd_sets */
    if ((sfd_read_set = FD_ISSET (server_fd, &readtmp)))
      FD_CLR (server_fd, &readtmp);
    if ((sfd_write_set = (selectOnWrite && FD_ISSET (server_fd, &writetmp))))
      FD_CLR (server_fd, &writetmp);
    if ((sfd_except_set= FD_ISSET (server_fd, &excepttmp)))
      FD_CLR (server_fd, &excepttmp);

#ifdef DEBUG
    LOG(facHigh, llevTrace, "select(%d)=%d fds=%d%d%d w=%d, %s",
	server_fd, sel,
	sfd_read_set != 0, sfd_write_set != 0, sfd_except_set != 0,
	selectOnWrite, strerror (errno));
#endif

    if (sel <= 0 || !(sfd_read_set || sfd_write_set || sfd_except_set))
      goto Done;
    sel--;			/* Dont count server_fd in sel. */

    switch(handle_socket(server_fd,
			 sfd_read_set	? True : False,
			 sfd_write_set	? True : False,
			 sfd_except_set ? True : False,
			 &selectOnWrite))
    {
     case sockStatOK:
      LOG(facHigh, llevTrace, "handle_socket(): OK%s",
	  (selectOnWrite ? " (Requests select on write)" : ""));
      break;

     case sockStatClosed:
     case sockStatFailed:
      close_server (llevDebug, "handle_socket(): False, closing connection");
      errno = server_errno = ECONNABORTED;
      sel = (-1);
      goto Done;

     case sockStatPacketReady:
      LOG(facHigh, llevDebug, "handle_socket(): Packet ready");
      switch (read_struct(server_fd, (void **) &incoming_packet))
      {
       case sockStatOK:
	LOG(facHigh, llevTrace, "Read incoming packet.");
	if (incoming_packet)
	{
	  sel = (-2);
	  goto Done;
	}
	break;

       default:
	close_server (llevExceptions, "Failed to read packet, closing connection.");
	errno = ECONNABORTED;
	sel = (-1);
	goto Done;
      }
      break;
    }

    if (sel)
      goto Done;		/* Return number of other descriptors. */
  }
  errno = EIO; /* Or...? Maybe it was a bug in this program. */
  sel = (-1);

 Done:
  if (readfds)   *readfds   = readtmp;
  if (writefds)  *writefds  = writetmp;
  if (exceptfds) *exceptfds = excepttmp;
  return sel;
}
