#include <stdio.h>

#include "lgi/common/LgiNetInc.h"
#include "lgi/common/Lgi.h"
#include "lgi/common/Mime.h"
#include "lgi/common/Base64.h"

#define DEBUG_MIME						0
#if DEBUG_MIME
#define LOG(...) printf(__VA_ARGS__)
#else
#define LOG(...)
#endif

static const char *MimeEol				= "\r\n";
static const char *MimeWs				= " \t\r\n";
static const char *MimeStr				= "\'\"";
static const char *MimeQuotedPrintable	= "quoted-printable";
static const char *MimeBase64			= "base64";

#define MimeMagic					( ('M'<<24) | ('I'<<24) | ('M'<<24) | ('E'<<24) )
#define SkipWs(s)					while (*s && strchr(MimeWs, *s)) s++
#define SkipNonWs(s)				while (*s && !strchr(MimeWs, *s)) s++

const char *LMime::DefaultCharset =		"text/plain";

template<typename T>
int CastInt(T in)
{
	int out = (int)in;
	LAssert(out == in);
	return out;
}

int AltScore(char *Mt)
{
	int Score = 0;
	if (Mt)
	{
		if (stristr(Mt, "/html"))
			Score = 1;
		else if (stristr(Mt, "/related"))
			Score = 2;
	}
	// printf("Score '%s' = %i\n", Mt, Score);
	DeleteArray(Mt);
	return Score;
}

int AltSortCmp(LMime **a, LMime **b)
{
	int a_score = AltScore((*a)->GetMimeType());
	int b_score = AltScore((*b)->GetMimeType());
	return a_score - b_score;
}

///////////////////////////////////////////////////////////////////////
enum MimeBoundary
{
	MimeData = 0,
	MimeNextSeg = 1,
	MimeEndSeg = 2
};

MimeBoundary IsMimeBoundary(char *Boundary, char *Line)
{
	if (Boundary)
	{
		size_t BoundaryLen = strlen(Boundary);
		if (Line &&
			*Line++ == '-' &&
			*Line++ == '-' &&
			strncmp(Line, Boundary, strlen(Boundary)) == 0)
		{
			// MIME segment boundary
			Line += BoundaryLen;
			if (Line[0] == '-' &&
				Line[1] == '-')
			{
				return MimeEndSeg;
			}
			else
			{
				return MimeNextSeg;
			}
		}
	}

	return MimeData;
}

void CreateMimeBoundary(char *Buf, int BufLen)
{
	if (Buf)
	{
		static int Count = 1;
		sprintf_s(Buf, BufLen, "--%x-%x-%x--", (int)LCurrentTime(), (int)(uint64)LGetCurrentThread(), Count++);
	}
}

///////////////////////////////////////////////////////////////////////
class LCoderStream : public LStream
{
protected:
	LStreamI *Out;

public:
	LCoderStream(LStreamI *o)
	{
		Out = o;
	}
};

class LMimeTextEncode : public LCoderStream
{
	// This code needs to make sure it writes an end-of-line at
	// the end, otherwise a following MIME boundary could be missed.
	bool LastEol;
	
public:
	LMimeTextEncode(LStreamI *o) : LCoderStream(o)
	{
		LastEol = false;
	}
	
	~LMimeTextEncode()
	{
		if (!LastEol)
		{
			#ifdef _DEBUG
			ssize_t w =
			#endif
			Out->Write(MimeEol, 2);
			LAssert(w == 2);
		}
	}

	ssize_t Write(const void *p, ssize_t size, int f = 0)
	{
		// Make sure any new lines are \r\n
		char *s = (char*)p, *e = s + size;
		int wr = 0;
		
		while (s < e)
		{
			char *c = s;
			while (c < e && *c != '\r' && *c != '\n')
				c++;
			
			if (c > s)
			{
				ptrdiff_t bytes = c - s;
				ssize_t w = Out->Write(s, (int)bytes);
				if (w != bytes)
					return wr;
				wr += w;
				LastEol = false;
			}
			
			while (c < e && (*c == '\r' || *c == '\n'))
			{
				if (*c == '\n')
				{
					ssize_t w = Out->Write(MimeEol, 2);
					if (w != 2)
						return wr;
					LastEol = true;
				}
				wr++;
				c++;
			}

			s = c;
		}
		
		return wr;
	}
};

class GMimeQuotedPrintableEncode : public LCoderStream
{
	// 'd' is our current position in 'Buf'
	char *d;
	
	// A buffer for a line of quoted printable text
	char Buf[128];

public:
	GMimeQuotedPrintableEncode(LStreamI *o) : LCoderStream(o)
	{
		Buf[0] = 0;
		d = Buf;
	}
	
	~GMimeQuotedPrintableEncode()
	{
		if (d > Buf)
		{
			// Write partial line
			*d++ = '\r';
			*d++ = '\n';

			ptrdiff_t Len = d - Buf;
			Out->Write(Buf, CastInt(Len));
		}
	}

	ssize_t Write(const void *p, ssize_t size, int f = 0)
	{
		char *s = (char*)p;
		char *e = s + size;
		while (s < e)
		{
			if (*s == '\n' || !*s)
			{
				*d++ = '\r';
				*d++ = '\n';

				ptrdiff_t Len = d - Buf;
				if (Out->Write(Buf, CastInt(Len)) < Len)
				{
					LAssert(!"write error");
					break;
				}
				if (!*s)
				{
					break;
				}

				d = Buf;
				s++;
			}
			else if (*s & 0x80 ||
					 *s == '.' ||
					 *s == '=')
			{
				int Ch = sprintf_s(d, sizeof(Buf)-(d-Buf), "=%2.2X", (uchar)*s);
				if (Ch < 0)
				{
					LAssert(!"printf error");
					break;
				}
				d += Ch;
				s++;
			}
			else if (*s != '\r')
			{
				*d++ = *s++;
			}
			else
			{
				// Consume any '\r' without outputting them
				s++;
			}

			if (d-Buf > 73)
			{
				// time for a new line.
				*d++ = '=';
				*d++ = '\r';
				*d++ = '\n';

				ptrdiff_t Len = d-Buf;
				if (Out->Write(Buf, CastInt(Len)) < Len)
				{
					LAssert(!"write error");
					break;
				}

				d = Buf;
			}
		}

		return CastInt(s - (const char*)p);
	}
};

class GMimeQuotedPrintableDecode : public LCoderStream
{
	uint8_t ConvHexToBin(char c)
	{
		if (c >= '0' && c <= '9')
			return c - '0';
		if (c >= 'a' && c <= 'f')
			return c + 10 - 'a';
		if (c >= 'A' && c <= 'F')
			return c + 10 - 'A';
		return 0;
	}

public:
	GMimeQuotedPrintableDecode(LStreamI *o) : LCoderStream(o) {}

	ssize_t Write(const void *p, ssize_t size, int f = 0)
	{
		ssize_t Written = 0;
		
		char Line[1024];
		char *o = Line;
		const char *s = (const char*) p;
		const char *e = s + size;

		#define NEXT(ptr) if (++ptr >= e) break
		for (const char *s = (const char*)p; s < e; )
		{
			if (*s == '=')
			{
				NEXT(s); // skip '='

				if (*s == '\r' || *s == '\n')
				{
					if (*s == '\r')
						NEXT(s);
					if (*s == '\n')
						NEXT(s);
				}
				else if (*s)
				{
					uint8_t hi = ConvHexToBin(*s++);
					if (s >= e) break;
					uint8_t low = ConvHexToBin(*s++);
					if (s >= e) break;
					*o++ = (hi << 4) | (low & 0xF);
				}
				else break;
			}
			else
			{
				*o++ = *s++;
			}

			if (o - Line > 1000)
			{
				auto w = Out->Write(Line, o - Line);
				if (w > 0)
				{
					Written += w;
					o = Line;
				}
				else
					break; // Error
			}
		}

		if (o > Line)
		{
			auto w = Out->Write(Line, o - Line);
			if (w > 0)
				Written += w;
		}
		
		return Written;
	}
};

#define BASE64_LINE_SZ			76
#define BASE64_READ_SZ			(BASE64_LINE_SZ*3/4)

class LMimeBase64Encode : public LCoderStream
{
	LMemQueue Buf;

public:
	LMimeBase64Encode(LStreamI *o) : LCoderStream(o) {}
	
	~LMimeBase64Encode()
	{
		uchar b[100];

		int64 Len = Buf.GetSize();
		LAssert(Len < sizeof(b));
		ssize_t r = Buf.Read(b, CastInt(Len));
		if (r > 0)
		{
			char t[256];
			ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r);
			Out->Write(t, w);
			Out->Write(MimeEol, 2);
		}
	}

	ssize_t Write(const void *p, ssize_t size, int f = 0)
	{
		Buf.Write((uchar*)p, size);

		int64 Sz;
		while ((Sz = Buf.GetSize()) >= BASE64_READ_SZ)
		{
			uchar b[100];
			ssize_t r = Buf.Read(b, BASE64_READ_SZ);
			if (r)
			{
				char t[256];
				ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r);
				if (w > 0)
				{
					Out->Write(t, w);
					Out->Write(MimeEol, 2);
				}
				else LAssert(0);
			}
			else return 0;
		}

		return size;
	}
};


class GMimeBase64Decode : public LCoderStream
{
	LStringPipe Buf;
	uint8_t Lut[256];

public:
	GMimeBase64Decode(LStreamI *o) : LCoderStream(o)
	{
		ZeroObj(Lut);
		memset(Lut+(int)'a', 1, 'z'-'a'+1);
		memset(Lut+(int)'A', 1, 'Z'-'A'+1);
		memset(Lut+(int)'0', 1, '9'-'0'+1);
		Lut[(int)'+'] = 1;
		Lut[(int)'/'] = 1;
		Lut[(int)'='] = 1;
	}

	ssize_t Write(const void *p, ssize_t size, int f = 0)
	{
		int Status = 0;

		// Push non whitespace into the memory buf
		char *s = (char*)p;
		char *e = s + size;
		while (s < e)
		{
			while (*s && s < e && !Lut[(int)*s]) s++;
			char *Start = s;
			while (*s && s < e && Lut[(int)*s]) s++;
			if (s-Start > 0)
				Buf.Push(Start, CastInt(s-Start));
			else
				break;
		}

		// While there is at least one run of base64 (4 bytes) convert it to text
		// and write it to the output stream
		int Size;
		while ((Size = CastInt(Buf.GetSize())) > 3)
		{
			Size &= ~3;

			char t[256];
			ssize_t r = MIN(sizeof(t), Size);
			if ((r = Buf.LMemQueue::Read((uchar*)t, r)) > 0)
			{
				uchar b[256];
				ssize_t w = ConvertBase64ToBinary(b, sizeof(b), t, r);
				Out->Write(b, w);
				Status += w;
			}
		}

		return Status;
	}
};

///////////////////////////////////////////////////////////////////////
LMimeBuf::LMimeBuf(LStreamI *src, LStreamEnd *end)
{
	Total = 0;
	Src = src;
	End = end;

	Src->SetPos(0);
}

ssize_t LMimeBuf::Pop(LArray<char> &Out)
{
	ssize_t Ret = 0;

	while (!(Ret = LStringPipe::Pop(Out)))
	{
		if (Src)
		{
			char Buf[2048];
			ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0;
			if (r)
			{
				if (End)
				{
					ssize_t e = End->IsEnd(Buf, r);
					if (e >= 0)
					{
						// End of stream
						ssize_t s = e - Total;
						Push(Buf, s);
						Total += s;
						Src = 0; // no more data anyway
					}
					else
					{
						// Not the end
						Push(Buf, r);
						Total += r;
					}
				}
				else
				{
					Push(Buf, r);
					Total += r;
				}
			}
			else
			{
				Src = NULL; // Source data is finished
			}
		}
		else
		{
			// Is there any unterminated space in the string pipe?
			int64 Sz = LStringPipe::GetSize();
			if (Sz > 0)
			{
				if (Out.Length() < Sz)
					Out.Length(Sz);
				Ret = LStringPipe::Read(Out.AddressOf(), Sz);
			}
			break;
		}
	}

	return Ret;
}

ssize_t LMimeBuf::Pop(char *Str, ssize_t BufSize)
{
	ssize_t Ret = 0;

	while (!(Ret = LStringPipe::Pop(Str, BufSize)))
	{
		if (Src)
		{
			char Buf[1024];
			ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0;
			if (r)
			{
				if (End)
				{
					ssize_t e = End->IsEnd(Buf, r);
					if (e >= 0)
					{
						// End of stream
						ssize_t s = e - Total;
						Push(Buf, s);
						Total += s;
						Src = 0; // no more data anyway
					}
					else
					{
						// Not the end
						Push(Buf, r);
						Total += r;
					}
				}
				else
				{
					Push(Buf, r);
					Total += r;
				}
			}
			else
			{
				Src = NULL; // Source data is finished
			}
		}
		else
		{
			// Is there any unterminated space in the string pipe?
			int64 Sz = LStringPipe::GetSize();
			if (Sz > 0)
			{
				Ret = LStringPipe::Read(Str, BufSize);
			}
			break;
		}
	}

	return Ret;
}

///////////////////////////////////////////////////////////////////////
// Mime Object
LMime::LMime(const char *tmp)
{
	Parent = 0;
	TmpPath = NewStr(tmp);

	Headers = 0;
	DataPos = 0;
	DataSize = 0;
	DataLock = 0;
	DataStore = 0;
	OwnDataStore = 0;

	Text.Decode.Mime = this;
	Text.Encode.Mime = this;
	Binary.Read.Mime = this;
	Binary.Write.Mime = this;
}

LMime::~LMime()
{
	Remove();
	Empty();
	DeleteArray(TmpPath);
}

char *LMime::GetFileName()
{
	char *n = GetSub("Content-Type", "Name");
	if (!n)
		n = GetSub("Content-Disposition", "Filename");
	if (!n)
	{
		n = Get("Content-Location");
		if (n)
		{
			char *trim = TrimStr(n, "\'\"");
			if (trim)
			{
			    DeleteArray(n);
			    n = trim;
			}
		}
	}
	return n;
}

LMime *LMime::NewChild()
{
	LMime *n = new LMime(GetTmpPath());
	if (n)
		Insert(n);
	return n;
}

bool LMime::Insert(LMime *m, int Pos)
{
	LAssert(m != NULL);
	if (!m)
		return false;
	
	if (m->Parent)
	{
		LAssert(m->Parent->Children.HasItem(m));
		m->Parent->Children.Delete(m, true);
	}

	m->Parent = this;
	LAssert(!Children.HasItem(m));
	if (Pos >= 0)
		Children.AddAt(Pos, m);
	else
		Children.Add(m);

	return true;
}

void LMime::Remove()
{
	if (Parent)
	{
		LAssert(Parent->Children.HasItem(this));
		Parent->Children.Delete(this, true);
		Parent = 0;
	}
}

LMime *LMime::operator[](uint32_t i)
{
	if (i >= Children.Length())
		return 0;

	return Children[i];
}

char *LMime::GetTmpPath()
{
	for (LMime *m = this; m; m = m->Parent)
	{
		if (m->TmpPath)
			return m->TmpPath;
	}
	return 0;
}

bool LMime::SetHeaders(const char *h)
{
	DeleteArray(Headers);
	Headers = NewStr(h);
	return Headers != 0;
}

bool LMime::Lock()
{
	bool Lock = true;
	if (DataLock)
	{
		Lock = DataLock->Lock(_FL);
	}
	return Lock;
}

void LMime::Unlock()
{
	if (DataLock)
	{
		DataLock->Unlock();
	}
}

bool LMime::CreateTempData()
{
	bool Status = false;

	DataPos = 0;
	DataSize = 0;
	OwnDataStore = true;
	if ((DataStore = new GTempStream(GetTmpPath(), 4 << 20)))
	{
		Status = true;
	}

	return Status;
}

LStreamI *LMime::GetData(bool Detach)
{
	LStreamI *Ds = DataStore;

	if (Ds)
	{		
		Ds->SetPos(DataPos);
		if (Detach)
		{
			LOG("%s:%i - Detaching %p from %p\n", _FL, DataStore, this);
			DataStore = 0;
		}
	}

	return Ds;
}

bool LMime::SetData(bool OwnStream, LStreamI *d, int Pos, int Size, LMutex *l)
{
	if (DataStore && Lock())
	{
		DeleteObj(DataStore);
		Unlock();
	}

	if (d)
	{
		OwnDataStore = OwnStream;
		DataPos = Pos;
		DataSize = Size >= 0 ? Size : (int)d->GetSize();
		DataLock = l;
		DataStore = d;
	}

	return true;
}

bool LMime::SetData(char *Str, int Len)
{
	if (DataStore && Lock())
	{
		DeleteObj(DataStore);
		Unlock();
	}

	if (Str)
	{
		if (Len < 0)
		{
			Len = (int)strlen(Str);
		}

		DataLock = 0;
		DataPos = 0;
		DataSize = Len;
		DataStore = new GTempStream(GetTmpPath(), 4 << 20);
		if (DataStore)
		{
			DataStore->Write(Str, Len);
		}
	}

	return true;
}

void LMime::Empty()
{
	if (OwnDataStore)
	{
		if (Lock())
		{
			DeleteObj(DataStore);
			Unlock();
		}
	}

	while (Children.Length())
		delete Children[0];
	DeleteArray(Headers);

	DataPos = 0;
	DataSize = 0;
	DataLock = 0;
	DataStore = 0;
	OwnDataStore = 0;
}

char *LMime::NewValue(char *&s, bool Alloc)
{
	char *Status = 0;

	int Inc = 0;
	char *End;
	if (strchr(MimeStr, *s))
	{
		// Delimited string
		char Delim = *s++;
		End = strchr(s, Delim);
		Inc = 1;
	}
	else
	{
		// Raw string
		End = s;
		while (*End && *End != ';' && *End != '\n' && *End != '\r') End++;
		while (strchr(MimeWs, End[-1])) End--;
	}

	if (End)
	{
		if (Alloc)
		{
			Status = NewStr(s, End-s);
		}
		s = End + Inc;
	}

	SkipWs(s);

	return Status;
}

char *LMime::StartOfField(char *s, const char *Field)
{
	if (s && Field)
	{
		size_t FieldLen = strlen(Field);
		while (s && *s)
		{
			if (strchr(MimeWs, *s))
			{
				s = strchr(s, '\n');
				if (s) s++;
			}
			else
			{
				char *f = s;
				while (*s && *s != ':' && !strchr(MimeWs, *s)) s++;
				int fLen = CastInt(s - f);
				if (*s++ == ':' &&
					fLen == FieldLen &&
					_strnicmp(f, Field, FieldLen) == 0)
				{
					return f;
					break;
				}
				else
				{
					s = strchr(s, '\n');
					if (s) s++;
				}
			}
		}
	}

	return 0;
}

char *LMime::NextField(char *s)
{
	while (s)
	{
		while (*s && *s != '\n') s++;
		if (*s == '\n')
		{
			s++;
			if (!strchr(MimeWs, *s))
			{
				break;
			}
		}
		else
		{
			break;
		}
	}

	return s;
}

char *LMime::Get(const char *Name, bool Short, const char *Default)
{
	char *Status = 0;

	if (Name && Headers)
	{
		char *s = StartOfField(Headers, Name);
		if (s)
		{
			s = strchr(s, ':');
			if (s)
			{
				s++;
				SkipWs(s);
				if (Short)
				{
					Status = NewValue(s);
				}
				else
				{
					char *e = NextField(s);
					while (strchr(MimeWs, e[-1])) e--;
					Status = NewStr(s, e-s);
				}
			}
		}

		if (!Status && Default)
		{
			Status = NewStr(Default);
		}
	}

	return Status;
}

bool LMime::Set(const char *Name, const char *Value)
{
	if (!Name)
		return false;

	LStringPipe p;

	char *h = Headers;
	if (h)
	{
		char *f = StartOfField(h, Name);
		if (f)
		{
			// 'Name' exists, push out pre 'Name' header text
			p.Push(h, CastInt(f - Headers));
			h = NextField(f);
		}
		else
		{
			if (!Value)
			{
				// Nothing to do here...
				return true;
			}

			// 'Name' doesn't exist, push out all the headers
			p.Push(Headers);
			h = 0;
		}
	}

	if (Value)
	{
		// Push new field
		int Vlen = CastInt(strlen(Value));
		while (Vlen > 0 && strchr(MimeWs, Value[Vlen-1])) Vlen--;

		p.Push(Name);
		p.Push(": ");
		p.Push(Value, Vlen);
		p.Push(MimeEol);
	}
	// else we're deleting the feild

	if (h)
	{
		// Push out any header text post the 'Name' field.
		p.Push(h);
	}

	DeleteArray(Headers);
	Headers = (char*)p.New(sizeof(char));

	return Headers != NULL;
}

char *LMime::GetSub(const char *Field, const char *Sub)
{
	char *Status = 0;

	if (Field && Sub)
	{
		int SubLen = CastInt(strlen(Sub));
		char *v = Get(Field, false);
		if (v)
		{
			// Move past the field value into the sub fields
			char *s = v;
			SkipWs(s);
			while (*s && *s != ';' && !strchr(MimeWs, *s)) s++;
			SkipWs(s);
			while (s && *s++ == ';')
			{
				// Parse each name=value pair
				SkipWs(s);
				char *Name = s;
				while (*s && *s != '=' && !strchr(MimeWs, *s)) s++;
				int NameLen = CastInt(s - Name);
				SkipWs(s);
				if (*s++ == '=')
				{
					bool Found = SubLen == NameLen && _strnicmp(Name, Sub, NameLen) == 0;
					SkipWs(s);
					Status = NewValue(s, Found);
					if (Found) break;
				}
				else break;
			}

			DeleteArray(v);
		}
	}

	return Status;
}

bool LMime::SetSub(const char *Field, const char *Sub, const char *Value, const char *DefaultValue)
{
	if (Field && Sub)
	{
		char Buf[256];

		char *s = StartOfField(Headers, Field);
		if (s)
		{
			// Header already exists
			s = strchr(s, ':');
			if (s++)
			{
				SkipWs(s);

				LStringPipe p;

				// Push the field data
				char *e = s;
				while (*e && !strchr("; \t\r\n", *e)) e++;
				p.Push(s, CastInt(e-s));
				SkipWs(e);

				// Loop through the subfields and push all those that are not 'Sub'
				s = e;
				while (*s++ == ';')
				{
					SkipWs(s);
					char *e = s;
					while (*e && *e != '=' && !strchr(MimeWs, *e)) e++;
					char *Name = NewStr(s, e-s);
					if (Name)
					{
						s = e;
						SkipWs(s);
						if (*s++ == '=')
						{
							char *v = NewValue(s);
							if (_stricmp(Name, Sub) != 0)
							{
								sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Name, v);
								p.Push(Buf);
							}
							DeleteArray(v);
						}
						else break;
					}
					else break;
				}

				if (Value)
				{
					// Push the new sub field
					sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Sub, Value);
					p.Push(Buf);
				}

				char *Data = p.NewStr();
				if (Data)
				{
					Set(Field, Data);
					DeleteArray(Data);
				}
			}
		}
		else if (DefaultValue)
		{
			// Header doesn't exist at all
			if (Value)
			{
				// Set
				sprintf_s(Buf, sizeof(Buf), "%s;\r\n\t%s=\"%s\"", DefaultValue, Sub, Value);
				return Set(Field, Buf);
			}
			else
			{
				// Remove
				return Set(Field, DefaultValue);
			}
		}
	}

	return false;
}

/////////////////////////////////////////////////////////////////////////
// Mime Text Conversion

// Rfc822 Text -> Object
ssize_t LMime::LMimeText::LMimeDecode::Pull(LStreamI *Source, LStreamEnd *End)
{
	LMimeBuf Buf(Source, End); // Stream -> Lines
	return Parse(&Buf);
}

class ParentState
{
public:
	char *Boundary;
	MimeBoundary Type;

	ParentState()
	{
		Boundary = 0;
		Type = MimeData;
	}
};

int LMime::LMimeText::LMimeDecode::Parse(LStringPipe *Source, ParentState *State)
{
	int Status = 0;

	if (!Mime || !Source)
	{
		LOG("%s:%i - Arg error %p %p.\n", _FL, Mime, Source);
		return Status;
	}
	
	Mime->Empty();
	if (!Mime->CreateTempData())
	{
		LOG("CreateTempData failed.\n");
		LAssert(!"CreateTempData failed.");
		return Status;
	}
	
	LAssert(Mime->DataStore != NULL);

	// Read the headers..
	LStringPipe HeaderBuf;
	ssize_t r;

	if (Buffer.Length() == 0)
		Buffer.Length(1 << 10);

	LOG("%s:%i - Reading headers...\n", _FL);
	while ((r = Source->Pop(Buffer)) > 0)
	{
		if (!strchr(MimeEol, Buffer[0]))
		{
			// Store part of the headers
			HeaderBuf.Push(Buffer.AddressOf(), r);
		}
		else break;
	}

	if (r < 0)
		return 0;

	// Not an error
	Mime->Headers = HeaderBuf.NewStr();
	LOG("%s:%i - Mime->Headers=%i\n", _FL, Mime->Headers?(int)strlen(Mime->Headers):-1);

	// Get various bits out of the header
	char *Encoding = Mime->GetEncoding();
	char *Boundary = Mime->GetBoundary();
	LAutoString MimeType(Mime->GetMimeType());
	LOG("%s:%i - Encoding=%s, MimeType=%s, Boundary=%s\n", _FL, Encoding, MimeType.Get(), Boundary);

	LStream *Decoder = 0;
	if (Encoding)
	{
		if (_stricmp(Encoding, MimeQuotedPrintable) == 0)
		{
			Decoder = new GMimeQuotedPrintableDecode(Mime->DataStore);
			LOG("%s:%i - Using GMimeQuotedPrintableDecode\n", _FL);
		}
		else if (_stricmp(Encoding, MimeBase64) == 0)
		{
			Decoder = new GMimeBase64Decode(Mime->DataStore);
			LOG("%s:%i - Using GMimeBase64Decode\n", _FL);
		}
		else
		{
			LOG("%s:%i - Unknown encoding '%s'\n", _FL, Encoding);
		}
	}
	DeleteArray(Encoding);

	// Read in the rest of the MIME segment
	bool Done = false;
	// int64 StartPos = Mime->DataStore->GetPos();
	while (!Done)
	{
		// Process existing lines
		ssize_t Len;
		ssize_t Written = 0;

		Status = true;
		while ((Len = Source->Pop(Buffer)) > 0)
		{
			// Check for boundary
			MimeBoundary Type = MimeData;
			auto b = Buffer.AddressOf();

			if (Boundary)
			{
				bool CouldBe = Buffer.Length() > 2 && b[0] == '-' && b[1] == '-';
				Type = IsMimeBoundary(Boundary, b);
				
				if (Type)
				{
					LOG("%s:%i - IsMimeBoundary=%i\n", _FL, Type);
				}
				else if (CouldBe)
				{
					LOG("%s:%i - CouldBe '%s'\n", _FL, b);
				}
			}
			
			if (State)
			{
				State->Type = IsMimeBoundary(State->Boundary, b);
				if (State->Type)
				{
					LOG("%s:%i - IsMimeBoundary=%i\n", _FL, State->Type);
					Status = Done = true;
					break;
				}
			}

			DoSegment:
			if (Type == MimeNextSeg)
			{
				ParentState MyState;
				MyState.Boundary = Boundary;

				LMime *Seg = new LMime(Mime->GetTmpPath());
				if (Seg &&
					Seg->Text.Decode.Parse(Source, &MyState))
				{
					LOG("%s:%i - Inserting child seg.\n", _FL);
					Mime->Insert(Seg);

					if (MyState.Type)
					{
						Type = MyState.Type;
						goto DoSegment;
					}
				}
				else
				{
					LOG("%s:%i - Text.Decode.Parse failed.\n", _FL);
					break;
				}
			}
			else if (Type == MimeEndSeg)
			{
				Done = true;
				LOG("%s:%i - MimeEndSeg.\n", _FL);
				break;
			}
			else
			{
				// Process data
				if (Decoder)
				{
					Written += Decoder->Write(Buffer.AddressOf(), Len);
				}
				else
				{
					ssize_t w = Mime->DataStore->Write(Buffer.AddressOf(), Len);
					if (w > 0)
					{
						Written += w;
					}
					else
					{
						LOG("%s:%i - w 0\n", _FL);
						Done = true;
						Status = false;
						break;
					}
				}
			}
		}

		Mime->DataSize = Written;
		if (Len == 0)
		{
			LOG("%s:%i - Len 0\n", _FL);
			Done = true;
		}
	}

	DeleteObj(Decoder);
	DeleteArray(Boundary);

	LOG("%s:%i - Finished\n", _FL);
	return Status;
}

void LMime::LMimeText::LMimeDecode::Empty()
{
}

// Object -> Rfc822 Text
ssize_t LMime::LMimeText::LMimeEncode::Push(LStreamI *Dest, LStreamEnd *End)
{
	int Status = 0;

	if (Mime)
	{
		char Buf[1024];
		int Ch;

		// Check boundary
		char *Boundary = Mime->GetBoundary();
		if (Mime->Children.Length())
		{
			// Boundary required
			if (!Boundary)
			{
				// Create one
				char b[256];
				CreateMimeBoundary(b, sizeof(b));
				Mime->SetBoundary(b);
				Boundary = Mime->GetBoundary();
			}
		}
		else if (Boundary)
		{
			// Remove boundary
			Mime->SetBoundary(0);
			DeleteArray(Boundary);
		}

		// Check encoding
		char *Encoding = Mime->GetEncoding();
		if (!Encoding)
		{
			// Detect an appropriate encoding
			int MaxLine = 0;
			bool Has8Bit = false;
			bool HasBin = false;
			if (Mime->DataStore &&
				Mime->Lock())
			{
				Mime->DataStore->SetPos(Mime->DataPos);
				int x = 0;
				for (int i=0; i<Mime->DataSize; )
				{
					ssize_t m = MIN(Mime->DataSize - i, sizeof(Buf));
					ssize_t r = Mime->DataStore->Read(Buf, m);
					if (r > 0)
					{
						for (int n=0; n<r; n++)
						{
							if (Buf[n] == '\n')
							{
								MaxLine = MAX(x, MaxLine);
								x = 0;
							}
							else
							{
								x++;
							}

							if (Buf[n] & 0x80)
							{
								Has8Bit = true;
							}
							if (((uint8_t)Buf[n]) < ' ' &&
								Buf[n] != '\t' &&
								Buf[n] != '\r' &&
								Buf[n] != '\n')
							{
								HasBin = true;
							}
						}

						i += r;
					}
					else break;
				}
				
				Mime->Unlock();			
			}

			if (HasBin)
			{
				Encoding = NewStr(MimeBase64);
			}
			else if (Has8Bit || MaxLine > 70)
			{
				Encoding = NewStr(MimeQuotedPrintable);
			}

			if (Encoding)
			{
				Mime->SetEncoding(Encoding);
			}
		}

		// Write the headers
		auto h = LString(Mime->Headers).SplitDelimit(MimeEol);
		for (unsigned i=0; i<h.Length(); i++)
		{
			Dest->Write(h[i], CastInt(strlen(h[i])));
			Dest->Write(MimeEol, 2);
		}
		Dest->Write(MimeEol, 2);

		// Write data
		LStream *Encoder = 0;
		if (Encoding)
		{
			if (_stricmp(Encoding, MimeQuotedPrintable) == 0)
			{
				Encoder = new GMimeQuotedPrintableEncode(Dest);
			}
			else if (_stricmp(Encoding, MimeBase64) == 0)
			{
				Encoder = new LMimeBase64Encode(Dest);
			}
		}
		if (!Encoder)
		{
			Encoder = new LMimeTextEncode(Dest);
		}

		if (Mime->DataStore)
		{
			if (Mime->Lock())
			{
				Mime->DataStore->SetPos(Mime->DataPos);
				Status = Mime->DataSize == 0; // Nothing is a valid segment??
				for (int i=0; i<Mime->DataSize; )
				{
					ssize_t m = MIN(Mime->DataSize-i, sizeof(Buf));
					ssize_t r = Mime->DataStore->Read(Buf, m);
					if (r > 0)
					{
						Encoder->Write(Buf, r);
						Status = true;
					}
					else break;
				}
				Mime->Unlock();
			}
		}
		else
		{
			Status = true;
		}

		DeleteObj(Encoder);

		// Write children
		if (Mime->Children.Length() && Boundary)
		{
			LAutoString Mt(Mime->GetMimeType());
			if (Mt && !_stricmp(Mt, "multipart/alternative"))
			{
				// Sort the children to order richer content at the bottom...
				Mime->Children.Sort(AltSortCmp);
			}
			
			for (unsigned i=0; i<Mime->Children.Length(); i++)
			{
				Ch = sprintf_s(Buf, sizeof(Buf), "--%s\r\n", Boundary);
				Dest->Write(Buf, Ch);

				if (!Mime->Children[i]->Text.Encode.Push(Dest, End))
				{
					break;
				}

				Status = 1;
			}

			Ch = sprintf_s(Buf, sizeof(Buf), "--%s--\r\n", Boundary);
			Dest->Write(Buf, Ch);
		}

		// Clean up
		DeleteArray(Encoding);
		DeleteArray(Boundary);
	}

	return Status;
}

void LMime::LMimeText::LMimeEncode::Empty()
{
}

/////////////////////////////////////////////////////////////////////////
// Mime Binary Serialization

// Source -> Object
ssize_t LMime::LMimeBinary::LMimeRead::Pull(LStreamI *Source, LStreamEnd *End)
{
	if (Source)
	{
		int32 Header[4];
		Mime->Empty();

		// Read header block (Magic, HeaderSize, DataSize, # of Children)
		// and check magic
		if (Source->Read(Header, sizeof(Header)) == sizeof(Header) &&
			Header[0] == MimeMagic)
		{
			// Read header data
			Mime->Headers = new char[Header[1]+1];
			if (Mime->Headers &&
				Source->Read(Mime->Headers, Header[1]) == Header[1])
			{
				// NUL terminate
				Mime->Headers[Header[1]] = 0;

				// Skip body data
				if (Source->SetPos(Source->GetPos() + Header[2]) > 0)
				{
					// Read the children in
					for (int i=0; i<Header[3]; i++)
					{
						LMime *c = new LMime(Mime->GetTmpPath());
						if (c &&
							c->Binary.Read.Pull(Source, End))
						{
							Mime->Insert(c);
						}
						else break;
					}
				}
			}

			return 1; // success
		}
	}

	return 0; // failure
}

void LMime::LMimeBinary::LMimeRead::Empty()
{
}

// Object -> Dest
int64 LMime::LMimeBinary::LMimeWrite::GetSize()
{
	int64 Size = 0;

	if (Mime)
	{
		Size =	(sizeof(int32) * 4) + // Header magic + block sizes
				(Mime->Headers ? strlen(Mime->Headers) : 0) + // Headers
				(Mime->DataStore ? Mime->DataSize : 0); // Data

		// Children
		for (unsigned i=0; i<Mime->Children.Length(); i++)
		{
			Size += Mime->Children[i]->Binary.Write.GetSize();
		}
	}

	return Size;
}

ssize_t LMime::LMimeBinary::LMimeWrite::Push(LStreamI *Dest, LStreamEnd *End)
{
	if (Dest && Mime)
	{
		int32 Header[4] =
		{
			MimeMagic,
			Mime->Headers ? (int32)strlen(Mime->Headers) : 0,
			Mime->DataStore ? (int32)Mime->DataSize : 0,
			(int32) Mime->Children.Length()
		};

		if (Dest->Write(Header, sizeof(Header)) == sizeof(Header))
		{
			if (Mime->Headers)
			{
				Dest->Write(Mime->Headers, Header[1]);
			}

			if (Mime->DataStore)
			{
				char Buf[1024];
				ssize_t Written = 0;
				ssize_t Read = 0;
				ssize_t r;
				while ((r = Mime->DataStore->Read(Buf, MIN(sizeof(Buf), Header[2]-Read) )) > 0)
				{
					ssize_t w;
					if ((w = Dest->Write(Buf, r)) <= 0)
					{
						// Write error
						break;
					}

					Written += w;
					Read += r;
				}

				// Check we've written out all the data
				LAssert(Written < Header[2]);
				if (Written < Header[2])
				{
					return 0;
				}
			}

			for (unsigned i=0; i<Mime->Children.Length(); i++)
			{
				Mime->Children[i]->Binary.Write.Push(Dest, End);
			}

			return 1;
		}
	}

	return 0;
}

void LMime::LMimeBinary::LMimeWrite::Empty()
{
}

