/** @file bytestreamencoder.h
*	An encoder that writes data in a fast bytestream format
*	@author Chris Burns (cburns@ea.com)
*
*	CTUI
*	(c) 2013 Electronic Arts Inc.
*/

#ifndef EABASETYPESPLUS_BYTESTREAMENCODER_H_
#define EABASETYPESPLUS_BYTESTREAMENCODER_H_

#include "EABaseTypes/datatypes.h"
#include "EABaseTypesPlus/bytestream.h"

#include "EAAssert/eaassert.h"

namespace EA
{
	namespace Types
	{
		// codes for types
		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_NUMBER  =	3;
		static const unsigned char CODE_STRING =	4;
		static const unsigned char CODE_FUNCTION =	5;
		static const unsigned char CODE_USERDATA =	6;
		static const unsigned char CODE_NULL =		7;
		
		// codes for control
		static const unsigned char CODE_HASNEXT =	8;
		static const unsigned char CODE_END =		9;
		static const unsigned char CODE_RESET =		10;
		
		class ByteStreamEncoder
		{
		public:

			ByteStreamEncoder(ByteStream* 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(BaseTypeFloat number);
			void String(const char* str, size_t length);
			void Function(FunctionIn function);
			void UserData(UserDataIn userData);
			void Null();

		private:

			ByteStream* mOut;
		};

		template<typename Encoder>
		class ByteStreamDecoder
		{
		public:

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

				ByteInputStream stream(in);

				while(!stream.End())
				{
					Decode(encoder, stream);
				}
			}
			
		private:

			class ByteInputStream
			{
			public:
				ByteInputStream(const ByteStream* in):
					mIn(in),
					mIndex(0) {}
					
				unsigned char Read() { return mIn->mStream[static_cast<EASTLICA::Vector<unsigned char>::size_type>(mIndex++)]; }
				bool End() { return mIndex >= mIn->mStream.size(); }
				const unsigned char* Raw() { return &mIn->mStream[static_cast<EASTLICA::Vector<unsigned char>::size_type>(mIndex)]; }
				void Seek(size_t i) { mIndex += i; }
					
			private:
				
				const ByteStream* mIn;
				size_t			  mIndex;
			};

			static void Decode(Encoder* encoder, ByteInputStream& in)
			{
				unsigned char code = in.Read();
					
				switch(code)
				{
					case CODE_ARRAY:
						DecodeArray(encoder, in);
						break;
					case CODE_OBJECT:
						DecodeObject(encoder, in);
						break;
					case CODE_BOOLEAN:
						encoder->Boolean(DecodeBytes<bool>(in));
						break;
					case CODE_NUMBER:
						encoder->Number(DecodeBytes<BaseTypeFloat>(in));
						break;
					case CODE_STRING:
						{
							const char* str = reinterpret_cast<const char*>(in.Raw());
							size_t length = strlen(str);
							encoder->String(str, length);
							in.Seek(length + 1);
						}
						break;
					case CODE_FUNCTION:
						encoder->Function(FunctionIn(DecodeBytes<Function*>(in)));
						break;
					case CODE_USERDATA:
						encoder->UserData(UserDataIn(DecodeBytes<UserData*>(in)));
						break;
					case CODE_NULL:
						encoder->Null();
						break;
					case CODE_RESET:
						break;
					default:
						EA_FAIL();
				}
			}
			
			static void DecodeArray(Encoder* encoder, ByteInputStream& in)
			{
				encoder->BeginArray();
				
				unsigned char code = in.Read();
				
				size_t index = 0;
				while(code == CODE_HASNEXT)
				{
					encoder->BeginArrayValue(index);
					Decode(encoder, in);
					encoder->EndArrayValue(index);
					++index;
					
					code = in.Read();
				}
				
				if(code == CODE_END)
				{
					encoder->EndArray();
				}
			}
			
			static void DecodeObject(Encoder* encoder, ByteInputStream& in)
			{
				encoder->BeginObject();

				unsigned char code = in.Read();
				
				while(code == CODE_HASNEXT)
				{
					const char* str = reinterpret_cast<const char*>(in.Raw());
					size_t length = strlen(str);
					in.Seek(length + 1);
					
					encoder->BeginObjectValue(str, length);
					Decode(encoder, in);
					encoder->EndObjectValue(str, length);
					
					code = in.Read();
				}
				
				if(code == CODE_END)
				{
					encoder->EndObject();
				}
			}
		
			template<typename T>
			static T DecodeBytes(ByteInputStream& in)
			{
				size_t size = sizeof(T);

				T value;
				
				unsigned char* bytes = reinterpret_cast<unsigned char*>(&value);
				
				for(size_t i = 0; i < size; ++i)
				{
					bytes[i] = in.Read();
				}
				
				return value;
			}
		};
	}
}

#endif /* EABASETYPESPLUS_BYTESTREAMENCODER_H_ */
