/** @file bitstream.h
 *  Basic bitstream class
 *	@author Chris Burns (cburns@ea.com)
 *
 *	UX
 *	(c) 2011 Electronic Arts Inc. 
 */

#ifndef EABASETYPESPLUS_BITSTREAM_H_
#define EABASETYPESPLUS_BITSTREAM_H_

#include "eaassert/eaassert.h"

namespace EA
{
	namespace Types
	{
		void OutputBits(const char *data, int bitOffset, int sizeBits);

		unsigned int FromBytes(const char *bytes, int size);

		void ToBytes(unsigned int value, char *bytes, int size);

		class BitStream
		{
		public:

			int GetNumBitsAvailable() const;

			int GetNumBitsStreamed() const;

			int GetNumBytesStreamed() const;

		protected:

			BitStream();

			BitStream(int bitOffset, int numBits);

			void Reset(int bitOffset, int numBits);

			int Seek(int numBits);

			int mBitOffset;
			int mNumBitsAvailable;
			int mNumBits;
		};

		class BitOutputStream;

		class BitInputStream : public BitStream 
		{
		public:

			BitInputStream();

			BitInputStream(const char *bytes, int bitOffset, int numBits);

			void Reset(const char *bytes, int bitOffset, int numBits);

			void Reset();

			void Seek(int numBits);

			void Read(BitOutputStream &out);

			template<typename number_type>
			inline number_type Deserialize()
			{
				return Deserialize<number_type>(sizeof(number_type) * 8);
			}

			template<typename number_type>
			number_type Deserialize(int numBits);

		private:

			friend class BitOutputStream;

			const char *mBytes;
		};

		class BitOutputStream : public BitStream
		{
		public:

			BitOutputStream();

			BitOutputStream(char *bytes, int bitOffset, int numBits);

			void Reset(char *bytes, int bitOffset, int numBits);

			void Reset();

			void Seek(int numBits);

			void Write(BitInputStream &in);

			template<typename number_type>
			inline void Serialize(number_type value)
			{
				Serialize(value, sizeof(number_type) * 8);
			}

			template<typename number_type>
			void Serialize(number_type value, int numBits)
			{
				char bytes[sizeof(number_type)];

				unsigned int unsignedIntValue = static_cast<unsigned int>(value);
			
				EA_ASSERT_FORMATTED(unsignedIntValue <= ~(0xFFFFFFFF << numBits), ("Can't serialize %u into %u bits!", unsignedIntValue, numBits));

				ToBytes(unsignedIntValue, bytes, sizeof(number_type));

				BitInputStream in(bytes, 0, numBits);

				Write(in);

				EA_ASSERT_FORMATTED(in.GetNumBitsStreamed() == numBits, ("Failed to serialize %d bits\n", numBits));
			}

		private:

			char	*mBytes;
		};

		template<typename number_type>
		number_type BitInputStream::Deserialize(int numBits)
		{
			char bytes[sizeof(number_type)];
			memset(bytes, 0, sizeof(number_type));

			BitOutputStream out(bytes, 0, numBits);

			Read(out);

			EA_ASSERT_FORMATTED(out.GetNumBitsStreamed() == numBits, ("Failed to deserialize %d bits\n", numBits));

			unsigned int unsignedIntValue = FromBytes(bytes, sizeof(number_type));

			return static_cast<number_type>(unsignedIntValue);
		}
	}
}

#endif /* EABASETYPESPLUS_BITSTREAM_H_ */
