// all server side masterserver and pinging functionality

#include "cube.h"

static ENetSocket mssock = ENET_SOCKET_NULL;

static void
httpgetsend(ENetAddress &ad, OFString *hostname, OFString *req, OFString *ref,
    OFString *agent)
{
	if (ad.host == ENET_HOST_ANY) {
		[OFStdOut writeFormat:@"looking up %@...\n", hostname];
		enet_address_set_host(&ad, hostname.UTF8String);
		if (ad.host == ENET_HOST_ANY)
			return;
	}
	if (mssock != ENET_SOCKET_NULL)
		enet_socket_destroy(mssock);
	mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL);
	if (mssock == ENET_SOCKET_NULL) {
		printf("could not open socket\n");
		return;
	}
	if (enet_socket_connect(mssock, &ad) < 0) {
		printf("could not connect\n");
		return;
	}
	ENetBuffer buf;
	OFString *httpget = [OFString stringWithFormat:@"GET %@ HTTP/1.0\n"
	                                               @"Host: %@\n"
	                                               @"Referer: %@\n"
	                                               @"User-Agent: %@\n\n",
	                              req, hostname, ref, agent];
	buf.data = (void *)httpget.UTF8String;
	buf.dataLength = httpget.UTF8StringLength;
	[OFStdOut writeFormat:@"sending request to %@...\n", hostname];
	enet_socket_send(mssock, NULL, &buf, 1);
}

static void
httpgetrecieve(ENetBuffer &buf)
{
	if (mssock == ENET_SOCKET_NULL)
		return;
	enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
	if (enet_socket_wait(mssock, &events, 0) >= 0 && events) {
		int len = enet_socket_receive(mssock, NULL, &buf, 1);
		if (len <= 0) {
			enet_socket_destroy(mssock);
			mssock = ENET_SOCKET_NULL;
			return;
		}
		buf.data = ((char *)buf.data) + len;
		((char *)buf.data)[0] = 0;
		buf.dataLength -= len;
	}
}

static uchar *
stripheader(uchar *b)
{
	char *s = strstr((char *)b, "\n\r\n");
	if (!s)
		s = strstr((char *)b, "\n\n");
	return s ? (uchar *)s : b;
}

static ENetAddress masterserver = { ENET_HOST_ANY, 80 };
static int updmaster = 0;
static OFString *masterbase;
static OFString *masterpath;
static uchar masterrep[MAXTRANS];
static ENetBuffer masterb;

static void
updatemasterserver(int seconds)
{
	// send alive signal to masterserver every hour of uptime
	if (seconds > updmaster) {
		OFString *path = [OFString
		    stringWithFormat:@"%@register.do?action=add", masterpath];
		httpgetsend(masterserver, masterbase, path, @"cubeserver",
		    @"Cube Server");
		masterrep[0] = 0;
		masterb.data = masterrep;
		masterb.dataLength = MAXTRANS - 1;
		updmaster = seconds + 60 * 60;
	}
}

static void
checkmasterreply()
{
	bool busy = mssock != ENET_SOCKET_NULL;
	httpgetrecieve(masterb);
	if (busy && mssock == ENET_SOCKET_NULL)
		printf("masterserver reply: %s\n", stripheader(masterrep));
}

uchar *
retrieveservers(uchar *buf, int buflen)
{
	OFString *path =
	    [OFString stringWithFormat:@"%@retrieve.do?item=list", masterpath];
	httpgetsend(
	    masterserver, masterbase, path, @"cubeserver", @"Cube Server");
	ENetBuffer eb;
	buf[0] = 0;
	eb.data = buf;
	eb.dataLength = buflen - 1;
	while (mssock != ENET_SOCKET_NULL)
		httpgetrecieve(eb);
	return stripheader(buf);
}

static ENetSocket pongsock = ENET_SOCKET_NULL;
static OFString *serverdesc;

void
serverms(int mode, int numplayers, int minremain, OFString *smapname,
    int seconds, bool isfull)
{
	checkmasterreply();
	updatemasterserver(seconds);

	// reply all server info requests
	ENetBuffer buf;
	ENetAddress addr;
	uchar pong[MAXTRANS], *p;
	int len;
	enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
	buf.data = pong;
	while (enet_socket_wait(pongsock, &events, 0) >= 0 && events) {
		buf.dataLength = sizeof(pong);
		len = enet_socket_receive(pongsock, &addr, &buf, 1);
		if (len < 0)
			return;
		p = &pong[len];
		putint(p, PROTOCOL_VERSION);
		putint(p, mode);
		putint(p, numplayers);
		putint(p, minremain);
		OFString *mname =
		    [OFString stringWithFormat:@"%@%@",
		              (isfull ? @"[FULL] " : @""), smapname];
		sendstring(mname, p);
		sendstring(serverdesc, p);
		buf.dataLength = p - pong;
		enet_socket_send(pongsock, &addr, &buf, 1);
	}
}

void
servermsinit(OFString *master_, OFString *sdesc, bool listen)
{
	const char *master = master_.UTF8String;
	const char *mid = strstr(master, "/");
	if (!mid)
		mid = master;
	masterpath = @(mid);
	masterbase = [[OFString alloc] initWithUTF8String:master
	                                           length:mid - master];
	serverdesc = sdesc;

	if (listen) {
		ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
		pongsock =
		    enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, &address);
		if (pongsock == ENET_SOCKET_NULL)
			fatal(@"could not create server info socket\n");
	}
}
