#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include "Connect.h"
#include "Parse.h"
#include "inet-udp.h"
#include "server.h"
#include "protocol.h"
#include "authorize.h"

#define DEBUG

void printError()
{
  while (Error_Exists)
    {
      if (Error == PARSE_BADKEYWORD)
	fprintf(stderr, "Bad keyword in line %d.\n", (int)Error_Info);
      else
	fprintf(stderr, "%s\n", Error_String(Error));
      Error_Pop();
    }
}

#define E(x) if (x) printError()

void MakeErrorPacket(P, type)
     P0Packet *P;
     Card32 type;
{
  P->type = P0_ERROR;
  P0_PutHeader(P);
  P0_PutCard32(P, type);
  P0_PutLength(P);
}

/*
 * Remove a context from all the lists and hash tables it occupies,
 * free the licenses it holds, close its connection, and free its memory.
 */
Obliterate(s, c)
     srvinfo *s;
     Context *c;
{
  RemoveStale(s, c);
  if (c->licensesHeld != c->licensesRequested)
    Dequeue(c);
  if (c->licensesHeld != 0)
    {
      ReleaseLicenses(c);
      fprintf(stdout, "timeout release\n");
      c->what->license->checkQueue = 1;
      s->checkQueues = 1;
    }
  free(c->user);
  free(c->machine);
  Connect_CloseConnection(c->activitySource, c->connection);
  FreeContext(c);
}

FlushTimeouts(s)
     srvinfo *s;
{
  Context *c;
  time_t t;

  t = time((time_t *)0);

  for (c = s->mostStale; c != NULL; c = s->mostStale)
    if (c->activity + (T_ping * N_MC) - t <= 0)
      Obliterate(s, c);
    else
      break;
}

CheckQueues(s)
     srvinfo *s;
{
  License *l;
  Pool *p;
  Context *c;
  Card32 bit;
  P0Packet P;
  Packet packet;
  char buffer[512]; /* XXX */

  P.packet = &packet;
  packet.packet = buffer;

  for (l = s->firstLicense; l != NULL; l = l->next)
    if (l->checkQueue && l->queue)
      {
	l->checkQueue = 0;
	for (p = l->firstPool, bit = 1;
	     p != NULL;
	     p = p->next, bit = bit<<1)
	  if (p->free > 0) /* assumes granularity == 1 */
	    for (c = l->queue; c != NULL; c = c->nextQueue)
	      if (c->authVec & bit)
		{
		  if ((c->licensesRequested - c->licensesHeld) <= p->free)
		    {
		      P.type = P0_ACCESS_UPGRADE;
		      P.context = c->contextNum;
		      packet.Source = c->activitySource;
		      packet.Destination = c->connection;
		      P0_PutHeader(&P);
		      P0_PutCard32(&P, c->licensesRequested);
		      P0_PutCard32(&P, c->licensesRequested);
		      P0_PutLength(&P);
		      Connect_SendPacket(&packet); /* XXX error trap */

		      p->free -= (c->licensesRequested - c->licensesHeld);
		      c->licensesHeld = c->licensesRequested;
		      c->what = p;
		      Dequeue(c);
		      if (p->free == 0)
			break; /* i.e., go to next pool */
		    }
		  else
		    break; /* save up until this one can be granted */
		           /* XXX other option - grant little by little */
		}
      }
}

main(argc, argv)
     char **argv;
{
  MasterBlock *block;
  ParseBlock *top;
  License *firstLicense;
  srvinfo *s;
  P0Packet Pin, Pout;
  Packet pin, pout;
  char pdata[1024]; /* XXX */
  Trap retval;
  Card32 timeout;
  int signedtimeout;
  int i, new, etype, reason;
  time_t now;
#ifdef DEBUG
  char hnam[50];
#endif

  Pin.packet = &pin;
  pout.packet = pdata;
  Pout.packet = &pout;

  E(Connect_Initialize());
  E(Connect_RegisterDomain(&inetudp));

  E(Parse_NewMasterBlock(&block));
  E(Parse_NewBlock(block, CONFIG, &top));
  E(Parse_RegisterKeywords(block, "server", mainwords));
  E(Parse_RegisterKeywords(block, "authorization", authorizeWords));
  E(Parse_ParseFile(block, "config"));

  Parse_SetCurrentBlock(block, top);
  E(Parse_Subblock(block));
  E(processLicenses(block, &firstLicense));

  Parse_SetCurrentBlock(block, top);
  E(Parse_GetCurrentData(block, "srvinfo", (caddr_t *)&s, 0));
  else
    /*
     * Open connections to other servers, listening port
     */
    for (i = 0; i < MAXCONNS; i++)
      if (i != s->us &&
	  s->fromConnections[i] != NOADDRESS &&
	  s->fromConnections[i] != s->toConnections[i]) /* XXX trap */
	E(Connect_OpenConnection(s->fromConnections[i],
				 s->toConnections[i]));

  s->firstLicense = firstLicense;
  s->mb = block;

  server_init(s);

  while (1)
    {
      /*
       * Process pending operations...
       *
       * First, if something has happened to free licenses,
       * check the appropriate queues to see if any can be granted.
       */
      if (s->checkQueues)
	{
	  s->checkQueues = 0;
	  CheckQueues(s);
	}

      /*
       * Prepare to hit the sack.
       */
      timeout = NOTIMEOUT;
      signedtimeout = 0;
      now = time((time_t *)0);

      /*
       * Compute earliest client timeout.
       */
      new = 0;

      if (s->nextTimeout)
	{
	  signedtimeout = s->nextTimeout->activity + (T_ping * N_MC) - now;
	  reason = CLIENTTIMEOUT;
	  if (signedtimeout <= 0)
	    {
	      FlushTimeouts(s);
	      continue;
	    }
	}

      if (signedtimeout == 0 || (s->nextWakeup - now < signedtimeout))
	{
	  signedtimeout = s->nextWakeup - now;
	  reason = SERVERTIMEOUT;
	  if (signedtimeout <= 0)
	    {
	      E(server_wakeup(s, &Pout));
	      continue;
	    }
	}

      timeout = signedtimeout;

      /*
       * Wait for a packet to come in, or for a timeout.
       */
      if (Connect_Wait(&pin, timeout * 1000))
	{
	  if (Error == CONNECT_NEWADDRESS)
	    {
	      new = 1;
	      Error_Pop();
	    }
	  else
	    {
	      if (Error == CONNECT_TIMEOUT)
		{
		  Error_Pop();
		  switch(reason)
		    {
		    case CLIENTTIMEOUT:
		      FlushTimeouts(s);
		      break;
		    case SERVERTIMEOUT:
		      E(server_wakeup(s, &Pout));
		      break;
		    }
		  continue;
		}
	      else
		{
		  printError();
		  continue;
		}
	    }
	}

      pout.Source = pin.Destination;
      pout.Destination = pin.Source;

      if (P0_GetHeader(&Pin))
	{
	  if (Error == PROTO_MISMATCH)
	    etype = E_BADPROTO;
	  else
	    etype = E_BADPACKET;

	  printError();

	  MakeErrorPacket(&Pout, etype);
	  if (new)
	    Connect_CloseConnection(pout.Source, pout.Destination);
	  free(pin.packet);
	  continue;
	}

      Pout.context = Pin.context;

#ifdef DEBUG
      Connect_AddressToName(pin.Source, hnam, sizeof(hnam));
      fprintf(stdout, "%s packet from %s\n", P0_PROTO_NAME(Pin.type), hnam);
#endif DEBUG

      if (ISSERVERPACKET(Pin.type))
	retval = server_packet(s, &Pin, &Pout, new);
      else
	retval = client_packet(s, &Pin, &Pout, new);

      E(retval);
      free(pin.packet);
    }
}
