#include "Connect.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/param.h>
#include <errno.h>

static int numDomains;
static ConnectDomain connectDomains[MAXDOMAINS];

static Card32 newAddress;
static Card32 addresses[MAXCONNECTIONS/32];
static Card32 one[32];

#define set_bit(b) addresses[(b)/32] |= one[(b)%32]
#define clr_bit(b) addresses[(b)/32] &= ~one[(b)%32]
#define tst_bit(b) (addresses[(b)/32] & one[(b)%32])
#define skipcard32(a) ((0xFFFFFFFF & ~addresses[(a)/32]) == 0)
#define hopbits(a, max) while (skipcard32(a) && (a) < max) (a) += 32

static Addr owner[NOFILE]; /* XXX use getdtablesize? */
static fd_set ref_read, ref_write, ref_except;
static int maxfds;

/* REGISTER: Connect_Initialize(); */

Trap Connect_Initialize()
{
  int i;
  Card32 j;

  j = 1;
  for (i = 0; i < 32; i++)
    {
      one[i] = j;
      j = j << 1;
    }

  for (i = 0; i < MAXCONNECTIONS/32; i++)
    addresses[i] = 0;

  set_bit(0); /* reserved */
  newAddress = 1;

  numDomains = 0;

  for (i = 0; i < NOFILE; i++)
    owner[i] = NOADDRESS;

  maxfds = 0;
  FD_ZERO(&ref_read);
  FD_ZERO(&ref_write);
  FD_ZERO(&ref_except);

  return OK;
}

/*
 * ERRORS:
 *
 * CONNECT_DOMAINEXISTS: FATAL
 * Could not register domain %s: domain is already registered.
 *
 * CONNECT_DOMAINLISTFULL: FATAL
 * Could not register domain %s: domain table is full.
 */
Trap Connect_RegisterDomain(domain)
     ConnectDomain *domain;
{
  int i;

  for (i = 0; i < numDomains; i++)
    if (!strcmp(domain->Domain, connectDomains[i].Domain))
      Return(CONNECT_DOMAINEXISTS, domain->Domain);

  if (numDomains == MAXDOMAINS)
    Return(CONNECT_DOMAINLISTFULL, domain->Domain);

  memcpy(&connectDomains[numDomains], domain, sizeof(ConnectDomain));
  numDomains++;

  /*
   * At the moment, the new domain exists. This is done so that
   * in initialization, the domain may call Connect routines.
   * However, if initialization fails, the domain is subsequently
   * destroyed.
   */
  
  if (connectDomains[numDomains - 1].Initialize(numDomains - 1) &&
      Error_Severity == S_FATAL)
    {
      numDomains--;
      return CHECK;
    }

  return OK;
}

/*
 * Converts an ASCII string to an address.
 *
 * ERRORS:
 *
 * CONNECT_DOMAINUNKNOWN: FATAL
 * The domain %s has never been registered.
 */
Trap Connect_NameToAddress(name, address)
     char *name;
     Addr *address;
{
  static char domain[MAXDOMAINLENGTH], *d, *p;
  int i;

  if (name == NULL)
    Return(GENERAL_NULLPOINTER, "Connect_NameToAddress");

  p = name;
  d = domain;

  while (*p != ':' && *p != '\0')
    *(d++) = *(p++);

  *d = '\0';
  p++;

  for (i = 0; i < numDomains; i++)
    if (!strcmp(domain, connectDomains[i].Domain))
      return(connectDomains[i].NameToAddress(i, p, address));

  Return(CONNECT_DOMAINUNKNOWN, domain);
}

/*
 * ERRORS:
 *
 * CONNECT_BADADDRESS: FATAL
 * An invalid address (%d) was passed in.
 */
Trap Connect_AddressToName(address, name, length)
     Addr address;
     char *name;
     int length;
{
  if (Connect_Domain(address) > numDomains)
    Return(CONNECT_BADADDRESS, address);

  strncpy(name, connectDomains[Connect_Domain(address)].Domain, length);
  length -= strlen(name);
  if (length > 0)
    {
      strcat(name, ":");
      length--;
    }

  return
    connectDomains[Connect_Domain(address)].AddressToName(address,
							  name, length);
}

Trap Connect_FreeAddress(address)
     Addr address;
{
  if (Connect_Domain(address) > numDomains)
    Return(CONNECT_BADADDRESS, address);

  return connectDomains[Connect_Domain(address)].FreeAddress(address);
}

Trap Connect_OpenConnection(from, to)
     Addr from, to;
{
  if (Connect_Domain(to) > numDomains)
    Return(CONNECT_BADADDRESS, to);

  return connectDomains[Connect_Domain(to)].OpenConnection(from, to);
}

Trap Connect_CloseConnection(from, to)
     Addr from, to;
{
  if (Connect_Domain(to) > numDomains)
    Return(CONNECT_BADADDRESS, to);

  return
    connectDomains[Connect_Domain(to)].CloseConnection(from, to);
}

Trap Connect_SendPacket(p)
     Packet *p;
{
  if (Connect_Domain(p->Destination) > numDomains)
    Return(CONNECT_BADADDRESS, p->Destination);

  return connectDomains[Connect_Domain(p->Destination)].SendPacket(p);
}

/*
 * This could be optimized.
 */
static Card32 nextAddress(a)
     Card32 a;
{
  Card32 s;

  if (!tst_bit(a))
    {
      set_bit(a);
      return a;
    }

  s = a;
  hopbits(a, MAXCONNECTIONS);

  if (a != s)
    a -= a%32;

  if (a >= MAXCONNECTIONS)
    {
      a = 0;
      hopbits(a, s);
      if (a >= s)
	return 0; /* no addresses are free */
    }

  while (tst_bit(a)) a++; /* guaranteed to terminate due to previous search */
  set_bit(a);
  return a;
}

/*
 * ERRORS:
 *
 * CONNECT_ADDRTBLFULL: FATAL
 * The maximum number of addresses available (%d) has been reached.
 */
Trap Connect_NewAddress(d, address)
     int d;
     Addr *address;
{
  Card32 a;

  a = nextAddress(newAddress);

  if (a == 0)
    Return(CONNECT_ADDRTBLFULL, MAXCONNECTIONS); /* ouch! */

  newAddress = a + 1; /* XXX think bug - compare MAXCONNECTIONS */
  		      /* test case - duh - run with small MAXCONNECTIONS */
  *address = Connect_Address(d, a);
  return OK;
}

/*
 * More comments...
 * Verify: called only from domain code, not from client code
 */
void Connect_KillAddress(address)
     Addr address;
{
  if (Connect_Index(address) != ANYADDRESS &&
      Connect_Index(address) < MAXCONNECTIONS)
    clr_bit(Connect_Index(address));
}

/*
 * Explain... Can client register descriptor? FD_SET of ref_'s?
 * Ah. Connect_SelectInterest...
 */
/*
 * ERRORS:
 *
 * CONNECT_FILDESTOOLARGE: FATAL
 * The file descriptor number passed (%d) was larger than supported.
 */
Trap Connect_RegisterDescriptor(address, descriptor)
     Addr address;
     int descriptor;
{
  if (descriptor > NOFILE)
    Return(CONNECT_FILDESTOOLARGE, descriptor);

  owner[descriptor] = address;
  maxfds = MAX(descriptor + 1, maxfds);
  return OK;
}

void Connect_FreeDescriptor(descriptor)
     int descriptor;
{
  
  owner[descriptor] = NOADDRESS;
  FD_CLR(descriptor, &ref_read);
  FD_CLR(descriptor, &ref_write);
  FD_CLR(descriptor, &ref_except);
}

/*
 * Why returns no errors?
 */
void Connect_SelectInterest(descriptor, read, write, except)
     int descriptor;
     int read, write, except;
{
  if (descriptor > NOFILE ||
      owner[descriptor] == NOADDRESS)
    return;

  switch(read)
    {
    case Set:
      FD_SET(descriptor, &ref_read);
      break;
    case Clear:
      FD_CLR(descriptor, &ref_read);
      break;
    default:
      break;
    }

  switch(write)
    {
    case Set:
      FD_SET(descriptor, &ref_write);
      break;
    case Clear:
      FD_CLR(descriptor, &ref_write);
      break;
    default:
      break;
    }

  switch(except)
    {
    case Set:
      FD_SET(descriptor, &ref_except);
      break;
    case Clear:
      FD_CLR(descriptor, &ref_except);
      break;
    default:
      break;
    }
}

/*
 * ERRORS:
 *
 * CONNECT_SELECT: FATAL
 * Select returned an unexpected error (errno %d); the call was not completed.
 *
 * CONNECT_TIMEOUT: INFO
 * Connect returned due to a timeout rather than an incoming packet.
 *
 * CONNECT_SOCKETREAD: WARNING
 * A socket (%d) is ready for reading for which no packeter was registered.
 *
 * CONNECT_CONFUSED: FATAL
 * Select said there was something to do, but there wasn't.
 */
Trap Connect_Wait(packet, timeout)
     Packet *packet;
     Card32 timeout;
{
  fd_set readfds, writefds, exceptfds;
  static int nfound = 0, first;
  int i;
  struct timeval t;

  /*
   * The purpose of nfound is to help round-robin servicing
   * of connections.
   */
  if (nfound == 0)
    {
      if (timeout != NOTIMEOUT)
	{
	  t.tv_sec = timeout / 1000;
	  t.tv_usec = (timeout % 1000) * 1000;
	}

      memcpy(&readfds, &ref_read, sizeof(fd_set));
      memcpy(&writefds, &ref_write, sizeof(fd_set));
      memcpy(&exceptfds, &ref_except, sizeof(fd_set));

      first = 0;
      nfound = select(maxfds, &readfds, &writefds, &exceptfds,
		      (timeout == NOTIMEOUT ? NULL : &t));

      switch(nfound)
	{
	case -1:
	  nfound = 0;
	  Return(CONNECT_SELECT, errno);
	  break;
	case 0:
	  Return(CONNECT_TIMEOUT, NULL);
	  break;
	}
   }

  for (i = first; i < NOFILE; i++)
    if (FD_ISSET(i, &readfds))
      {
	first = i + 1;
	nfound--;

	if (owner[i] == NOADDRESS)
	  Return(CONNECT_SOCKETREAD, i);

	/* XXX conditionalize time */
	packet->when = time(0);
	return connectDomains[Connect_Domain(owner[i])].
	  ReceivePacket(i, owner[i], packet);
      }

  /* Hmmmm... */
  nfound = 0;
  Return(CONNECT_CONFUSED, NULL);
}
