#include <stdio.h>
#include <stdlib.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 "defs.h"
#include "struct.h"
#include "data.h"
#include "solicit.h"

/* our copy of .metaservers file */
static struct metaserver metaservers[MAXMETASERVERS];

/* initialisation done flag */
static int initialised = 0;

/* remove non-printable chars in a string.  Lifted from tools/players.c */
/* with a minor addition:  error checking on the strdup */
static char *name_fix(char *name)
{
   char *new = strdup(name);                    /* xx, never freed */
   register
   char *r = new;

   if(!new) return new;                         /* don't play with null ptr */

   while(*name){
      *r++ = (*name <= 32)?'_':*name;
      name++;
   }
   *r = 0;
   return new;
}

/* attach to a metaserver, i.e. prepare the socket */
static int udp_attach(struct metaserver *m)
{
  /* create the socket structure */
  m->sock = socket(AF_INET, SOCK_DGRAM, 0);
  if (m->sock < 0) {
    perror("solicit: udp_attach: socket");
    return 0;
  }
  
  /* bind the local socket */
  m->address.sin_addr.s_addr = INADDR_ANY;
  m->address.sin_family      = AF_INET;
  m->address.sin_port        = 0;
  if (bind(m->sock,(struct sockaddr *)&m->address, sizeof(m->address)) < 0) {
    perror("solicit: udp_attach: bind");
    return 0;
  }
  
  /* build the destination address */
  m->address.sin_family = AF_INET;
  m->address.sin_port = htons(m->port);
  
  /* attempt numeric translation first */
  if ((m->address.sin_addr.s_addr = inet_addr(m->host)) == -1) {
    struct hostent *hp;
    
    /* then translation by name */
    if ((hp = gethostbyname(m->host)) == NULL) {
      /* if it didn't work, return failure and warning */
      fprintf(stderr, "solicit: udp_attach: host %s not known\n", 
	      m->host);
      return 0;
    } else {
      m->address.sin_addr.s_addr = *(long *) hp->h_addr;
    }
  }
  
  return 1;
}

/* transmit a packet to the metaserver */
static int udp_tx(struct metaserver *m, char *buffer, int length)
{
  /* send the packet */
  if (sendto(m->sock, buffer, length, 0, (struct sockaddr *)&m->address, 
	     sizeof(m->address)) < 0) {
    perror("solicit: udp_tx: sendto");
    return 0;
  }
  
  return 1;
}

void solicit(int force)
{
  int i, nplayers=0, nfree=0; 
  char packet[MAXMETABYTES];
/*  static char prior[MAXMETABYTES];*/
  char *fixed_name, *fixed_login;
  char *here = packet;
  time_t now = time(NULL);
  int gamefull = 0;              /* is the game full? */
  int isrsa = 0;                 /* is this server RSA? */

  /* perform first time initialisation */ 
  if (initialised == 0) {
    FILE *file;
    
    /* clear metaserver socket list */
    for (i=0; i<MAXMETASERVERS; i++) metaservers[i].sock = -1;
    
    /* open the metaserver list file */
    file = fopen(".metaservers", "r"); /* ??? LIBDIR prefix? */
    if (file == NULL) {
      initialised++;
      return;
    }
    
    /* read the metaserver list file */
    for (i=0; i<MAXMETASERVERS; i++) {
      struct metaserver *m = &metaservers[i];
      
      /* if end of file reached, stop */
      if (feof(file)) break;
      
      /* scan the line */
      fscanf(file, "%s %d %d %d %s %s %d %d %s/n", m->host, &m->port,
	     &m->minimum, &m->maximum, m->ours, m->type, &m->pport,
	     &m->oport, m->comment);
      
      /* force minimum and maximum delays (see note on #define) */
      if (m->minimum < META_MINIMUM_DELAY)
	m->minimum = META_MINIMUM_DELAY;
      if (m->maximum > META_MAXIMUM_DELAY)
	m->maximum = META_MAXIMUM_DELAY;
      
      /* attach to the metaserver (DNS lookup is only delay) */
      udp_attach(m);
      /* place metaserver addresses in /etc/hosts to speed this */
      /* use numeric metaserver address to speed this */
      
      /* initialise the other parts of the structure */
      m->sent = 0;
      strcpy(m->prior, "");
    }
    initialised++;
    fclose(file);
  }
  
  /* update each metaserver */
  for (i=0; i<MAXMETASERVERS; i++) {
    struct metaserver *m = &metaservers[i];
    int j;
    
    /* skip empty metaserver entries */
    if (m->sock == -1) continue;
    
    /* if we told metaserver recently, don't speak yet */
    if (!force)
      if ((now-m->sent) < m->minimum) continue;
    
    /* don't remake the packet unless necessary */
    if ( here == packet ) {
      
      /* count up the number of free slots and players for pickup games */
      /* don't want to go all the way to MAX_PLAYERS.  Queue starts at */
      /* queues[QU_PICKUP].high_slot players. */
      for (j=0; j<queues[QU_PICKUP].high_slot; j++)
	if (players[j].p_status == PFREE)
	  nfree++;
	else
	  nplayers++;
      
      /* if the free slots are zero, translate it to a queue length */
      /* and report that the game is full */
      if (nfree == 0) 
	{
	  nfree = -queues[QU_PICKUP].count;
	  gamefull++;
	}      

#ifdef RSA
      isrsa++;     /* this is an RSA server */
#endif


      /* build start of the packet, the server information */
      sprintf(here, "%s\n%s\n%s\n%d\n%d\n%d\n%d\n%s\n%s\n%s\n%s\n",
	      /* version */   "b",
	      /* address */   m->ours,
	      /* type    */   m->type,
	      /* port    */   m->pport,
	      /* observe */   m->oport,
	      /* players */   nplayers,
	      /* free    */   nfree,
	      /* t-mode  */   status->tourn ? "y" : "n",
	      /* RSA     */   isrsa ? "y" : "n",
	      /* full    */   gamefull ? "y" : "n",
	      /* comment */   m->comment
	      );
      here += strlen(here);
      
      /* now append per-player information to the packet */
      for (j=0; j<MAXPLAYER; j++) {
	/* ignore free slots */
        if (players[j].p_status == PFREE ||
#ifdef LTD_STATS
            ltd_ticks(&(players[j]), LTD_TOTAL) == 0)
#else
            players[j].p_stats.st_tticks == 0)
#endif
	  continue;
        fixed_name = name_fix(players[j].p_name);  /*get rid of non-printables*/
        fixed_login = name_fix(players[j].p_login);
	sprintf(here, "%c\n%c\n%d\n%d\n%s\n%s@%s\n",
                /* number */   players[j].p_mapchars[1], 
                /* team   */   players[j].p_mapchars[0],
                /* class  */   players[j].p_ship.s_type,
		/* ??? note change from design, ship type number not string */
                /* rank   */   players[j].p_stats.st_rank,
		/* ??? note change from design, rank number not string */
                /* name   */   (fixed_name) ? fixed_name : players[j].p_name,
                /* user   */   (fixed_login) ? fixed_login : players[j].p_login,
                /* host   */   players[j].p_monitor );
	here += strlen(here);
        free(fixed_name);      /*because name_fix malloc()s a string */
        free(fixed_login);
      }
    }
    
    /* if we have exceeded the maximum time, force an update */
    if ((now-m->sent) > m->maximum) force=1;
    
    /* if we are not forcing an update, and nothing has changed, drop */
    if (!force)
      if (!strcmp(packet, m->prior)) continue;
    
    /* send the packet */
    if (udp_tx(m, packet, here-packet)) {
      m->sent=time(NULL);
      strcpy(m->prior, packet);
    }
  }
}
