#define UNICODE

#include <Core/Core.h>

using namespace Upp;

#include <iostream>

/* static */ class UTFBOM
{
protected:

	static bool Eq(char v1, unsigned char v2) { return (v1 == (char)v2); }

public:
	
	static int ReadBOM(const String& s, String* os, WString* ows)
	{
		// UTF-8 / Unknown -> String (UTF-8)
		// UTF-16 / UTF-32 -> WString (UTF-16)
		int len = s.GetCount();
		if (os) os->Clear();
		if (ows) ows->Clear();
		if (len >= 3 && Eq(s[0], 0xEF) && Eq(s[1], 0xBB) && Eq(s[2], 0xBF)) // UTF-8
		{
			if (os) (*os) <<= s.Mid(3);
			return 1;
		}
		else if (len >= 2 && Eq(s[0], 0xFF) && Eq(s[1], 0xFE)) // UTF-16 LE
		{
			if (ows)
			{
				len -= 2;
				ows->Cat(0, len / 2);
				for (int i = 0; i < len; i += 2)
					ows->Set(i / 2, (s[(i + 2) + 1] << 8) | (unsigned char)s[i + 2]);
			}
			return 2;
		}
		else if (len >= 2 && Eq(s[0], 0xFE) && Eq(s[1], 0xFF)) // UTF-16 BE
		{
			if (ows)
			{
				len -= 2;
				ows->Cat(0, len / 2);
				for (int i = 0; i < len; i += 2)
					ows->Set(i / 2, (s[i + 2] << 8) | (unsigned char)s[(i + 2) + 1]);
			}
			return 2;
		}
		else if (len >= 4 && Eq(s[0], 0xFF) && Eq(s[1], 0xFE) && Eq(s[2], 0) && Eq(s[3], 0)) // UTF-32 LE
		{
			// Not yet implemented
			return 2;
		}
		else if (len >= 4 && Eq(s[0], 0) && Eq(s[1], 0) && Eq(s[2], 0xFE) && Eq(s[3], 0xFF)) // UTF-32 BE
		{
			// Not yet implemented
			return 2;
		}
		else
		{
			if (os) (*os) <<= s;
			return 0;	
		}
	}
	
	static String WriteBOM(const String& s, int bytes, bool BOM = true, bool LE = true)
	{
		String os;
		int len = s.GetCount();
		if (bytes < 2) // UTF-8
		{
			if (BOM)
			{
				os.Reserve(len + 3);
				os.Cat(0xEF); os.Cat(0xBB); os.Cat(0xBF);
				os.Cat(s);
			}
			else
				os.Cat(s);
		}
		else if (bytes == 2) // UTF-16
		{
			WString tmp = s.ToWString();
			int tlen = tmp.GetCount();
			if (BOM)
			{
				os.Reserve(tlen * 2 + 2);
				if (LE) { os.Cat(0xFF); os.Cat(0xFE); } else { os.Cat(0xFE); os.Cat(0xFF); };
			}
			else
				os.Reserve(tlen);
			if (LE)
			{
				for (int i = 0; i < tlen; ++i)
				{
					os.Cat(tmp[i] & 0x00FF);
					os.Cat(tmp[i] >> 8);
				}
			}
			else
			{
				for (int i = 0; i < tlen; ++i)
				{
					os.Cat(tmp[i] >> 8);
					os.Cat(tmp[i] & 0x00FF);
				}
			}
		}
		else if (bytes == 4) // UTF-32
		{
			// Not yet implemented
		}
		return os;
	}
	
	static String WriteBOM(const WString& s, int bytes, bool BOM = true, bool LE = true)
	{
		String os;
		int len = s.GetCount();
		if (bytes < 2) // UTF-8
		{
			String tmp = s.ToString();
			int tlen = tmp.GetCount();
			if (BOM)
			{
				os.Reserve(tlen + 3);
				os.Cat(0xEF); os.Cat(0xBB); os.Cat(0xBF);
				os.Cat(tmp);
			}
			else
				os.Cat(tmp);
		}
		else if (bytes == 2) // UTF-16
		{
			if (BOM)
			{
				os.Reserve(len * 2 + 2);
				if (LE) { os.Cat(0xFF); os.Cat(0xFE); } else { os.Cat(0xFE); os.Cat(0xFF); };
			}
			else
				os.Reserve(len);
			if (LE)
			{
				for (int i = 0; i < len; ++i)
				{
					os.Cat(s[i] & 0x00FF);
					os.Cat(s[i] >> 8);
				}
			}
			else
			{
				for (int i = 0; i < len; ++i)
				{
					os.Cat(s[i] >> 8);
					os.Cat(s[i] & 0x00FF);
				}
			}
		}
		else if (bytes == 4) // UTF-32
		{
			// Not yet implemented
		}
		return os;
	}
	
};

CONSOLE_APP_MAIN
{
	String binary = LoadFile("FName.txt");
	String sEncUTF8;
	UTFBOM::ReadBOM(binary, &sEncUTF8, 0);
	SaveFile("FRead.txt", UTFBOM::WriteBOM(sEncUTF8, 2));
	SaveFile("FRead2.txt", UTFBOM::WriteBOM(sEncUTF8, 1));
	String unibin = LoadFile(sEncUTF8);
	SaveFile("FBin.txt", unibin);
	UTFBOM::ReadBOM(unibin, &sEncUTF8, 0);
	SaveFile("FCont.txt", UTFBOM::WriteBOM(sEncUTF8, 2));
	SaveFile("FCont2.txt", UTFBOM::WriteBOM(sEncUTF8, 1));
	
	int wait;
	std::cin >> wait;
}

