/** @file bitstreamencoder.h
*	An encoder that writes data in a compact bitstream format
*	@author Chris Burns (cburns@ea.com)
*
*	UX
*	(c) 2012 Electronic Arts Inc.
*/

#ifndef EABASETYPESPLUS_BITSTREAMENCODER_H_
#define EABASETYPESPLUS_BITSTREAMENCODER_H_

#include "EABaseTypes/datatypes.h"
#include "EAAssert/EAAssert.h"
#include "EABaseTypesPlus/bitstream.h"
#include "EASTLICA/hash_map.h"
#include "EABaseTypes/string.h"

namespace EA
{
	namespace Types
	{
		// 3 bit code for type or end of stream
		static const unsigned char CODE_ARRAY  =	0;
		static const unsigned char CODE_OBJECT =	1;
		static const unsigned char CODE_BOOLEAN =	2;
		static const unsigned char CODE_FLOAT =		3;
		static const unsigned char CODE_INTEGER =	4;
		static const unsigned char CODE_STRING =	5;
		static const unsigned char CODE_NULL =		6;

		// 2 bit code for type of string encoding
		static const unsigned char STRING_ENCODING_INTERN =			0;
		static const unsigned char STRING_ENCODING_UTF8 =			1;
		static const unsigned char STRING_ENCODING_ALPHANUMERIC =	2;
		static const unsigned char STRING_ENCODING_SMAZ =			3;

		class BitOutputStream;

		class BitStreamEncoder
		{
		public:

			BitStreamEncoder(EA::Allocator::ICoreAllocator* allocator, BitOutputStream *out);

			// template interface
			void Reset();

			void BeginArray();
			void EndArray();

			void BeginArrayValue(size_t index);
			void EndArrayValue(size_t index) {}

			void BeginObject();
			void EndObject();

			void BeginObjectValue(const char* key, size_t length);
			void EndObjectValue(const char* key, size_t length) {}

			void Boolean(bool boolean);
			void Number(EA::BaseTypeFloat number);
			void String(const char* str, size_t length);
			void Function(EA::Types::FunctionIn function);
			void UserData(EA::Types::UserDataIn userData);
			void Null();

		private:
		
			void Integer(int integer);
			void StringData(const char* str, size_t length);
			void UTF8(const char* str, size_t length);
			bool IsAlphanumeric(const char* str, size_t length);
			void Alphanumeric(const char* str, size_t length);

			EA::Allocator::ICoreAllocator*	mAllocator;
			BitOutputStream*				mOut;
			int								mNextId;

			typedef EASTLICA::HashMap<EA::RawString, int> StringMap;
			StringMap						mInterned;
		};

		class BitInputStream;

		void DecodeUTF8(BitInputStream* in, EA::RawString& str);

		void DecodeAlphanumeric(BitInputStream* in, EA::RawString& str);

		void DecodeSMAZ(BitInputStream* in, EA::RawString& str);

		struct BitStreamDecoderParams
		{
			BitStreamDecoderParams(EA::Allocator::ICoreAllocator* allocator, BitInputStream* in):
				mAllocator(allocator),
				mIn(in) {}

			EA::Allocator::ICoreAllocator*	mAllocator;
			BitInputStream*					mIn;
		};

		template<typename Encoder>
		class BitStreamDecoder
		{
		public:

			// Decoder template interface
			static void Decode(void* e, void *p)
			{
				Encoder* encoder = static_cast<Encoder*>(e);
				BitStreamDecoderParams* params = static_cast<BitStreamDecoderParams*>(p);

				BitStreamDecoder<Encoder> decoder(params->mAllocator, params->mIn);
				decoder.Decode(encoder);
			}

		private:

			/*!
			*	BitStreamDecoder Constructor
			*	@param[in]	allocator	The allocator to use for temporary allocations
			*	@param[in]	in			The input stream to read from
			*/
			BitStreamDecoder(EA::Allocator::ICoreAllocator* allocator, BitInputStream* in):
				mAllocator(allocator),
				mIn(in),
				mInterned(allocator)
			{
			}

			void Decode(Encoder* encoder)
			{
				unsigned char code = mIn->Deserialize<unsigned char>(3);

				DecodeValue(code, encoder);
			}

			void DecodeArray(Encoder* encoder)
			{
				encoder->BeginArray();

				unsigned char next = mIn->Deserialize<unsigned char>(1);
				int index = 0;
				while(next != 0)
				{
					encoder->BeginArrayValue(index);

					Decode(encoder);

					encoder->EndArrayValue(index);

					next = mIn->Deserialize<unsigned char>(1);
					index++;
				}

				encoder->EndArray();
			}

			void DecodeObject(Encoder* encoder)
			{
				encoder->BeginObject();

				unsigned char next = mIn->Deserialize<unsigned char>(1);
				while(next != 0)
				{
					EA::RawString key(mAllocator);
					DecodeString(key);

					encoder->BeginObjectValue(key.c_str(), key.length());
					Decode(encoder);
					encoder->EndObjectValue(key.c_str(), key.length());

					next = mIn->Deserialize<unsigned char>(1);
				}

				encoder->EndObject();
			}

			void DecodeBoolean(Encoder* encoder)
			{
				unsigned char b = mIn->Deserialize<unsigned char>(1);
				encoder->Boolean(b != 0);
			}

			int DecodeInteger()
			{
				unsigned char type = mIn->Deserialize<unsigned char>(2);

				switch(type)
				{
					case 0:
						return mIn->Deserialize<int>(4);
					case 1:
						return mIn->Deserialize<int>(8);
					case 2:
						return mIn->Deserialize<int>(16);
					case 3:
						return mIn->Deserialize<int>();
				}

				EA_FAIL();
				return 0;
			}

			EA::BaseTypeFloat DecodeFloat()
			{
				return mIn->Deserialize<EA::BaseTypeFloat>();
			}

			void DecodeString(Encoder* encoder)
			{
				EA::RawString str(mAllocator);
				DecodeString(str);
				encoder->String(str.c_str(), str.length());
			}

			void DecodeString(EA::RawString& str)
			{
				unsigned char encoding = mIn->Deserialize<unsigned char>(2);

				switch(encoding)
				{
					case STRING_ENCODING_INTERN:
						{
							str = mInterned[DecodeInteger()];
						}
						return;
					case STRING_ENCODING_UTF8:
						DecodeUTF8(mIn, str);
						break;
					case STRING_ENCODING_ALPHANUMERIC:
						DecodeAlphanumeric(mIn, str);
						break;
					case STRING_ENCODING_SMAZ:
						DecodeSMAZ(mIn, str);
						break;
				}

				mInterned.push_back(str);
			}

			void DecodeNull(Encoder* encoder)
			{
				encoder->Null();
			}

			void DecodeValue(unsigned char code, Encoder* encoder)
			{
				switch(code)
				{
					case CODE_ARRAY:
						DecodeArray(encoder);
						break;
					case CODE_OBJECT:
						DecodeObject(encoder);
						break;
					case CODE_BOOLEAN:
						DecodeBoolean(encoder);
						break;
					case CODE_FLOAT:
						encoder->Number(DecodeFloat());
						break;
					case CODE_STRING:
						DecodeString(encoder);
						break;
					case CODE_NULL:
						DecodeNull(encoder);
						break;
					case CODE_INTEGER:
						encoder->Number(DecodeInteger());
						break;
					default:
						EA_FAIL_FORMATTED(("unrecognized bit code %u\n", code));
						break;
				}
			}

			EA::Allocator::ICoreAllocator*		mAllocator;
			BitInputStream*						mIn;

			EASTLICA::Vector<EA::RawString>		mInterned;
		};
	}
}

#endif /* EABASETYPESPLUS_BITSTREAMENCODER_H_ */
