#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include "defs.h"
#include "struct.h"
#include "data.h"

/* bytes to reserve for outgoing packet to metaserver */
#define MAXMETABYTES 1024

/* maximum number of metaservers supported */
#define MAXMETASERVERS 3

/* minimum allowable delay between updates to metaserver */
#define META_MINIMUM_DELAY 60
/* Note: changing this may cause the metaserver to delist your server */

/* maximum delay between updates to metaserver */
#define META_MAXIMUM_DELAY 900

/* ship classes (wish these were in data.c/data.h) */
static char *ships[] = {"SC", "DD", "CA", "BB", "AS", "SB", "GA"};

/* structure of information about a single metaserver */
struct metaserver
{
    /* data items derived from metaservers file */
    char host[32];		/* address of metaserver (DNS)		*/
    int port;			/* port of metaserver			*/
    int minimum;		/* minimum update time			*/
    int maximum;		/* maximum update time			*/
    char ours[32];		/* DNS address of server 	 	*/
    char type[2];		/* server type code (B/P/C/H/?)		*/
    int pport;			/* server main player port (e.g. 2592)	*/
    int oport;			/* server observer player port		*/
    char comment[32];		/* comment string			*/

    /* our own data about the communication with the metaserver */
    int sock;                   /* our socket number                    */
    struct sockaddr_in address;	/* address of metaserver		*/
    time_t sent;		/* date time metaserver last updated	*/
    char prior[MAXMETABYTES];	/* prior packet sent			*/
} metaservers[MAXMETASERVERS];

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

/* 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 *here = packet;
    time_t now = time(NULL);
   
    /* 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++;
    }

    /* 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;

        /* count up the number of free slots and players */
        for (j=0; j<MAXPLAYER; j++)
            if (players[j].p_status == PFREE)
                nfree++;
            else
                nplayers++;

        /* if the free slots are zero, translate it to a queue length */
        if (nfree == 0) nfree = -queues[QU_PICKUP].count;
    
        /* 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",
            /* version */   "a",
            /* address */   m->ours,
            /* type    */   m->type,
            /* port    */   m->pport,
            /* observe */   m->oport,
            /* players */   nplayers,
            /* free    */   nfree,
            /* t-mode  */   status->tourn ? "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 || players[j].p_stats.st_tticks==0)
                continue;
            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   */   players[j].p_name,
                /* user   */   players[j].p_login,
                /* host   */   players[j].p_monitor );
            here += strlen(here);
        }

	/* 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);
        }
    }
}
