#include <stdlib.h>
#include <stdio.h>

#include "lgi/common/Lgi.h"
#include "lgi/common/Mail.h"
#include "lgi/common/DocView.h"

//////////////////////////////////////////////////////////////////////////
#include "lgi/common/Http.h"

static char PopOverHttpSep[] = "\n-----------------[EndPopOverHttp]-----------------";

class Msg
{
public:
	int Index;
	int Size;
	char *Uid;
	char *Headers;
	bool Delete;

	Msg()
	{
		Index = 0;
		Size = 0;
		Uid = 0;
		Headers = 0;
		Delete = false;
	}

	~Msg()
	{
		DeleteArray(Uid);
		DeleteArray(Headers);
	}
};

class MailPhpPrivate
{
public:
	int Messages;
	char Uri[256];
	bool HasDeletes;
	bool HeadersRetreived;
	List<class Msg> Msgs;
	char *ProxyServer;
	int ProxyPort;
	char *UserName;
	char *UserPass;

	MailPhpPrivate()
	{
		Uri[0] = 0;
		Messages = 0;
		HasDeletes = 0;
		ProxyServer = 0;
		ProxyPort = 0;
		UserName = 0;
		UserPass = 0;
		HeadersRetreived = false;
	}
	
	~MailPhpPrivate()
	{
		DeleteArray(ProxyServer);
		Msgs.DeleteObjects();
		DeleteArray(UserName);
		DeleteArray(UserPass);
	}
};

MailPhp::MailPhp()
{
	d = new MailPhpPrivate;
}

MailPhp::~MailPhp()
{
	DeleteObj(d);
}

class MailSocket : public LSocket
{
	LSocketI *S;
	MailProtocolProgress *T;
	MailProtocol *Log;
	LStringPipe ReadBuf, WriteBuf;
	bool InData;
	LFile *Temp;
	ssize_t SepLen;
	bool OwnSocket;

public:
	MailSocket(LSocketI *s, MailProtocolProgress *t, MailProtocol *l)
	{
		if (s)
		{
			S = s;
			OwnSocket = false;
		}
		else
		{
			S = new LSocket;
			OwnSocket = true;
		}
		T = t;

		InData = false;
		Log = l;
		Temp = 0;
		SepLen = strlen(PopOverHttpSep) - 1;
	}

	~MailSocket()
	{
		DeleteObj(Temp);
		if (OwnSocket)
		{
			DeleteObj(S);
		}
	}

	// Stream
	int Open(const char *Str, int Int) { return S->Open(Str, Int); }
	bool IsOpen() { return S->IsOpen(); }
	int Close() { return S->Close(); }
	int64 GetSize() { return S->GetSize(); }
	int64 SetSize(int64 Size) { return S->SetSize(Size); }
	int64 GetPos() { return S->GetPos(); }
	int64 SetPos(int64 Pos) { return S->SetPos(Pos); }
	LStreamI *Clone() { return S->Clone(); }

	// Socket
	OsSocket Handle(OsSocket Set) { return S->Handle(Set); }
	bool GetLocalIp(char *IpAddr) { return S->GetLocalIp(IpAddr); }
	int GetLocalPort() { return S->GetLocalPort(); }
	bool GetRemoteIp(char *IpAddr) { return S->GetRemoteIp(IpAddr); }
	int GetRemotePort() { return S->GetRemotePort(); }
	bool IsReadable(int TimeoutMs = 0) { return S->IsReadable(); }
	bool Listen(int Port = 0) { return S->Listen(Port = 0); }
	bool Accept(LSocketI *c) { return S->Accept(c); }
	int Error(void *param) { return S->Error(param); }
	void OnDisconnect() { S->OnDisconnect(); }
	void OnError(int ErrorCode, const char *ErrorDescription) { S->OnError(ErrorCode, ErrorDescription); }
	void OnInformation(const char *Str) { S->OnInformation(Str); }
	void OnRead(char *Data, ssize_t Len)
	{
		S->OnRead(Data, Len);

		if (Temp)
		{
			Temp->Write(Data, Len);
		}
		else if (!Data)
		{
			ReadBuf.Push(Data, Len);

			char Buf[512];
			while (ReadBuf.Pop(Buf, sizeof(Buf)))
			{
				if (strchr("\r\n", Buf[0]))
				{
					InData = true;
					ReadBuf.Empty();
					break;
				}
				else
				{
					Log->Log(Buf, LSocketI::SocketMsgReceive);
				}
			}
		}
		else if (Log->Items)
		{
			ReadBuf.Push(Data, Len);

			char Buf[512];
			while (ReadBuf.Pop(Buf, sizeof(Buf)))
			{
				if (strncmp(Buf, PopOverHttpSep+1, SepLen) == 0)
				{
					Log->Items->Value++;
				}
			}
		}
	}

	void OnWrite(const char *Data, ssize_t Len)
	{
		S->OnWrite(Data, Len);

		WriteBuf.Push(Data, Len);

		char Buf[256];
		while (WriteBuf.Pop(Buf, sizeof(Buf)))
		{
			if (!strchr("\r\n", Buf[0]))
			{
				Log->Log(Buf, LSocketI::SocketMsgSend);
			}
		}
	}

	bool SetValue(const char *Which, LVariant &What)
	{
		int r = S->SetValue(Which, What);
		if (T && _stricmp(Which, LSocket_TransferSize) == 0)
		{
			T->Range = What.CastInt32();
		}
		return r != 0;
	}

	ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0)
	{
		// Remove null characters
		char *o = (char*) Buffer;
		char *i = o;
		while (i < (char*)Buffer+Size)
		{
			if (*i)
			{
				*o++ = *i;
			}
			i++;
		}

		return S->Write(Buffer, o - (char*)Buffer, Flags);
	}
	
	ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0)
	{
		ssize_t s = S->Read(Buffer, Size, Flags);
		if (T && s > 0)
		{
			T->Value += s;
		}
		return s;
	}
};

void MailPhp::SetProxy(char *Server, int Port)
{
	d->ProxyServer = NewStr(Server);
	d->ProxyPort = Port;
}

bool MailPhp::Get(LSocketI *S, char *Uri, LStream &Out, bool MailTransfer)
{
	bool Status = false;

	if (S && Uri)
	{
		char *Start = Uri;
		if (_strnicmp(Start, "http://", 7) == 0)
			Start += 7;
		
		char *s = strchr(Start, '/');
		if (s)
		{
			char *Base = NewStr(Start, s-Start);

			LHttp Http;
			if (d->UserName && d->UserPass)
			{
				Http.SetAuth(d->UserName, d->UserPass);
			}
			if (d->ProxyServer)
			{
				Http.SetProxy(d->ProxyServer, d->ProxyPort);
			}
			
			LAutoPtr<LSocketI> s(new MailSocket(S, MailTransfer ? Transfer : 0, this));
			if (Http.Open(s, Base))
			{
				LStringPipe Buf;
				int Code = 0;
				LHttp::ContentEncoding Enc;
				if (Http.Get(Uri, 0, &Code, &Buf, &Enc))
				{
					char Start[256];
					if (Buf.Peek((uchar*)Start, sizeof(Start)))
					{
						Start[sizeof(Start)-1] = 0;
						if (_stricmp(Start, "Error:") == 0)
						{
							return false;
						}
					}
					
					GCopyStreamer s;
					Status = s.Copy(&Buf, &Out) > 0;
				}
			}
			
			DeleteArray(Base);
		}
	}

	return Status;
}

int MailPhp::GetMessages()
{
	return d->Messages;
}

bool MailPhp::Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags)
{
	if (S &&
		RemoteHost)
	{
		d->HeadersRetreived = false;
		DeleteArray(d->UserName);
		d->UserName = NewStr(User);
		DeleteArray(d->UserPass);
		d->UserPass = NewStr(Password);

		uint64 Token = LCurrentTime();
		strcpy_s(d->Uri, sizeof(d->Uri), RemoteHost);

		char *e = d->Uri + strlen(d->Uri);
		sprintf_s(e, sizeof(d->Uri)-(e-d->Uri), "?token=" LPrintfInt64, Token);
		
		char *m = 0;
		LStringPipe Text;
		Socket.Reset(S);
		if (Get(Socket, d->Uri, Text, false))
		{
			m = Text.NewStr();
		}

		*e = 0;
		if (m)
		{
			bool PopOverHttp = false;
			bool GotToken = false;

			auto Lines = LString(m).SplitDelimit("\r\n");
			if (_strnicmp(m, "error:", 6) == 0)
			{
				for (unsigned Line=0; Line<Lines.Length(); Line++)
				{
					Log(Lines[Line].Get() + 7, LSocketI::SocketMsgError);
				}
			}
			else if (Lines.Length() > 1)
			{
				for (unsigned Line=0; Line<Lines.Length(); Line++)
				{
					char *Var = Lines[Line];
					char *Val = strchr(Var, ':');

					if (Val)
					{
						*Val++ = 0;
						while (strchr(" \t", *Val)) Val++;
					
						if (IsDigit(*Var))
						{
							if (PopOverHttp)
							{
								auto p = LString(Val).SplitDelimit(" ");
								if (p.Length() > 1)
								{
									Msg *m = new Msg;
									if (m)
									{
										m->Index = atoi(Var);
										m->Size = atoi(p[0]);
										m->Uid = NewStr(p[1]);

										d->Msgs.Insert(m);
									}
								}
							}
						}
						else if (_stricmp(Var, "protocol") == 0)
						{
							PopOverHttp = stristr(Val, "popoverhttp") != 0;
						}
						else if (_stricmp(Var, "messages") == 0)
						{
							d->Messages = atoi(Val);
						}
						else if (_stricmp(Var, "token") == 0)
						{
							int Tok = atoi(Val);
							GotToken = Tok == Token;
						}
					}
				}
			}
			else Log("Empty index page returned.", LSocketI::SocketMsgError);

			DeleteArray(m);
			
			if (!PopOverHttp)
			{
				Log("Page is not a PopOverHttp index.", LSocketI::SocketMsgError);
			}
			if (!GotToken)
			{
				Log("Missing or invalid token. Is your PopOverHttp page broken?", LSocketI::SocketMsgError);
			}

			return	PopOverHttp &&
					GotToken &&
					d->Messages == d->Msgs.Length();
		}
		else Log("No PopOverHttp index page.", LSocketI::SocketMsgError);
	}
	else Log("No remote host.", LSocketI::SocketMsgError);

	return false;
}

bool MailPhp::Close()
{
	if (d->HasDeletes &&
		d->Messages)
	{
		LStringPipe p;
		p.Print("%s?time=%i&delete=", d->Uri, LCurrentTime());
		
		bool First = true;
		for (auto m: d->Msgs)
		{
			if (m->Delete)
			{
				if (!First) p.Push(",");
				else First = false;
				p.Print("%i", m->Index+1);
			}
		}
		
		bool Status = false;
		LSocketI *S = new MailSocket(Socket, 0, this);
		if (S)
		{
			LStringPipe Out;
			
			char *u = p.NewStr();
			if (u)
			{
				if (Get(S, u, Out, false))
				{
					char *m = Out.NewStr();
					if (m)
					{
						Status = stristr("Error:", m) == 0;
						DeleteArray(m);
					}
				}
				
				DeleteArray(u);
			}
		}		
	}
	
	d->Msgs.DeleteObjects();

	return true;
}

bool MailPhp::Receive(LArray<MailTransaction*> &Trans, MailCallbacks *Callbacks)
{
	LStringPipe Cmd;

	Cmd.Push(d->Uri);
	Cmd.Print("?time=%i&msg=", LCurrentTime());
	for (unsigned i=0; i<Trans.Length(); i++)
	{
		if (i) Cmd.Push(",");
		Cmd.Print("%i", Trans[i]->Index + 1);
	}

	bool Status = false;
	LSocketI *S = new MailSocket(Socket, 0, this);
	if (S)
	{
		// Download all the messages
		LStringPipe Out;
		char *c = Cmd.NewStr();
		Status = Get(S, c, Out, true);
		DeleteArray(c);
		
		// Split them into individual transactions
		char *All = Out.NewStr();
		if (All)
		{
			char *In = All;
			char *Out = All;
			while (*In)
			{
				if (*In != '\r')
				{
					*Out++ = *In;
				}
				In++;
			}
			*Out = 0;

			char *s = All;
			unsigned i = 0;
			for (char *e = strstr(s, PopOverHttpSep); s && e; i++)
			{
				MailTransaction *t = Trans[i];
				if (t && t->Stream)
				{
					ssize_t Len = e-s;
					if (!Strnstr(s, "Error: ", MIN(Len, 256)))
					{
						t->Stream->Write(s, Len - 2);
						t->Status = true;
						Status = true;
					}
					else
					{
						printf("%s:%i - Error in Mail.\n", __FILE__, __LINE__);
					}
					
					s = strchr(e + 1, '\n');
					if (s) s++;
					e = s ? strstr(s, PopOverHttpSep) : 0;
				}
				else
				{
					printf("%s:%i - Error: No Stream.\n", __FILE__, __LINE__);
					break;
				}
			}
			
			if (i < Trans.Length())
			{
				printf("%s:%i - Error: Only found %i of %i separators in %i bytes from server.\n",
					__FILE__, __LINE__,
					i, (int)Trans.Length(),
					(int)strlen(All));
			}
			
			DeleteArray(All);
		}
	}		
	
	return Status;
}

bool MailPhp::Delete(int Message)
{
	Msg *m = d->Msgs[Message];
	if (m)
	{
		m->Delete = true;
		d->HasDeletes = true;
		return true;
	}
	
	return false;
}

int MailPhp::Sizeof(int Message)
{
	Msg *m = d->Msgs[Message];
	return m ? m->Size : 0;
}

bool MailPhp::GetSizes(LArray<int> &Sizes)
{
	for (auto m: d->Msgs)
	{
		Sizes.Add(m->Size);
	}
	return Sizes.Length() == d->Msgs.Length();
}

bool MailPhp::GetUid(int Message, char *Id, int IdLen)
{
	Msg *m = d->Msgs[Message];
	if (!m)
		return false;
	
	strcpy_s(Id, IdLen, m->Uid);
	return true;
}

bool MailPhp::GetUidList(List<char> &Id)
{
	for (auto m: d->Msgs)
	{
		if (m->Uid)
		{
			Id.Insert(NewStr(m->Uid));
		}
	}

	return Id.Length() > 0;
}

char *MailPhp::GetHeaders(int Message)
{
	Msg *TheMsg = d->Msgs[Message];
	if (TheMsg)
	{
		if (!d->HeadersRetreived)
		{
			d->HeadersRetreived = true;

			char *e = d->Uri + strlen(d->Uri);
			sprintf_s(e, sizeof(d->Uri)-(e-d->Uri), "?top=1");

			LStringPipe Text;
			if (Get(new LSocket, d->Uri, Text, false))
			{
				int n = 0;
				char *All = Text.NewStr();
				// int AllLen = strlen(All);
				for (char *s = All; s && *s; )
				{
					Msg *m = d->Msgs[n++];
					if (m)
					{
						DeleteArray(m->Headers);

						char *e = stristr(s, "\r\n.\r\n");
						if (e)
						{
							m->Headers = NewStr(s, e-s);
							s = e + 5;
							while (*s == '\r' || *s == '\n') s++;
						}
						else
						{
							m->Headers = NewStr(s, strlen(s)-3);
							s = 0;
						}
					}
					else break;
				}

				DeleteArray(All);
			}
			*e = 0;
		}

		return NewStr(TheMsg->Headers);
	}

	return 0;
}

