/** @file bitstream.cpp
 *  Basic bitstream class
 *	@headerfile bitstream.h "EABaseTypesPlus/bitstream.h"
 *	@author Chris Burns (cburns@ea.com)
 *
 *	UX
 *	(c) 2012 Electronic Arts Inc. 
 */

#include "EABaseTypesPlus/bitstream.h"

#include "EABaseTypes/string.h"

namespace EA
{
	namespace Types
	{
		//lint -efunc(702, EA::Types::OutputBits)
		//lint -efunc(920, EA::Types::OutputBits)
		void OutputBits(EA::RawString& output, const char *data, int bitOffset, int sizeBits)
		{
			while(sizeBits--)
			{
				// bit offset 0 corresponds to the least significant bit!
				if((*data >> bitOffset) & 0x1)
				{
					output.append("1");
				}
				else
				{
					output.append("0");
				}

				bitOffset++;

				if(bitOffset >= 8)
				{
					bitOffset %= 8;
					data++;
				}
			}
		}

		//lint -efunc(616, EA::Types::FromBytes)
		//lint -efunc(701, EA::Types::FromBytes)
		//lint -efunc(825, EA::Types::FromBytes)
		unsigned int FromBytes(const char *data, int size)
		{
			// the number is stored with the least significant bytes
			// first
			unsigned int result = 0;

			switch(size)
			{
			case 4:
				result |= (data[3] & 0xFF) << 24;
			case 3:
				result |= (data[2] & 0xFF) << 16;
			case 2:
				result |= (data[1] & 0xFF) << 8;
			case 1:
				result |= data[0] & 0xFF;
				break;
			default:
				EA_FAIL_MESSAGE("Invalid size!!");
			}

			return result;
		}

		//lint -efunc(616, EA::Types::ToBytes)
		//lint -efunc(825, EA::Types::ToBytes)
		void ToBytes(unsigned int value, char *data, int size)
		{
			// pack the least significant bytes first so that we don't have
			// empty space at the beginning of our array
			switch(size)
			{
			case 4:
				data[3] = static_cast<char>((value >> 24) & 0xFF);
			case 3:
				data[2] = static_cast<char>((value >> 16) & 0xFF);
			case 2:
				data[1] = static_cast<char>((value >> 8) & 0xFF);
			case 1:
				data[0] = static_cast<char>(value & 0xFF);
				break;
			default:
				EA_FAIL_MESSAGE("Invalid size!!");
				break;
			}
		}

		//lint -efunc(58, EA::Types::StreamBits)
		//lint -efunc(413, EA::Types::StreamBits)
		//lint -efunc(502, EA::Types::StreamBits)
		//lint -efunc(701, EA::Types::StreamBits)
		//lint -efunc(734, EA::Types::StreamBits)
		//lint -efunc(831, EA::Types::StreamBits)
		static void StreamBits(char **dstData, 
							   int *dstBitOffset, 
							   int *dstSizeBits, 
							   const char **srcData, 
							   int *srcBitOffset, 
							   int *srcSizeBits)
		{
			EA_ASSERT_FORMATTED(*dstBitOffset >= 0 && *dstBitOffset < 8, ("Invalid dstBitOffset!"));
			EA_ASSERT_FORMATTED(dstSizeBits  != 0, ("Invalid dstSizeBits!"));
		
			EA_ASSERT_FORMATTED(*srcBitOffset >= 0 && *srcBitOffset < 8, ("Invalid srcBitOffset!"));
			EA_ASSERT_FORMATTED(srcSizeBits != 0, ("Invalid srcSizeBits!"));

			// calculate the total number of bits we can copy
			int sizeBits = *dstSizeBits > *srcSizeBits ? *srcSizeBits : *dstSizeBits;

			// decrement the source and destination size bits fields
			*dstSizeBits -= sizeBits;
			*srcSizeBits -= sizeBits;

			while(sizeBits > 0)
			{
				int dstBitsAvailable = (8 - *dstBitOffset);
				int srcBitsAvailable = (8 - *srcBitOffset);
				// total bits we can write in this iteration is the minimum of
				// the source bits available and the destination bits available
				// this is because we need to re-align the two bytes to have the
				// same offset
				int bitsAvailable = dstBitsAvailable > srcBitsAvailable ? srcBitsAvailable : dstBitsAvailable;

				// if we're copying less than 8 bits, we want to preserve
				// those bits we're not going to overwrite
				// 76543210
				// 11111111 - 8 or more bits remaining
				// 01111111 - 7 bits remaining
				// 00111111 - 6 bits remaining
				// 00000001 - 1 bit remaining
				unsigned char dstMask = 0xFF;
				if(sizeBits < 8)
				{
					dstMask = static_cast<unsigned char>(~(dstMask << sizeBits));
				}

				dstMask = static_cast<unsigned char>(~(dstMask << *dstBitOffset));
				// 76543210
				// 11110000 - bit offset 0, 4 bits remaining
				// 11100001 - bit offset 1, 4 bits remaining
				// 11000011 - bit offset 2, 4 bits remaining
				// ...
				// 01111111 - bit offset 7, 4 bits remaining

				// mask off all the destination data we're overwriting
				unsigned char dstBits = static_cast<unsigned char>(**dstData & dstMask);

				// create the mask for the bits we want to read from in the source byte

				// if we're not reading the entire source byte, we need to mask off those
				// bits we're not reading
				// 76543210
				// 11111111 - 8 or more bits remaining
				// 01111111 - 7 bits remaining
				// ...
				// 00000001 - 1 bit remaining
				unsigned char srcMask = 0xFF; // won't let me use 0xFF?
				if(sizeBits < 8)
				{
					srcMask = static_cast<unsigned char>(~(srcMask << sizeBits));
				}

				srcMask <<= *srcBitOffset;
				// 76543210
				// 00001111 - bit offset 0, 4 bits remaining
				// 00011110 - bit offset 1, 4 bits remaining
				// 00111100 - bit offset 2, 4 bits remaining
				// 01111000 - bit offset 3, 4 bits remaining
				// 11110000 - bit offset 4, 4 bits remaining
				// 11100000 - bit offset 5, 4 bits remaining
				// 11000000 - bit offset 5, 4 bits remaining
				// 10000000 - bit offset 7

				// mask off all the source data we're not reading
				unsigned char srcBits = static_cast<unsigned char>(**srcData & srcMask);

				// align the source bits with the destination bits
				// we need to move the source bits by the difference
				// in the bit offsets
			
				// ex
				//      76543210
				// src: 11111000 - bit offset 3 (5 bits available)
				// dst: 00111111 - bit offset 6 (2 bits available)
				// destination bit offset is greater, so source bit
				// is increased by 6 - 3 = 3
				// src: 11000000 - bit offset 3 + 3 = 6
 				// dst: 00111111 - bit offset 6
				if(*srcBitOffset > *dstBitOffset)
				{
					// if the source bit offset is greater than the destination
					// bit offset, then we need to decrease the source's
					// offset
					int offset = *srcBitOffset - *dstBitOffset;

					srcBits >>= offset;
				}
				else
				{
					// if the source bit offset is less than the destination
					// bit offset, then we need to increase the source's
					// offset
					int offset = *dstBitOffset - *srcBitOffset;

					srcBits <<= offset;
				}

				// now the source and destination data should be aligned
			
				// or the source and destination bits together and write them back
				**dstData = dstBits | srcBits;

				// skip ahead by the number of bits written
				// adjust pointers and offsets as needed
				int bitsCopied = sizeBits > bitsAvailable ? bitsAvailable : sizeBits;

				*dstBitOffset += bitsCopied;
				if(*dstBitOffset >= 8)
				{
					*dstBitOffset %= 8;
					(*dstData)++;
				}

				*srcBitOffset += bitsCopied;
				if(*srcBitOffset >= 8)
				{
					*srcBitOffset %= 8;
					(*srcData)++;
				}

				sizeBits -= bitsCopied;
			}
		}

		//lint -efunc(1927, EA::Types::BitStream::BitStream)
		/* protected */ BitStream::BitStream()
		{
			Reset(0, 0);
		}

		//lint -efunc(1927, EA::Types::BitStream::BitStream)
		/* protected */ BitStream::BitStream(int bitOffset, int numBits)
		{
			Reset(bitOffset, numBits);
		}

		/* protected */ void BitStream::Reset(int bitOffset, int numBits)
		{
			mBitOffset = bitOffset;
			mNumBitsAvailable = numBits;
			mNumBits = numBits;
		}

		/* protected */ int BitStream::Seek(int numBits)
		{
			int currentBit = mNumBits - mNumBitsAvailable;

			int newBit = currentBit + numBits;

			if(newBit < 0)
			{
				newBit = 0;
			}

			if(newBit > mNumBits)
			{
				newBit = mNumBits;
			}

			int seekBits = newBit - currentBit;

			int seekBytes = seekBits / 8;
			mBitOffset += seekBits % 8;

			if(mBitOffset < 0)
			{
				seekBytes -= 1;
				mBitOffset += 8;
			}
			else if(mBitOffset > 7)
			{
				seekBytes += 1;
				mBitOffset -= 8;
			}

			mNumBitsAvailable = mNumBits - newBit;

			return seekBytes;
		}

		int BitStream::GetNumBitsAvailable() const
		{
			return mNumBitsAvailable;
		}

		int BitStream::GetNumBitsStreamed() const
		{
			return mNumBits - mNumBitsAvailable;
		}

		int BitStream::GetNumBytesStreamed() const
		{
			int bitsStreamed = GetNumBitsStreamed();

			return (bitsStreamed / 8) + ((bitsStreamed % 8 > 0) ? 1 : 0);
		}

		//lint -efunc(1927, EA::Types::BitInputStream::BitInputStream)
		BitInputStream::BitInputStream():BitStream()
		{
			Reset(NULL, 0, 0);
		}

		//lint -efunc(1927, EA::Types::BitInputStream::BitInputStream)
		BitInputStream::BitInputStream(const char *bytes, int bitOffset, int numBits):BitStream()
		{
			Reset(bytes, bitOffset, numBits);
		}

		void BitInputStream::Reset(const char *bytes, int bitOffset, int numBits)
		{
			BitStream::Reset(bitOffset, numBits);

			mBytes = bytes;
		}

		void BitInputStream::Reset()
		{
			Seek(mNumBitsAvailable - mNumBits);
		}

		void BitInputStream::Seek(int numBits)
		{
			mBytes += BitStream::Seek(numBits);
		}

		void BitInputStream::Read(BitOutputStream &out)
		{
			out.Write(*this);
		}

		//lint -efunc(1927, EA::Types::BitOutputStream::BitOutputStream)
		BitOutputStream::BitOutputStream():BitStream()
		{
			Reset(NULL, 0, 0);
		}

		//lint -efunc(1927, EA::Types::BitOutputStream::BitOutputStream)
		BitOutputStream::BitOutputStream(char *bytes, int bitOffset, int numBits):BitStream()
		{
			Reset(bytes, bitOffset, numBits);
		}

		void BitOutputStream::Reset(char *bytes, int bitOffset, int numBits)
		{
			BitStream::Reset(bitOffset, numBits);

			mBytes = bytes;
		}

		void BitOutputStream::Reset()
		{
			Seek(mNumBitsAvailable - mNumBits);
		}

		void BitOutputStream::Seek(int numBits)
		{
			mBytes += BitStream::Seek(numBits);
		}

		void BitOutputStream::Write(BitInputStream &in)
		{
			StreamBits(&mBytes, 
					   &mBitOffset, 
					   &mNumBitsAvailable, 
					   &(in.mBytes),
					   &(in.mBitOffset),
					   &(in.mNumBitsAvailable));
		}
	}
}
