#include "bothdefs.h"

#ifdef EMAILSERVER

#include "quakedef.h"
#include "winquake.h"

//FIXME: the DELE command's effects arn't properly checked.
//FIXME: no UIDL command

//FIXME: remove sequential naming.

char *MD5_GetPop3APOPString(char *timestamp, char *secrit);






#define HASH_FILESYSTEM
#ifdef HASH_FILESYSTEM
//#include "hash.h"

#define Hash_BytesForBuckets(b) (sizeof(bucket_t)*b)

#define STRCMP(s1,s2) (((*s1)!=(*s2)) || strcmp(s1+1,s2+1))	//saves about 2-6 out of 120 - expansion of idea from fastqcc
typedef struct bucket_s {
	void *data;
	char *keystring;
	struct bucket_s *next;
} bucket_t;
typedef struct hashtable_s {
	int numbuckets;
	bucket_t **bucket;
} hashtable_t;

void Hash_InitTable(hashtable_t *table, int numbucks, void *mem);	//mem must be 0 filled. (memset(mem, 0, size))
int Hash_Key(char *name, int modulus);
void *Hash_Get(hashtable_t *table, char *name);
void *Hash_GetKey(hashtable_t *table, int key);
void *Hash_GetNext(hashtable_t *table, char *name, void *old);
void *Hash_Add(hashtable_t *table, char *name, void *data);
void *Hash_Add2(hashtable_t *table, char *name, void *data, bucket_t *buck);
void *Hash_AddKey(hashtable_t *table, int key, void *data);
void Hash_Remove(hashtable_t *table, char *name);
#endif








#ifdef _WIN32
#define EWOULDBLOCK	WSAEWOULDBLOCK
#define EMSGSIZE	WSAEMSGSIZE
#define ECONNRESET	WSAECONNRESET
#define ECONNABORTED	WSAECONNABORTED
#define ECONNREFUSED	WSAECONNREFUSED
#define EADDRNOTAVAIL	WSAEADDRNOTAVAIL


#define qerrno WSAGetLastError()
#else
#define qerrno errno

#define MSG_PARTIAL 0
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <errno.h>

#include <unistd.h>

#define closesocket close
#define ioctlsocket ioctl
#endif


#define POP3_PORT	110
#define POP3_TIMEOUT	30

static qboolean pop3active;
static int pop3serversocket;

typedef struct {
	char filename[MAX_QPATH];
	int size;
	qboolean deleted;
	bucket_t bucket;
} pop3message_t;

typedef struct svpop3client_s {
	struct svpop3client_s *next;

	int socket;

	float timeout;

	char *messagelump;
	int messagelumppos;
	int messagelumplen;
	qboolean messagelumphitbody;
	int messagelumplines;	//lines to send past header

	int nummessages;
	int totalsize;

	char greeting[64];
	char username[64];
	qboolean loggedin;

	char inmessagebuffer[1024];
	int inmessagelen;
	char outmessagebuffer[1024];
	int outmessagelen;
	qboolean dropwhensent;

#define NUMBUCKETS 64
	hashtable_t emails;
	bucket_t *bucketpointer[NUMBUCKETS];
} svpop3client_t;
static svpop3client_t *svpop3client;





static void POP3_ServerInit(void)
{
	struct sockaddr_in address;
	unsigned long _true = true;
	int port = POP3_PORT;

	if ((pop3serversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
	{
		Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno));
	}

	if (ioctlsocket (pop3serversocket, FIONBIO, &_true) == -1)
	{
		Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
	}

	address.sin_family = AF_INET;
	address.sin_addr.s_addr = INADDR_ANY;

	if (port == PORT_ANY)
		address.sin_port = 0;
	else
		address.sin_port = htons((short)port);
	
	if( bind (pop3serversocket, (void *)&address, sizeof(address)) == -1)
	{
		closesocket(pop3serversocket);
		return;
	}
	
	listen(pop3serversocket, 3);

	pop3active = true;


	IWebPrintf("POP3 server is running\n");
	return;
}

static void POP3_ServerShutdown(void)
{
	closesocket(pop3serversocket);
	pop3active = false;
}

static void SV_POP3_QueueMessage(svpop3client_t *cl, char *msg)
{
	int len = strlen(msg);
	if (len + cl->outmessagelen > sizeof(cl->outmessagebuffer)-1)
		len = sizeof(cl->outmessagebuffer)-1 - cl->outmessagelen;
	Q_strncpyz(cl->outmessagebuffer+cl->outmessagelen, msg, len+1);
	cl->outmessagelen += len;
}

static void POP3_NewConnection(int socket)
{
	unsigned long _true = true;
	svpop3client_t *newcl;
	newcl = IWebMalloc(sizeof(svpop3client_t));
	if (!newcl)	//bother
	{
		closesocket(socket);
		return;
	}
	if (ioctlsocket (socket, FIONBIO, &_true) == -1)
	{
		closesocket(socket);
		return;	
	}
	memset(newcl, 0, sizeof(svpop3client_t));

	newcl->socket = socket;
	newcl->next = svpop3client;
	svpop3client = newcl;

	newcl->timeout = realtime + POP3_TIMEOUT;

	*newcl->outmessagebuffer = '\0';

	sprintf(newcl->greeting, "<%i.%i.%i.%i.%i.%i.%i>", rand(), rand(), rand(), rand(), rand(), rand(), rand());
//	_true = strlen(newcl->greeting);

printf("newclient\n");

	SV_POP3_QueueMessage(newcl, va("+OK %s\r\n", newcl->greeting));
}

static int SV_POP3_AddMessage(char *filename, int flags, void *incl)
{
	FILE *f;
	svpop3client_t *cl = incl;
	pop3message_t *msg;

	f = fopen(filename, "rb");
	if (!f)
		return true;	//shouldn't happen

	msg = IWebMalloc(sizeof(pop3message_t));
	if (!msg)
	{
		fclose(f);
		return false;
	}

	Q_strncpyz(msg->filename, filename, sizeof(msg->filename));
	msg->deleted = false;

	fseek(f, 0, SEEK_END);
	msg->size = ftell(f);
	fclose(f);

	cl->totalsize+=msg->size;
	cl->nummessages++;

	Hash_Add2(&cl->emails, va("%i", ++cl->nummessages), msg, &msg->bucket);

	return true;
}

static void SV_POP3_CountMessages(svpop3client_t *cl)
{
	Hash_InitTable(&cl->emails, NUMBUCKETS, cl->bucketpointer);
	cl->totalsize=0;
	Sys_EnumerateFiles(".", va("emails/%s/*.eml", cl->username), SV_POP3_AddMessage, cl);
}

static pop3message_t *SV_POP3_GetMessage(svpop3client_t *cl, int num)
{
	pop3message_t *msg;

	msg = IWebMalloc(sizeof(pop3message_t));
	if (!msg)
		return NULL;

	if (msg->deleted)
		return NULL;

	msg = Hash_Get(&cl->emails, va("%i", num));

	return msg;
}

static void SV_POP3_CleanUp(svpop3client_t *cl, qboolean dodelete)	//closes the messages list created by SV_POP3_CountMessages
{
	pop3message_t *msg;
	int mn;
	for (mn = 1; mn <= cl->nummessages; mn++)
	{
		msg = SV_POP3_GetMessage(cl, mn);
		if (!msg)
			continue;

		if (dodelete && msg->deleted)
		{
			remove(msg->filename);
		}

		Hash_Remove(&cl->emails, va("%i", mn));
		BZ_Free(msg);
	}
}

static qboolean SV_POP3_ReadMessage(svpop3client_t *cl, int index)
{
	FILE *f;
	pop3message_t *msg;

	msg = SV_POP3_GetMessage(cl, index);
	if (!msg)
		return false;
	if (msg->deleted)
		return false;

	f = fopen(msg->filename, "rb");
	if (!f)
		return false;
	fseek(f, 0, SEEK_END);
	cl->messagelumplen = ftell(f);
	fseek(f, 0, SEEK_SET);
	cl->messagelump = IWebMalloc(cl->messagelumplen+3);
	fread(cl->messagelump, 1, cl->messagelumplen, f);
	cl->messagelump[cl->messagelumplen++] = '\r';
	cl->messagelump[cl->messagelumplen++] = '\n';
	fclose(f);

	cl->messagelumplen = strlen(cl->messagelump);
	cl->messagelumppos = 0;
	cl->messagelumphitbody = false;
	cl->messagelumplines = 0x7fffffff;

	return true;
}

static void SV_POP3_BuildListing(svpop3client_t *cl, qboolean isuidl)
{
	pop3message_t *msg;
	int mn;
	char *listing;

	listing = cl->messagelump = IWebMalloc(cl->nummessages*64+3);

	for (mn = 1; mn <= cl->nummessages; mn++)
	{
		msg = SV_POP3_GetMessage(cl, mn);
		if (!msg || msg->deleted)
			continue;

		if (isuidl)
			sprintf(listing, "%i %s,S=%i\r\n", mn, msg->filename, msg->size);
		else
			sprintf(listing, "%i %i\r\n", mn, msg->size);
		listing += strlen(listing);
	}

	cl->messagelumplen = listing - cl->messagelump;
}

static qboolean SV_POP3_RunClient(svpop3client_t *cl)	//true means client should be dropped
{
	int read;
	char *nl;
	char *token;
	int blankline;

	if (cl->messagelump)
	{
		blankline=false;
		while (cl->outmessagelen < sizeof(cl->outmessagebuffer)-100)
		{
			if (cl->messagelumppos >= cl->messagelumplen)
				break;

			if (cl->messagelump[cl->messagelumppos] == '.')	//double up all '.'s at start of lines
				cl->outmessagebuffer[cl->outmessagelen++] = '.';

			blankline = true;
			for(;;)
			{
				if (cl->messagelumppos >= cl->messagelumplen)
					break;
				if (cl->messagelump[cl->messagelumppos] > ' ')
					blankline = false;
				cl->outmessagebuffer[cl->outmessagelen++] = cl->messagelump[cl->messagelumppos];
				if (cl->messagelump[cl->messagelumppos++] == '\n')
					break;
			}
			if (blankline)
				cl->messagelumphitbody = true;
			if (cl->messagelumphitbody)
			{
				if (cl->messagelumplines--<=0)
					cl->messagelumppos = cl->messagelumplen;	//easy way to terminate.
			}
		}
		if (cl->messagelumppos >= cl->messagelumplen)
		{	//we've sent the entire buffer now.
			cl->outmessagebuffer[cl->outmessagelen++] = '.';
			cl->outmessagebuffer[cl->outmessagelen++] = '\r';
			cl->outmessagebuffer[cl->outmessagelen++] = '\n';

			BZ_Free(cl->messagelump);
			cl->messagelump = NULL;
		}
		cl->outmessagebuffer[cl->outmessagelen] = '\0';
printf("%s\n", cl->outmessagebuffer);
	}
	if (cl->outmessagelen)
	{
		read = send(cl->socket, cl->outmessagebuffer, cl->outmessagelen, MSG_PARTIAL);
		if (read < 0)
			read = 0;
		memmove(cl->outmessagebuffer, cl->outmessagebuffer + read, cl->outmessagelen - read);
		cl->outmessagelen -= read;
		cl->outmessagebuffer[cl->outmessagelen] = '\0';

		if (cl->dropwhensent && !cl->outmessagelen)
			return true;
	}

	read = recv(cl->socket, cl->inmessagebuffer+cl->inmessagelen, sizeof(cl->inmessagebuffer)-1-cl->inmessagelen, MSG_PARTIAL);

	if (read == -1)
	{
		if (qerrno != EWOULDBLOCK)	//blocking is the only way to keep the connection on fail.
			return true;

		if (!*cl->inmessagebuffer)
			return false;
//nonblocking allows us to get multiple commands from one packet.
	}
	else if (read == 0)	//don't quite know why this happens.
		return true;	//believed to be an indication that the other end has disconnected.
	else
	{
		cl->timeout = realtime + POP3_TIMEOUT;

		cl->inmessagelen += read;
		if (cl->inmessagelen >= sizeof(cl->inmessagebuffer)-1)	//happens if we fill the buffer with no hope of empting it.
			return true;
		cl->inmessagebuffer[cl->inmessagelen] = '\0';
	}
	nl = strchr(cl->inmessagebuffer, '\n');
	if (nl)
	{
		*nl = '\0';
//Con_Printf("%s\n", cl->inmessagebuffer);
		read = nl - cl->inmessagebuffer + 1;

		token = COM_ParseToken(cl->inmessagebuffer);

//auth mechanism 1
		if (!strcmp(com_token, "USER"))
		{
			token = COM_ParseToken(token);
			if (*com_token)
			{
				Q_strncpyz(cl->username, com_token, sizeof(cl->username));
				SV_POP3_QueueMessage(cl,	"+OK User name accepted, password please\r\n");

				SV_POP3_CleanUp(cl, true);
				cl->loggedin = false;
			}
			else
				SV_POP3_QueueMessage(cl,	"-ERR no username was specified\r\n");
		}
		else if (!strcmp(com_token, "PASS"))
		{
#ifndef CLIENTONLY
			int id;
			extern cvar_t rank_filename;

			token = COM_ParseToken(token);
			id = Rank_GetPlayerID(cl->username, atoi(com_token), false);
			if (!id && *rank_filename.string)
			{
				SV_POP3_QueueMessage(cl,	"-ERR User or Password not valid\r\n");

				SV_POP3_CleanUp(cl, true);
				cl->loggedin = false;
			}
			else
#endif
			{
				SV_POP3_QueueMessage(cl,	"+OK Logged in\r\n");
				cl->loggedin = true;
				SV_POP3_CountMessages(cl);
			}
		}

//auth2
		else if (!strcmp(com_token, "APOP"))
		{
			int id;
			int pass;
			extern cvar_t rank_filename;

			token = COM_ParseToken(token);
			if (*com_token)
			{
				Q_strncpyz(cl->username, com_token, sizeof(cl->username));

#ifndef CLIENTONLY
				token = COM_ParseToken(token);
				pass = Rank_GetPass(cl->username);
				id = Rank_GetPlayerID(cl->username, pass, false);
				if ((!id && *rank_filename.string) || strcmp(MD5_GetPop3APOPString(cl->greeting, va("%i", pass)), com_token))
				{
					SV_POP3_QueueMessage(cl,	"-ERR User or Password not valid\r\n");

					SV_POP3_CleanUp(cl, true);
					cl->loggedin = false;
				}
				else
#endif
				{
					SV_POP3_QueueMessage(cl,	"+OK Logged in\r\n");
					cl->loggedin = true;
					SV_POP3_CountMessages(cl);
				}
			}
		}


//now they need to have been logged in properly.
		else if (!cl->loggedin)
			SV_POP3_QueueMessage(cl,	"-ERR You didn't log in properly\r\n");
		else if (!strcmp(com_token, "STAT"))
		{
			char text[64];
			sprintf(text, "+OK %i %i\r\n", cl->nummessages, cl->totalsize);
			SV_POP3_QueueMessage(cl,	text);
		}
		else if (!strcmp(com_token, "LIST"))
		{
			SV_POP3_QueueMessage(cl,	"+OK EMail listing follows:\r\n");
			SV_POP3_BuildListing(cl, false);
		}
		else if (!strcmp(com_token, "DELE"))
		{
			pop3message_t *msg;
			int mnum;
			token = COM_ParseToken(token);
			mnum = atoi(com_token);

			msg = SV_POP3_GetMessage(cl, mnum);
			if (!msg)
				SV_POP3_QueueMessage(cl,	"-ERR message index out of range\r\n");
			else if (msg->deleted)
				SV_POP3_QueueMessage(cl,	"-ERR message already deleted\r\n");
			else
			{
				msg->deleted = true;
				SV_POP3_QueueMessage(cl,	"+OK Message marked for deleted\r\n");
			}
		}
		else if (!strcmp(com_token, "TOP"))
		{
			token = COM_ParseToken(token);
			if (SV_POP3_ReadMessage(cl, atoi(com_token)))
			{
				token = COM_ParseToken(token);
				SV_POP3_QueueMessage(cl,	"+OK message contents follow:\n");
				cl->messagelumplines = atoi(com_token);
			}
			else
				SV_POP3_QueueMessage(cl,	"-ERR Message index wasn't valid\n");
		}
		else if (!strcmp(com_token, "RETR"))
		{
			token = COM_ParseToken(token);
			if (SV_POP3_ReadMessage(cl, atoi(com_token)))
				SV_POP3_QueueMessage(cl,	"+OK message contents follow:\n");
			else
				SV_POP3_QueueMessage(cl,	"-ERR Message index wasn't valid\n");
		}
		else if (!strcmp(com_token, "UIDL"))
		{
			SV_POP3_QueueMessage(cl,	"+OK I hope someone likes you\r\n");
			SV_POP3_BuildListing(cl, true);
		}
		else if (!strcmp(com_token, "QUIT"))
		{
			SV_POP3_CleanUp(cl, true);

			SV_POP3_QueueMessage(cl,	"+OK I hope someone likes you\r\n");
			cl->dropwhensent = true;
		}
		else
			SV_POP3_QueueMessage(cl,	"-ERR Unrecognised command\r\n");
//printf("%s\n", cl->outmessagebuffer);

		memmove(cl->inmessagebuffer, cl->inmessagebuffer + read, cl->inmessagelen - read);
		cl->inmessagelen -= read;
	}

	return false;
}

static void SV_POP3_RunClients(void)
{
	svpop3client_t *cl, *prev;

	cl = svpop3client;
	prev = NULL;
	while(cl)
	{
		if (cl->timeout < realtime || SV_POP3_RunClient(cl))
		{
printf("drop client\n");
			closesocket(cl->socket);
			if (prev)
				prev->next = cl->next;
			else
				svpop3client = cl->next;

			if (cl->messagelump)
				IWebFree(cl->messagelump);
			SV_POP3_CleanUp(cl, false);

			IWebFree(cl);
			if (prev)
				cl = prev->next;
			else
				cl = svpop3client;
			continue;
		}
		prev = cl;
		cl = cl->next;
	}
}

qboolean SV_POP3(qboolean activewanted)
{
	struct sockaddr from;
	int fromlen;
	int clientsock;

	if (!pop3active)
	{
		if (activewanted)
			POP3_ServerInit();
		else
			return false;
	}
	else if (!activewanted)
	{
		POP3_ServerShutdown();
		return false;
	}

	fromlen = sizeof(from);
	clientsock = accept(pop3serversocket, (struct sockaddr *)&from, &fromlen);

	if (clientsock == -1)
	{
		if (qerrno == ECONNABORTED || qerrno == ECONNRESET)
		{
			Con_TPrintf (TL_CONNECTIONLOSTORABORTED);
			return false;
		}
		else if (qerrno != EWOULDBLOCK)
		{
			Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno));
			return false;
		}
	}
	else	//we got a new client. yay.
		POP3_NewConnection(clientsock);

	SV_POP3_RunClients();

	return true;
}

#endif