//------------------------------------------------------------------------------
// <copyright file="LqrSMACByteArray.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>
//   Declarations for the ByteArray class, which implements methods like
//   vector<byte>, but which has been extended to support compact serialization.
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef LQRSMACBYTEARRAY_H_
#define LQRSMACBYTEARRAY_H_

#include "LqrSMACSharedLib.h"
#include <iostream>
#include <string>
#include <vector>
#include <boost/serialization/access.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/shared_ptr.hpp>

namespace LiquidRobotics {

//! Classes deployed in the SMACFramework shared library (general utilities)
namespace SMACFramework {

class SHARED_LIB_PUBLIC ByteArray
{
public:

	typedef unsigned char byte;
	typedef std::string string;
	typedef std::vector<byte> atype;
	typedef atype::reference reference;
	typedef atype::const_reference const_reference;
	typedef atype::iterator iterator;
	typedef atype::const_iterator const_iterator;
	typedef atype::size_type size_type;
	typedef atype::difference_type difference_type;
	typedef atype::value_type value_type;
	typedef atype::allocator_type allocator_type;
	typedef atype::pointer pointer;
	typedef atype::const_pointer const_pointer;
	typedef atype::reverse_iterator reverse_iterator;
	typedef atype::const_reverse_iterator const_reverse_iterator;
	typedef boost::shared_ptr<ByteArray> Ptr;

	ByteArray() { }
	explicit ByteArray(size_type n, const byte value = 0) : bytes(n,value) 	{ }

	explicit ByteArray(const byte* pStart, size_type length)					{ assign(pStart,length); }
	ByteArray(const char* pSerial)											{ FromSerialization(pSerial); }
	ByteArray(const string& serial)											{ FromSerialization(serial); }

	template<class InputIterator>
	ByteArray(InputIterator first, InputIterator last)
	: bytes(first, last)
	{
	}

	//explicit ByteArray(ByteArray& right) : bytes(right.bytes)				{ }
	ByteArray(const ByteArray& right) : bytes(right.bytes) 		{ }

	virtual ~ByteArray() 													{ }

	ByteArray& operator=(const ByteArray& right) 							{ bytes = right.bytes; return *this; }

	int operator==(const ByteArray& right) const							{ return bytes == right.bytes; }
	int operator!=(const ByteArray& right) const							{ return bytes != right.bytes; }

	template<typename T>
	T& as(size_type offset = 0)
	{
		if ((offset+sizeof(T)) > size())
			ThrowAsParametersOutOfRange(offset, typeid(T).name());

		return (T&) at(offset);
	}

	template<typename T>
	T& as(size_type offset = 0) const
	{
		if ((offset+sizeof(T)) > size())
			ThrowAsParametersOutOfRange(offset, typeid(T).name());

		return (T&) at(offset);
	}

	void assign(const byte* pStart, size_type length);

	template<class InputIterator>
	void assign(InputIterator first, InputIterator last)
	{
		bytes.assign(first, last);
	}

	void assign(size_type n, byte u)										{ bytes.assign(n, u); }

	reference at(size_type n)												{ return bytes.at(n); }
	const_reference at(size_type n) const									{ return bytes.at(n); }

	reference back()														{ return bytes.back(); }
	const_reference back() const											{ return bytes.back(); }

	iterator begin()														{ return bytes.begin(); }
	const_iterator begin() const											{ return bytes.begin(); }

	size_type capacity() const												{ return bytes.capacity(); }

	void clear()															{ bytes.clear(); }

	bool empty() const														{ return bytes.empty(); }

	iterator end()															{ return bytes.end(); }
	const_iterator end() const												{ return bytes.end(); }

	iterator erase(iterator position)										{ return bytes.erase(position); }
	iterator erase(iterator first, iterator last)							{ return bytes.erase(first, last); }

	reference front()														{ return bytes.front(); }
	const_reference front() const											{ return bytes.front(); }

	allocator_type get_allocator() const									{ return bytes.get_allocator(); }

	iterator insert(iterator position, byte x)								{ return bytes.insert(position, x); }
	void insert(iterator position, size_type n, byte x)						{ bytes.insert(position, n, x); }
	template<class InputIterator>
	void insert(iterator position, InputIterator first, InputIterator last)	{ bytes.insert<InputIterator>(position, first, last); }
	void insert(iterator position, const byte* pStart, size_type length);

	template<typename T>
	void insert_type(iterator position, const T& item, size_type numBytes)
	{
		if (numBytes > sizeof(item))
			InsertTypeTooManyBytes(numBytes, typeid(item).name(), sizeof(T));

		const byte* pStart = (const byte*) &item;
		const byte* pEnd = pStart+numBytes;
		insert(position, pStart, pEnd);
	}

	template<typename T>
	void insert_type(iterator position, const T& item)
	{
		insert_type(position, item, sizeof(T));
	}

	size_type max_size() const												{ return bytes.max_size(); }

	reference operator[](size_type n)										{ return bytes[n]; }
	const_reference operator[](size_type n) const							{ return bytes[n]; }

	void pop_back()															{ bytes.pop_back(); }

	void push_back(byte x)													{ bytes.push_back(x); }

	reverse_iterator rbegin()												{ return bytes.rbegin(); }
	const_reverse_iterator rbegin() const									{ return bytes.rbegin(); }

	reverse_iterator rend()													{ return bytes.rend(); }
	const_reverse_iterator rend() const										{ return bytes.rend(); }

	void reserve(size_type n)												{ bytes.reserve(n); }

	void resize(size_type n, byte c = 0)									{ bytes.resize(n, c); }

	size_type size() const													{ return bytes.size(); }

	void swap(ByteArray& other)												{ bytes.swap(other.bytes); }

	void FromSerialization(const char* pSerial);
	void FromSerialization(const string& serial);

    void FromHexString(const char* pHexStr);
    void FromHexString(const string& hexStr);
    string ToHexString() const;					// displays the byte array as a hex string

    void InitFromLiteralString(const string& str); // initializes the byte array from the literal content of a string

	string ToSerialization() const;				// converts the byte array into a string that can be deserialized by FromSerialization

	string ToString() const;						// displays diagnostic output about the array
	string ToStringSplitView() const;			// displays the byte array as a hex/ascii dump
	string ToASCIIString() const;					// represents the byte array literally as a string

private:

	friend class boost::serialization::access;

	template<class Archive>
	void save(Archive& ar, const unsigned int version) const
	{
		std::string bytesString = ToSerialization();
		ar & BOOST_SERIALIZATION_NVP(bytesString);
	}

	template<class Archive>
	void load(Archive& ar, const int version)
	{
		std::string bytesString;
		ar & BOOST_SERIALIZATION_NVP(bytesString);
		FromSerialization(bytesString);
	}

	void InsertTypeTooManyBytes(size_type numBytes, const char* typeName, size_type typeSize) const;
	void ThrowAsParametersOutOfRange(size_type offset, const char* typeName) const;

	BOOST_SERIALIZATION_SPLIT_MEMBER()

	atype bytes;
};

typedef ByteArray::Ptr ByteArrayPtr;

std::ostream& operator<<(std::ostream& stm, const ByteArray& ba);

}
}

#endif /* BYTEARRAY_H_ */
