/*
Copyright (C) 2005 David Walton.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.



As a special exception, you may incorpotate patents and libraries regarding only hashing and security, on the conditions that it is also open source.
This means md4/5, rsa, ssl and similar.
*/

#include "../plugin.h"

//code to sit on an imap server and check for new emails every now and then.





char *STR_Parse(char *str, char *out, int outlen, char *punctuation)
{
	char *s = str;
	char *f;
	
skipwhite:
	//skip over the whitespace
	while (*s <= ' ' && *s)
		s++;

	if (*s == '/')
	{
		if (s[1] == '/')	//c++ style comment
		{
			while(*s != '\n' && *s)
				s++;

			goto skipwhite;
		}
		if (s[1] == '*')
		{
			s+=2;
			while(*s)
			{
				if (s[0] == '*' && s[1] == '/')
				{
					s+=2;
					break;
				}
				s++;
			}
			goto skipwhite;
		}
	}

	if (*s == '\"')
	{
		s++;
		while(*s && outlen>1)
		{
			if (*s == '\"')
			{
				s++;
				break;
			}
			*out++ = *s++;
			outlen--;
		}
		*out++ = '\0';
		return s;
	}

	if (strchr(punctuation, *s))
	{	//starts with punctuation, so return only the first char
		if (outlen < 2)
			return NULL;	//aaaah!
		*out++ = *s;
		*out++ = '\0';
		s++;

		return s;
	}
	//skip over non-white
	for (f = s; outlen>1 && *(unsigned char*)f > ' '; f++, outlen--)
	{
		if (strchr(punctuation, *f))
		{	//found punctuation, so return up to here
			break;
		}
		*out++ = *f;
	}
	*out++ = '\0';

	return f;
}






//exported.
void IMAP_CreateConnection(char *servername, char *username, char *password);
int imap_checkfrequency=60*1000;
void IMAP_Think (void);
//end export list.




#define IMAP_PORT 143


typedef struct imap_con_s {
	char server[128];
	char username[128];
	char password[128];

	unsigned int lastnoop;

	//these are used so we can fail a send.
	//or recieve only part of an input.
	//FIXME:	make dynamically sizable, as it could drop if the send is too small (That's okay.
	//			but if the read is bigger than one command we suddenly fail entirly.
	int sendlen;
	int sendbuffersize;
	char *sendbuffer;
	int readlen;
	int readbuffersize;
	char *readbuffer;

	qboolean drop;

	qhandle_t socket;

	enum {
		IMAP_WAITINGFORINITIALRESPONCE,
		IMAP_AUTHING,
		IMAP_AUTHED,
		IMAP_INBOX
	} state;

	struct imap_con_s *next;
} imap_con_t;

static imap_con_t *imapsv;

void IMAP_CreateConnection(char *addy, char *username, char *password)
{
	unsigned long _true = true;
	imap_con_t *con;

	for (con = imapsv; con; con = con->next)
	{
		if (!strcmp(con->server, addy))
		{
			Con_Printf("Already connected to that imap server\n");
			return;
		}
	}

	con = malloc(sizeof(imap_con_t));

	con->socket = Net_TCPConnect(addy, IMAP_PORT);

	if (!con->socket)
	{
		Con_Printf ("IMAP_CreateConnection: connect failed\n");
		free(con);
		return;
	}

	strlcpy(con->server, addy, sizeof(con->server));
	strlcpy(con->username, username, sizeof(con->username));
	strlcpy(con->password, password, sizeof(con->password));

	con->next = imapsv;
	imapsv = con;

	Con_Printf ("Connected to %s (%s)\n", addy, username);
}

static void IMAP_EmitCommand(imap_con_t *imap, char *text)
{
	int newlen;
	
	//makes a few things easier though

	newlen = imap->sendlen + 2 + strlen(text) + 2;

	if (newlen >= imap->sendbuffersize || !imap->sendbuffer)	//pre-length check.
	{
		char *newbuf;
		imap->sendbuffersize = newlen*2;
		newbuf = malloc(imap->sendbuffersize);	//the null terminator comes from the >=
		if (!newbuf)
		{
			Con_Printf("Memory is low\n");
			imap->drop = true;	//failed.
			return;
		}
		if (imap->sendbuffer)
		{
			memcpy(newbuf, imap->sendbuffer, imap->sendlen);
			free(imap->sendbuffer);
		}
		imap->sendbuffer = newbuf;
	}

	snprintf(imap->sendbuffer+imap->sendlen, newlen+1, "* %s\r\n", text);
	imap->sendlen = newlen;
}

static char *IMAP_AddressStructure(char *msg, char *out, int outsize)
{
	char name[256];
	char mailbox[64];
	char hostname[128];
	char route[128];
	int indents=0;
	while(*msg == ' ')
		msg++;
	while(*msg == '(')	//do it like this, we can get 2... I'm not sure if that's always true..
	{
		msg++;
		indents++;
	}

	msg = STR_Parse(msg, name, sizeof(name), "");	//name
	msg = STR_Parse(msg, route, sizeof(route), "");	//smtp route (ignored normally)
	msg = STR_Parse(msg, mailbox, sizeof(mailbox), "");	//mailbox
	msg = STR_Parse(msg, hostname, sizeof(hostname), "");	//hostname

	while(indents && *msg == ')')
		msg++;

	if (out)
	{
		if (!strcmp(name, "NIL"))
			snprintf(out, outsize, "%s@%s", mailbox, hostname);
		else
			snprintf(out, outsize, "%s <%s@%s>", name, mailbox, hostname);
	}

	return msg;
}

static qboolean IMAP_ThinkCon(imap_con_t *imap)	//false means drop the connection.
{
	char *ending;
	int len;

	//get the buffer, stick it in our read holder
	if (imap->readlen+32 >= imap->readbuffersize || !imap->readbuffer)
	{
		len = imap->readbuffersize;
		if (!imap->readbuffer)
			imap->readbuffersize = 256;
		else
			imap->readbuffersize*=2;

		ending = malloc(imap->readbuffersize);
		if (!ending)
		{
			Con_Printf("Memory is low\n");
			return false;
		}
		if (imap->readbuffer)
		{
			memcpy(ending, imap->readbuffer, len);
			free(imap->readbuffer);
		}
		imap->readbuffer = ending;
	}

	len = Net_Recv(imap->socket, imap->readbuffer+imap->readlen, imap->readbuffersize-imap->readlen-1);
	if (len>0)
	{
		imap->readlen+=len;
		imap->readbuffer[imap->readlen] = '\0';
	}

	if (imap->readlen>0)
	{
		ending = strstr(imap->readbuffer, "\r\n");

		if (ending)	//pollable text.
		{
			*ending = '\0';
//			Con_Printf("%s\n", imap->readbuffer);

			ending+=2;
			if (imap->state == IMAP_WAITINGFORINITIALRESPONCE)
			{
				//can be one of two things.
				if (!strncmp(imap->readbuffer, "* OK", 4))
				{
					IMAP_EmitCommand(imap, va("LOGIN %s %s", imap->username, imap->password));
					imap->state = IMAP_AUTHING;
				}
				else if (!strncmp(imap->readbuffer, "* PREAUTH", 9))
				{
					Con_Printf("Logged on to %s\n", imap->server);
					IMAP_EmitCommand(imap, "SELECT INBOX");
					imap->state = IMAP_AUTHED;
					imap->lastnoop = Sys_Milliseconds();
				}
				else
				{
					Con_Printf("Unexpected response from IMAP server\n");
					return false;
				}
			}
			else if (imap->state == IMAP_AUTHING)
			{
				if (!strncmp(imap->readbuffer, "* OK", 4))
				{
					Con_Printf("Logged on to %s\n", imap->server);
					IMAP_EmitCommand(imap, "SELECT INBOX");
					imap->state = IMAP_AUTHED;
					imap->lastnoop = Sys_Milliseconds();
				}
				else
				{
					Con_Printf("Unexpected response from IMAP server\n");
					return false;
				}
			}
			else if (imap->state == IMAP_AUTHED)
			{
				char *num;
				num = imap->readbuffer;
				if (!strncmp(imap->readbuffer, "* SEARCH ", 8))	//we only ever search for recent messages. So we fetch them and get sender and subject.
				{
					char *s;
					s = imap->readbuffer+8;
					num = NULL;
					while(*s)
					{
						s++;
						num = s;
						while (*s >= '0' && *s <= '9')
							s++;

						IMAP_EmitCommand(imap, va("FETCH %i ENVELOPE", atoi(num)));	//envelope so that it's all one line.
					}
				}
				else if (imap->readbuffer[0] == '*' && imap->readbuffer[1] == ' ')
				{
					num = imap->readbuffer+2;
					while(*num >= '0' && *num <= '9')
					{
						num++;
					}
					if (!strcmp(num, " RECENT"))
					{
						if (atoi(imap->readbuffer+2) > 0)
						{
							IMAP_EmitCommand(imap, "SEARCH RECENT");
						}
					}
					else if (!strncmp(num, " FETCH (ENVELOPE (", 18))
					{
						char from[256];
						char subject[256];
						char date[256];

						num += 18;

						num = STR_Parse(num, date, sizeof(date), "");
						num = STR_Parse(num, subject, sizeof(subject), "");
//						Con_Printf("Date/Time: %s\n", date);

						num = IMAP_AddressStructure(num, from, sizeof(from));


						if ((rand() & 3) == 3)
						{
							if (rand()&1)
								Con_Printf("\n^2New spam has arrived\n");
							else
								Con_Printf("\n^2You have new spam\n");
						}
						else if (rand()&1)
							Con_Printf("\n^2New mail has arrived\n");
						else
							Con_Printf("\n^2You have new mail\n");

						Con_Printf("Subject: %s\n", subject);
						Con_Printf("From: %s\n", from);

						SCR_CenterPrint(va("NEW MAIL HAS ARRIVED\n\nTo: %s@%s\nFrom: %s\nSubject: %s", imap->username, imap->server, from, subject));

						//throw the rest away.
					}
				}
			}
			else
			{
				Con_Printf("Bad client state\n");
				return false;
			}
			imap->readlen -= ending - imap->readbuffer;
			memmove(imap->readbuffer, ending, strlen(ending)+1);
		}
	}
	if (imap->drop)
		return false;

	if (imap->state == IMAP_AUTHED)
	{
		if (imap->lastnoop + imap_checkfrequency < Sys_Milliseconds())
		{	//we need to keep the connection reasonably active

			IMAP_EmitCommand(imap, "SELECT INBOX");	//this causes the recent flags to be reset. This is the only way I found.
			imap->lastnoop = Sys_Milliseconds();
		}
	}

	if (imap->sendlen)
	{
		len = Net_Send(imap->socket, imap->sendbuffer, imap->sendlen);
		if (len>0)
		{
			imap->sendlen-=len;
			memmove(imap->sendbuffer, imap->sendbuffer+len, imap->sendlen+1);
		}
	}
	return true;
}

void IMAP_Think (void)
{
	imap_con_t *prev = NULL;
	imap_con_t *imap;

	for (imap = imapsv; imap; imap = imap->next)
	{
		if (imap->drop || !IMAP_ThinkCon(imap))
		{
			if (!prev)
				imapsv = imap->next;
			else
				prev->next = imap->next;
			Net_Close(imap->socket);
			free(imap);
			if (!prev)
				break;
		}

		prev = imap;
	}
}