#ifndef _TEXT_FILE_H_
#define _TEXT_FILE_H_

class GTextFile : public LFile
{
public:
	enum EncodingType
	{
		Unknown,
		Utf8,
		Utf16BE,
		Utf16LE,
		Utf32BE,
		Utf32LE,
	};

protected:
	bool First;
	size_t Used;
	bool InEndOfLine;
	LPointer Pos;
	EncodingType Type;
	LArray<uint8_t> Buf;
	LAutoString Charset;

public:
	GTextFile(const char *charset = NULL)
	{
		First = true;
		InEndOfLine = false;
		Used = 0;
		Pos.u8 = NULL;
		Type = Unknown;
		if (charset)
			Charset.Reset(NewStr(charset));
	}
	
	EncodingType GetType()
	{
		return Type;
	}
	
	const char *GetTypeString()
	{
		switch (Type)
		{
			case Utf8: return "utf-8";
			case Utf16BE: return "utf-16be";
			case Utf16LE: return "utf-16";
			case Utf32BE: return "utf-32be";
			case Utf32LE: return "utf-32";
			default: break;
		}
		return NULL;
	}

	bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override
	{
		if (LStringToDomProp(Name) == FileEncoding)
		{
			Value = (int)Type;
			return true;
		}
		
		return LFile::GetVariant(Name, Value, Array);
	}
	
	/// Read the whole file as utf-8
	LAutoString Read()
	{
		LAutoString Ret;
		size_t Sz = (size_t)GetSize();
		if (Sz > 0)
		{
			LAutoPtr<uint8_t, true> Buf(new uint8_t[Sz]);
			if (Buf)
			{
				ssize_t Rd = Read(Buf, (ssize_t)Sz);
				if (Rd > 0)
				{
					const char *Cs = GetTypeString();
					if (Cs)
					{
						// printf("Text file read: %s\n", GetName());
						Ret.Reset((char*)LNewConvertCp("utf-8", Buf, Cs, Rd));
					}
				}
			}
		}
		
		return Ret;
	}
	
	/// Read the whole file as wchar_t
	LAutoWString ReadW()
	{
		LAutoWString Ret;
		int Sz = (int)GetSize();
		if (Sz > 0)
		{
			LAutoPtr<uint8_t, true> Buf(new uint8_t[Sz]);
			if (Buf)
			{
				ssize_t Rd = Read(Buf, Sz);
				if (Rd > 0)
				{
					const char *Cs = GetTypeString();
					if (Cs)
						Ret.Reset((char16*)LNewConvertCp(LGI_WideCharset, Buf, Cs, Sz));
				}
			}
		}
		
		return Ret;
	}

	template<typename T>
	bool CheckForNull(T *ptr, uint8_t *end)
	{
		while ((uint8_t*)ptr < (end-sizeof(T)))
		{
			if (*ptr == 0)
				return false;
			ptr++;
		}
		return true;
	}
		
	ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) override
	{
		ssize_t Rd = LFile::Read(Buffer, Size, Flags);
		if (First)
		{
			if (Rd < 4)
			{
				// LAssert(!"Initial read is too small");
				return 0;
			}
			else
			{
				First = false;

				uint8_t *buf = (uint8_t*)Buffer;
				uint8_t *start = buf;
				if (Used > 2 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF)
				{
					Type = Utf8;
					start += 3;
				}
				else if (Used > 1 && buf[0] == 0xFE && buf[1] == 0xFF)
				{
					Type = Utf16BE;
					start += 2;
				}
				else if (Used > 1 && buf[0] == 0xFF && buf[1] == 0xFE)
				{
					Type = Utf16LE;
					start += 2;
				}
				else if (Used > 3 && buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF)
				{
					Type = Utf32BE;
					start += 4;
				}
				else if (Used > 3 && buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00)
				{
					Type = Utf32LE;
					start += 4;
				}
				else
				{
					// Try and detect the char type
					uint8_t *end = buf + Rd;
					if (CheckForNull(buf, end))
						Type = Utf8;
					else if (CheckForNull((uint16*)buf, end))
						Type = Utf16LE;
					else 
						Type = Utf32LE;
				}
				
				if (start > buf)
				{
					ssize_t bytes = start - buf;
					if (bytes <= Rd)
					{
						// Remove byte order mark from the buffer
						memmove(buf, start, Rd - bytes);
						Rd -= bytes;
					}
				}
			}
		}
		
		return Rd;
	}

	bool FillBuffer()
	{
		if (!Buf.Length())
		{
			Buf.Length(4 << 10);
			Pos.u8 = &Buf[0];
			Used = 0;
		}
		
		if (Buf.Length())
		{
			// Move any consumed data down to the start of the buffer
			size_t BytePos = Pos.u8 - &Buf[0];
			size_t Remaining = Used - BytePos;
			if (BytePos > 0 && Remaining > 0)
			{
				memmove(&Buf[0], &Buf[BytePos], Remaining);
				Used = Remaining;
			}
			else
			{
				Used = 0;
			}
			Pos.u8 = &Buf[0];
			
			// Now read some more data into the free space
			Remaining = Buf.Length() - Used;
			LAssert(Remaining > 0);
			ssize_t Rd = Read(&Buf[Used], Remaining);
			if (Rd <= 0)
				return 0;
			
			Used += Rd;			
		}
		
		return true;
	}
	
	template<typename T>
	int GetLine(LArray<T> &Out)
	{
		uint8_t *End = NULL;
		
		int OutPos = 0;
		if (Buf.Length())
			End = &Buf[0] + Used;
		
		while (true)
		{
			if (!End || End - Pos.u8 < sizeof(T))
			{
				if (!FillBuffer())
					break;
				End = &Buf[0] + Used;
			}

			if (End - Pos.u8 < sizeof(T))
				break;
			
			T ch = 0;
			switch (Type)
			{
				case Utf16BE:
					LAssert(sizeof(ch) == 2);
					ch = LgiSwap16(*Pos.u16);
					Pos.u16++;
					break;
				case Utf16LE:
					LAssert(sizeof(ch) == 2);
					ch = *Pos.u16++;
					break;
				case Utf32BE:
					LAssert(sizeof(ch) == 4);
					ch = LgiSwap32(*Pos.u32);
					Pos.u32++;
					break;
				case Utf32LE:
					LAssert(sizeof(ch) == 4);
					ch = *Pos.u32++;
					break;
				default: // Utf8
				{
					ssize_t len = (int) (End - Pos.u8);
					ch = LgiUtf8To32(Pos.u8, len);
					break;
				}
			}
			
			if (ch == 0)
				break;

			if (ch == '\r' || ch == '\n')
			{
				if (InEndOfLine)
				{
					continue;
				}
				else
				{
					InEndOfLine = true;
					break;
				}
			}
			else
			{
				InEndOfLine = false;
			}
			
			Out[OutPos++] = ch;
		}
		
		Out[OutPos] = 0; // NULL terminate				
		return OutPos;
	}
};

#endif
