//------------------------------------------------------------------------------
// <copyright file="RawSerializationBuffer.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// </summary>
//
// <owner></owner>
//------------------------------------------------------------------------------


#ifndef RAWSERIALIZATIONBUFFER_H_
#define RAWSERIALIZATIONBUFFER_H_

#include <iostream>
#include <stdexcept>
#include <vector>
#include <boost/utility.hpp>

namespace LiquidRobotics {
namespace PayloadInterface {

class RawSerializationBuffer : boost::noncopyable
{
public:

	typedef unsigned char byte;
	typedef std::vector<byte> ByteArray;
	typedef std::string string;

	class EndOfReadStream : public std::runtime_error
	{
	public:
		EndOfReadStream(const std::string& whatArg) : runtime_error(whatArg) { }
	};

	class UnusedDataInReadStream : public std::runtime_error
	{
	public:
		UnusedDataInReadStream(const std::string& whatArg) : runtime_error(whatArg) { }
	};

	class  WriteStreamOverflow : public std::runtime_error
	{
	public:
		WriteStreamOverflow(const std::string& whatArg) : runtime_error(whatArg) { }
	};

	class Endianness
	{
	public:

		enum EEndian
		{
			BIG,			// numeric values are arranged with most significant bytes first
			LITTLE			// numeric values are arranged with least significant bytes first
		};

		inline Endianness()
		: desired(DetectArchitecturalEndianness())
		, detected(DetectArchitecturalEndianness())
		{
		}

		inline Endianness(EEndian _desired)
		: desired(_isLittle)
		, detected(DetectArchitecturalEndianness())
		{
		}

		inline EEndian GetDesired() const			{ return desired; }
		inline EEndian GetDetected() const			{ return detected; }
		inline EEndian NumbersReversed() const		{ return desired != detected; }

		inline static EEndian DetectArchitecturalEndianness()
		{
			const long i = 1;
			return *((const byte*) &i) ? LITTLE : BIG;
		}

		template<typename T>
		void AutoReverseNumeric(T& num)
		{
			if (NumbersReversed())
				ReverseArray((byte*) &num, sizeof(num));
		}

		string ToString() const;
		static string EEndian_ToString(EEndian eEndian);

	private:

		static void ReverseByteArray(byte* pData, int dataLength);

		EEndian desired;					// desired endianness
		EEndian detected;					// endianness of this architecture
	};

	class LittleEndian : public Endianness
	{
	public:
		LittleEndian() : Endianness(LITTLE) { }
	};

	class BigEndian : public Endianness
	{
	public:
		BigEndian() : Endianness(BIG) { }
	};

	// constructor for writing an arbitrarily long sequence
	RawSerializationBuffer(Endianness::EEndian eEndian);
	// constructor for writing up a certain length of stream
	RawSerializationBuffer(Endianness::EEndian eEndian, int _count);
	// constructor for reading from a predefined buffer
	RawSerializationBuffer(Endianness::EEndian eEndian, const byte* _pData, int _count, int _offset = 0);
	// constructor for reading from a predefined buffer
	RawSerializationBuffer(Endianness::EEndian eEndian, const ByteArray& _data, int _count = -1, int _offset = 0);

	virtual ~RawSerializationBuffer();

	const Endianness& Endian() const;
	int Index() const;
	bool IsReadStream() const;
	bool IsWriteStream() const;
	int Remaining() const;
	void Reserve(int reserveCount);
	void ThrowIfDataRemaining() const;

	template<typename T> T GetNumeric()
	{
		T val;

		Get(&val, sizeof(val));
		endianness.AutoReverseNumeric(val);
		return val;
	}

	template<typename T> void PutNumeric(const T& num)
	{
		if (endianness.NumbersReversed())
		{
			T val = num;

			endianness.AutoReverseNumeric(val);
			Put(&val, sizeof(val));
		}
		else
			Put(&num, sizeof(num));

	}

	void Get(void* pData, int dataLength);
	void Put(const void* pData, int dataLength);

	RawSerializationBuffer& operator<<(const char& data);
	RawSerializationBuffer& operator<<(const unsigned char& data);
	RawSerializationBuffer& operator<<(const short& data);
	RawSerializationBuffer& operator<<(const unsigned short& data);
	RawSerializationBuffer& operator<<(const long& data);
	RawSerializationBuffer& operator<<(const unsigned long& data);
	RawSerializationBuffer& operator<<(const float& data);
	RawSerializationBuffer& operator<<(const double& data);
	RawSerializationBuffer& operator<<(const long double& data);
	RawSerializationBuffer& operator<<(const char* data);
	RawSerializationBuffer& operator<<(const std::string& data);

	RawSerializationBuffer& operator>>(char& data);
	RawSerializationBuffer& operator>>(unsigned char& data);
	RawSerializationBuffer& operator>>(short& data);
	RawSerializationBuffer& operator>>(unsigned short& data);
	RawSerializationBuffer& operator>>(long& data);
	RawSerializationBuffer& operator>>(unsigned long& data);
	RawSerializationBuffer& operator>>(float& data);
	RawSerializationBuffer& operator>>(double& data);
	RawSerializationBuffer& operator>>(long double& data);

	void Get(string& result, int stringLen);

private:

	const ByteArray* pArrayRead;				// points to the array to read from
	ByteArray arrayWrite;						// array to write to
	const byte* pData;							// points to the sequence to read from
	int count;									// specified count to read/write or -1 for not specified
	int offset;									// specified offset to read from
	bool reading;								// true if reading, false if writing
	Endianness endianness;						// byte swapping flags
};

std::ostream& operator<<(std::ostream& out, const RawSerializationBuffer::Endianness& endianness);
std::ostream& operator<<(std::ostream& out, const RawSerializationBuffer& buffer);

}
}

#endif /* RAWSERIALIZATIONBUFFER_H_ */
