#pragma once

#include "lgi/common/StringClass.h"

enum LSTypes
{
	LInt,
	LStr,
	LFloat,
	LObject,
	LBinary,
};

class LSerialize
{
	#ifdef WIN32
	#pragma pack(push, before_pack)
	#pragma pack(1)
	#endif
	struct Str
	{
		uint32_t Length;
		union {
			char u[1];
			char16 w[1];
		};
	};
	struct Field
	{
		uint8_t Type;
		uint8_t Size;
		uint16 Id;
		union {
			char bytes[1];
			Str s;
			uint8_t u8;
			uint16 u16;
			uint32_t u32;
			uint64 u64;
			float f;
			double d;
		};

		bool IsValid()
		{
			switch (Type)
			{
				case LInt:
					switch (Size)
					{
						case 1:
						case 2:
						case 4:
						case 8:
							return true;
					}
					return false;
				case LStr:
					if (Size == 1)
						return s.u[s.Length] == 0;
					else if (Size == sizeof(char16))
						return s.w[s.Length] == 0;
					return false;
				case LBinary:
					if (Size != 1)
						return false;
					return true;
				case LFloat:
					return Size == sizeof(f);
				case LObject:
					return true;
			}

			return false;
		}

		uint32_t Sizeof()
		{
			switch (Type)
			{
				case LInt:
				case LFloat:
					return 4 + Size;
				case LStr:
					return 8 + (Size * (s.Length + 1));
				case LObject:
					return 4 + u32;
				case LBinary:
					return 8 + s.Length;
			}

			return sizeof(*this);
		}
	}
	#ifndef WIN32
	__attribute__((packed))
	#endif
		;
	#ifdef WIN32
	#pragma pack(pop, before_pack)
	#endif

	uint16 ObjectId;
	bool ToStream;
	LStreamI *Stream;
	LArray<char> FieldMem;
	LHashTbl<IntKey<int>, ssize_t> Fields;
	uint32_t Bytes;

protected:
	Field *GetField(int Id)
	{
		ssize_t o = Fields.Find(Id);
		if (o >= 0)
			return (Field*)FieldMem.AddressOf(o);
		return NULL;
	}

	template<typename T>
	bool Int(uint16 Id, T &i)
	{
		if (ToStream)
		{
			Field f;
			f.Type = LInt;
			f.Id = Id;
			f.Size = sizeof(i);
			int Bytes = 4 + f.Size;
			memcpy(f.bytes, &i, sizeof(i));

			if (Stream->Write(&f, Bytes) != Bytes)
				return false;
		}
		else
		{
			Field *f = GetField(Id);
            if (!f)
                return false;

			switch (f->Size)
			{
				case 1: i = (T)f->u8; break;
				case 2: i = (T)f->u16; break;
				case 4: i = (T)f->u32; break;
				case 8: i = (T)f->u64; break;
				default: return false;
			}
		}

		return true;
	}

	bool String(uint16 Id, LString &s)
	{
		if (ToStream)
		{
			Field f;
			f.Type = LStr;
			f.Size = 1;
			f.Id = Id;
			f.s.Length = (uint32_t)s.Length();
			ssize_t bytes = (char*)&f.s.u - (char*)&f;
			if (Stream->Write(&f, bytes) != bytes)
				return false;
			if (Stream->Write(s.Get(), s.Length()) != s.Length())
				return false;
		}
		else
		{
			Field *f = GetField(Id);
			if (!f || f->Type != LStr)
				return false;
			return s.Set(f->s.u, f->s.Length);
		}

		return false;
	}

	Field *Alloc(int Id, size_t Sz)
	{
		Field *f = GetField(Id);
		if (f && f->Sizeof() >= Sz)
			return f;

		size_t Off = FieldMem.Length();
		FieldMem.Length(Off + Sz);
		f = (Field*) FieldMem.AddressOf(Off);
		f->Id = Id;
		Fields.Add(Id, Off);
		return f;		
	}

public:
	LSerialize(uint16 ObjId = 0) : Fields(64,-1)
	{
		ObjectId = ObjId;
		ToStream = true;
		Stream = NULL;
	}

	virtual ~LSerialize()
	{
	}

	uint16 GetObjectId()
	{
		return ObjectId;
	}

	size_t GetSize()
	{
		size_t Sz = 0;
		
		// for (ssize_t o = Fields.First(); o >= 0; o = Fields.Next())
		for (auto o : Fields)
		{
			Field *i = (Field*)FieldMem.AddressOf(o.value);
			Sz += i->Sizeof();
		}

		return Sz;
	}

	void Empty()
	{
		ObjectId = 0;
		Fields.Empty();
		FieldMem.Length(0);
	}

	int64 GetInt(int Id, int Default = -1)
	{
		Field *f = GetField(Id);
		if (!f || f->Type == LObject)
			return Default;

		switch (f->Type)
		{
			case LInt:
				switch (f->Size)
				{
					case 1:
						return f->u8;
					case 2:
						return f->u16;
					case 4:
						return f->u32;
					case 8:
						return f->u64;
				}
			case LFloat:
				return (int)f->f;
			case LStr:
				if (f->Size == 1)
					return atoi(f->s.u);
				else if (f->Size == 2)
					return AtoiW(f->s.w);
				else
					LAssert(!"Invalid string size");
				break;
			default:
				LAssert(!"Invalid type.");
		}

		return Default;
	}

	float GetFloat(int Id, float Default = 0.0)
	{
		Field *f = GetField(Id);
		if (!f || f->Type == LObject)
			return Default;

		switch (f->Type)
		{
			case LInt:
				switch (f->Size)
				{
					case 1:
						return (float)f->u8;
					case 2:
						return (float)f->u16;
					case 4:
						return (float)f->u32;
					case 8:
						return (float)f->u64;
				}
			case LFloat:
				if (f->Size == sizeof(float))
					return f->f;
				else if (f->Size == sizeof(double))
					return (float)f->d;
				else
					LAssert(!"Invalid size.");
				break;
			case LStr:
				if (f->Size == 1)
					return (float)atof(f->s.u);
				else if (f->Size == 2)
				{
					LString s(f->s.w);
					return (float)s.Float();
				}
				else
					LAssert(!"Invalid string size");
				break;
			default:
				LAssert(!"Invalid type.");
				break;
		}

		return Default;
	}


	const char *GetStr(int Id, const char *Default = NULL)
	{
		Field *f = GetField(Id);
		if (!f || f->Type != LStr)
			return Default;
		
		if (f->Size == 1)
			return f->s.u;
		else
			LAssert(!"Request for wrong string width");

		return Default;
	}

	const char16 *GetStrW(int Id, const char16 *Default = NULL)
	{
		Field *f = GetField(Id);
		if (!f || f->Type != LStr)
			return Default;
		
		if (f->Size == 2)
			return f->s.w;
		else
			LAssert(!"Request for wrong string width");

		return Default;
	}

	bool SetBinary(int Id, void *Ptr, ssize_t Size)
	{
		Field *f = Alloc(Id, 8 + Size);
		if (!f)
			return false;
		
		f->Type = LBinary;
		f->Size = 1;
		f->s.Length = (uint32_t) Size;
		if (Ptr && Size > 0)
			memcpy(f->s.u, Ptr, Size);
		
		return true;
	}

	bool GetBinary(int Id, uint8_t *&Ptr, uint32_t &Size)
	{
		Field *f = GetField(Id);
		if (!f || f->Type != LBinary)
			return false;
		
		Ptr = (uint8_t*) f->s.u;
		Size = f->s.Length;

		#ifdef _DEBUG
		uint8_t *End = Ptr + Size;
		uint8_t *Flds = (uint8_t*)FieldMem.AddressOf();
		LAssert(Ptr >= Flds && End <= Flds + FieldMem.Length());
		#endif
		
		return true;
	}

	template<typename T>
	bool SetInt(int Id, T i)
	{
		Field *f = Alloc(Id, 4 + sizeof(i));
		if (!f)
			return false;

		f->Type = LInt;
		f->Size = sizeof(i);
		memcpy(f->bytes, &i, sizeof(i));
		return true;
	}

	template<typename T>
	bool SetFloat(int Id, T i)
	{
		Field *f = Alloc(Id, 4 + sizeof(i));
		if (!f)
			return false;

		f->Type = LFloat;
		f->Size = sizeof(i);
		memcpy(f->bytes, &i, sizeof(i));
		return true;
	}

	bool SetStr(int Id, const char *u)
	{
		if (!u)
			return false;

		size_t len = strlen(u) + 1;
		Field *f = Alloc(Id, 8 + len);
		if (!f)
			return false;

		f->Type = LStr;
		f->Size = 1;
		f->s.Length = (uint32_t) (len - 1);
		memcpy(f->s.u, u, len);
		return true;
	}

	bool SetStr(int Id, const char16 *w)
	{
		if (!w)
			return false;

		size_t len = Strlen(w) + 1;
		Field *f = Alloc(Id, 8 + len);
		if (!f)
			return false;

		f->Type = LStr;
		f->Size = sizeof(*w);
		f->s.Length = (uint32_t) (len - 1);
		memcpy(f->s.u, w, len * sizeof(*w));
		return true;
	}

	bool CanRead(void *ptr, size_t size)
	{
		if (!ptr || size < 8)
			return false;

		Field *f = (Field*)ptr;
		if (f->Type != LObject)
			return false;

		if (f->u32 > size)
			return false;

		return true;
	}

	bool Serialize(LStream *stream, bool write)
	{
		if ((ToStream = write))
		{
			uint32_t Size = 0;
			// for (ssize_t o = Fields.First(); o >= 0; o = Fields.Next())
			for (auto o : Fields)
			{
				Field *i = (Field*)FieldMem.AddressOf(o.value);
				Size += i->Sizeof();
			}

			Field f;
			f.Size = 0;
			f.Type = LObject;
			f.Id = GetObjectId();
			f.u32 = Size;
			
			if (stream->Write(&f, 8) != 8)
				return false;

			// for (ssize_t o = Fields.First(); o >= 0; o = Fields.Next())
			for (auto o : Fields)
			{
				Field *i = (Field*)FieldMem.AddressOf(o.value);
				int bytes = i->Sizeof();
				if (stream->Write(i, bytes) != bytes)
					return false;
			}
		}
		else
		{
			Empty();

			Field f;
			if (stream->Read(&f, 8) != 8)
				return false;

			if (f.Type != LObject)
				return false;
			ObjectId = f.Id;

			FieldMem.Length(f.u32);
			if (stream->Read(FieldMem.AddressOf(), f.u32) != f.u32)
				return false;

			// Parse all the fields into a hash table for O(1) access.
			char *s = FieldMem.AddressOf();
			char *e = s + FieldMem.Length();
			while (s < e)
			{
				Field *fld = (Field*)s;
				if (!fld->IsValid())
				{
					LAssert(!"Invalid field");
					return false;
				}

				Fields.Add(fld->Id, s - FieldMem.AddressOf(0));
				s += fld->Sizeof();
			}
		}

		return true;
	}

};


