/* Copyright (c) 1993 Stephen F. White */

#include "cool.h"
#include "proto.h"
#include "buf.h"
#include "netio.h"
#include "servers.h"
#include "servers_private.h"

Server *servers = 0;
List *server_list;

static int serverconfig (const char *line, int lineno);
static void serv_addtolist (Serverid id);
static void serv_addtocfg (Server * new);
static char configfilename[MAX_PATH_LEN];

static void serv_addtocfg (Server * new)
{
  FILE *f;

  if (!(f = fopen (configfilename, "ab"))) {
    writelog ();
    perror (configfilename);
  } else {
    fprintf (f, "server %s %s %d\n", new->name, new->hostname, new->port);
    fclose (f);
  }
}

#define MATCHES(A, B) (!strncasecmp(A, B, strlen(B)))

int read_config (const char *filename)
{
  char line[512];
  int lineno = 0;
  FILE *configfile;
  int ok;

  strcpy (configfilename, filename);
  if (!(configfile = fopen (filename, "rb"))) {
    writelog ();
    perror (filename);
    return -1;
  }
  servers = 0;
  server_list = list_new (0);
  while (!feof (configfile)) {  /* while there's more input */
    if (!fgets (line, sizeof (line), configfile)) {     /* get a line */
      break;
    }
    lineno++;
    ok = 1;
    switch (line[0]) {
    case '#':
    case '\n':
    case '\0':
      /* if it starts with '#', or is blank, skip it */
      break;
    case 'c':                  /* cache, corefile */
      if (MATCHES (line, "cache ")) {
        if (cacheconfig (line + 6, lineno)) {
          fclose (configfile);
          return -3;
        }
      } else if (MATCHES (line, "corefile")) {
        corefile = 1;
      } else {
        ok = 0;
      }
      break;
    case 's':                  /* server */
      if (MATCHES (line, "server ")) {
        if (serverconfig (line + 7, lineno)) {
          fclose (configfile);
          return -2;
        }
	    } else if (MATCHES(line, "sleep_to_refresh ")) {
		sleep_to_refresh = atoi(line + 17);
      } else {
        ok = 0;
      }
      break;
    case 'm':                  /* max_age, max_ticks */
      if (MATCHES (line, "max_age ")) {
        max_age = atoi (line + 8);
      } else if (MATCHES (line, "max_ticks ")) {
        max_ticks = atoi (line + 10);
      } else {
        ok = 0;
      }
	    break;
    case 'p':                  /* player_port, promiscuous */
      if (MATCHES (line, "player_port ")) {
        player_port = atoi (line + 12);
      } else if (MATCHES (line, "promiscuous")) {
        promiscuous = 1;
      } else {
        ok = 0;
      }
      break;
    case 'r':                  /* registration */
      if (MATCHES (line, "registration")) {
        registration = 1;
      } else {
        ok = 0;
      }
      break;
    case 'v':                  /* verify_servers */
      if (MATCHES (line, "verify_servers")) {
        verify_servers = 1;
        continue;
      } else {
        ok = 0;
      }
      break;
    default:
      ok = 0;
    }
    /* unknown config command */
    if (!ok) {
      writelog ();
      fprintf (stderr, "Couldn't read config file, line %d\n", lineno);
      fclose (configfile);
      return -4;
    }
  }
  if (!servers) {
    writelog ();
    fprintf (stderr, "Must be at least one servers entry in config file\n");
    fclose (configfile);
    return -5;
  }
  yo_port = servers->port;
  fclose (configfile);
  return 0;
}

static int serverconfig (const char *line, int lineno)
{
  Server *s;
  struct hostent *h;
  static Serverid sid = 0;
  static Server *prev = 0;
  char name[21], hostname[41];
  short port;

  if (sscanf (line, "%20s %40s %hd", name, hostname, &port) != 3) {
    writelog ();
    fprintf (stderr, "Bad server entry in config file, line %d\n", lineno);
    return -1;
  } else if (!(h = gethostbyname (hostname))) {
    writelog ();
    fprintf (stderr, "Host '%s' not found, line %d\n", hostname, lineno);
    return -2;
  } else if (h->h_addrtype != AF_INET || h->h_length != 4) {
    writelog ();
    fprintf (stderr, "Host '%s' not an Internet host, line %d\n",
      hostname, lineno);
    return -3;
  } else {
    s = MALLOC (Server, 1);
    strcpy (s->name, name);
    strcpy (s->hostname, hostname);
    s->port = port;
    s->addr = ntohl (*((unsigned long *) h->h_addr_list[0]));
    s->id = sid++;
    s->last_msgid = -1;
    s->connected = 0;
    s->next = 0;
    if (prev) {
      prev->next = s;
    } else {
      servers = s;
    }
    prev = s;
    serv_addtolist (s->id);
  }
  return 0;
}

const char *serv_id2name (Serverid id)
{
  Server *s;

  for (s = servers; s; s = s->next) {
    if (s->id >= 0 && s->id == id) {
      return s->name;
    }
  }
  return "";
}

Serverid serv_name2id (const char *name)
{
  Server *s;

  for (s = servers; s; s = s->next) {
    if (!strcasecmp (s->name, name)) {
      return s->id;
    }
  }
  return -1;
}

void serv_id2entry (Serverid server, char *buf)
{
  Server *s;

  for (s = servers; s; s = s->next) {
    if (s->id == server) {
      sprintf (buf, "%s %s %d\n", s->name, s->hostname, s->port);
      return;
    }
  }
  buf[0] = '\0';
}

Server *serv_add (struct sockaddr_in *from, const char *name)
{
  Server *s, *prev = 0, *new;
  struct hostent *h;
  int newid = 0;

  if (serv_addr2server (from) || serv_name2id (name) >= 0) {
    return 0;
  }
  for (s = servers; s; s = s->next) {
    prev = s;
    if (s->id >= newid) {
      newid = s->id + 1;
    }
  }

  new = MALLOC (Server, 1);

  new->addr = ntohl (from->sin_addr.s_addr);
  new->port = ntohs (from->sin_port);
  strncpy (new->name, name, 30);
  if (!(h = gethostbyaddr ((void *) &(from->sin_addr.s_addr),
        sizeof (from->sin_addr.s_addr), AF_INET))) {
    writelog ();
    fprintf (stderr, "%s", addr_htoa (new->addr));
    fprintf (stderr, ":  gethostbyaddr() failed\n");
    FREE (new);
    return 0;
  }
  strncpy (new->hostname, h->h_name, 40);
  new->last_msgid = -1;
  writelog ();

  fprintf (stderr, "SERVER %s (%s %d) added successfully.\n",
    new->name, new->hostname, new->port);
  new->id = newid;
  serv_addtolist (newid);
  serv_addtocfg (new);

  if (prev) {
    prev->next = new;
  } else {
    servers = new;
  }

  return new;
}

static void serv_addtolist (Serverid id)
{
  Var el;

  el.type = OBJ;
  el.v.obj.id = 0;              /* add #0@remoteserver */
  el.v.obj.server = id;
  server_list = list_setadd (server_list, el);
}

void writelog (void)
{
  long t = time ((time_t *) 0);
  char *s = ctime ((time_t *) & t);
  s[19] = '\0';                 /* remove newline */

  fprintf (stderr, "%s:  ", s + 4);
}

Server *serv_addr2server (struct sockaddr_in *sock)
{
  Server *s;

  for (s = servers; s; s = s->next) {
    if (s->addr == ntohl (sock->sin_addr.s_addr)
      && s->port == ntohs (sock->sin_port)) {
      return s;
    }
  }
  return 0;
}

Server *serv_id2server (Serverid server)
{
  Server *s;

  for (s = servers; s; s = s->next) {
    if (s->id == server) {
      return s;
    }
  }
  return 0;
}

Server *serv_name2server (const char *name)
{
  Server *s;

  for (s = servers; s; s = s->next) {
    if (!strcasecmp (s->name, name)) {
      return s;
    }
  }
  return 0;
}

int verify_server (Server * s, struct sockaddr_in *from)
{
  struct hostent *h;

  if (!(h = gethostbyaddr ((void *) &from->sin_addr.s_addr,
        sizeof (from->sin_addr.s_addr), AF_INET))) {
    writelog ();
    fprintf (stderr, "Failed verify host %s as %s (%s %d)",
      addr_htoa (ntohl (from->sin_addr.s_addr)), s->name, s->hostname,
      s->port);
    fprintf (stderr, ":  gethostbyaddr() failed\n");
    return 0;
  } else if (strcasecmp (h->h_name, s->hostname)
    || ntohs (from->sin_port) != s->port) {
    writelog ();
    fprintf (stderr, "Host %s %d tried to connect as server %s (%s %d)\n",
      h->h_name, ntohs (from->sin_port), s->name, s->hostname, s->port);
    return 0;
  } else {
    return 1;
  }
}

/*
 * serv_discardmsg()
 *
 * Returns non-zero if the system should discard a received message.
 */

int serv_discardmsg (Serverid server, int msgid)
{
  Server *s = serv_id2server (server);

  if (!s) {
    return -1;
  }
  if (msgid > s->last_msgid) {
    s->last_msgid = msgid;
    return 0;
  } else {
    return 1;
  }
}