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

#include "LqrSMACByteArray.h"
#include "LqrSMACPrintfBuffer.h"
#include "nsextend.h"
#include <string.h>
#include <stdexcept>
#include <sstream>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>

using namespace std;
using namespace boost;

namespace LiquidRobotics {
namespace SMACFramework {

void ByteArray::assign(const byte* pStart, size_type length)
{
	if (!pStart && length)
	{
		stringstream stm;
		stm << __FILE__ << "(" << __LINE__ << ") pStart is NULL";

		throw invalid_argument(stm.str());
	}

	clear();

	if (length)
	{
		reserve(length);
		while(length--) push_back(*pStart++);
	}
}

void ByteArray::insert(iterator position, const byte* pStart, size_type length)
{
	insert(position, pStart, pStart+length);
}


class ByteArrayFromSerialization
{
public:

	ByteArrayFromSerialization(const string& _str)
	: ptr(NULL)
	, pstr(&_str)
	, length(_str.length())
	{
	}

	ByteArrayFromSerialization(const char* _pstr)
	: ptr(_pstr)
	, pstr(NULL)
	, length(strlen(_pstr))
	{
	}

	inline int IndexOf(char c)
	{
		if (ptr)
		{
			const char* pFound = strchr(ptr, c);

			return pFound ? (pFound-ptr) : -1;
		}
		else
		{
			string::size_type idx = pstr->find_first_of(c);

			return (idx == string::npos) ? -1 : (int) idx;
		}
	}

	inline string Substr(int start, int length)
	{
		if (ptr)
			return string(&ptr[start], length);
		else
			return pstr->substr(start, length);
	}

	void GetData(ByteArray::atype& data)
	{
		const int coloIdx = IndexOf(':');

		if (coloIdx == -1)
		{
			stringstream stm;
			stm << __FILE__ << "(" << __LINE__ << ") colon not found in string";
			throw invalid_argument(stm.str());
		}

		const int numBytes = lexical_cast<int>(Substr(0, coloIdx));
		const int tailLen = length-coloIdx-1;

		if (tailLen != 2*numBytes)
		{
			stringstream stm;
			stm << __FILE__ << "(" << __LINE__ << ") "
			    << "number of bytes (" << numBytes << ") does not agree with the string tail length (" << tailLen << ")";
			throw invalid_argument(stm.str());
		}

		data.clear();
		data.reserve(numBytes);

		for (int idx = 0; idx < tailLen; idx += 2)
		{
			string sub = Substr(coloIdx+1+idx, 2);
			char* endptr = NULL;
			long num = strtol(sub.c_str(), &endptr, 16);

			if (!endptr || *endptr)
			{
				stringstream stm;
				stm << __FILE__ << "(" << __LINE__ << ") "
				    << "substring at offset " << (coloIdx+1+idx) << " (" << sub.c_str() << " could not be converted";
				throw invalid_argument(stm.str());
			}

			data.push_back((ByteArray::byte) num);
		}
	}

private:

	const char* ptr;
	const string* pstr;
	const int length;
};

void ByteArray::FromSerialization(const char* pSerial)
{
	ByteArrayFromSerialization op(pSerial);
	op.GetData(bytes);
}

void ByteArray::InsertTypeTooManyBytes(size_type numBytes, const char* typeName, size_type typeSize) const
{
	stringstream msg;
	msg << "Cannot insert the first " << numBytes << " bytes from type " << typeName << ", which is only " << typeSize << " bytes wide";

	string msg2 = msg.str();
	throw invalid_argument(msg2);
}

void ByteArray::ThrowAsParametersOutOfRange(size_type offset, const char* typeName) const
{
	stringstream msg;
	msg << "Type " << typeName << " at offset " << offset << " exceeds ByteArray size of " << size();

	string msg2 = msg.str();
	throw invalid_argument(msg2);
}

void ByteArray::FromSerialization(const string& serial)
{
	ByteArrayFromSerialization op(serial);
	op.GetData(bytes);
}

string ByteArray::ToSerialization() const
{
	stringstream stm;

	stm << bytes.size() << ":";

	for (const_iterator cit = bytes.begin(); cit != bytes.end(); ++cit)
		stm << ultos(*cit, 16, 2);

	return stm.str();
}

string ByteArray::ToString() const
{
	stringstream stm;

	stm << *this;
	return stm.str();
}

string ByteArray::ToStringSplitView() const
{
	stringstream stm;
	stringstream stm2;
	unsigned totalOffset = 0;
	unsigned lineCounter = 0;
	const unsigned bytesPerLine = 16;

	stm << size() << " bytes {";

	for (ByteArray::const_iterator cit = begin(); cit != end(); ++cit)
	{
		if (!lineCounter)
			stm << endl << ultos(totalOffset, 16, 4);

		if (!(lineCounter & 7))
			stm << " ";

		stm << " " << ultos(*cit, 16, 2);

		if ((*cit >= ' ') && (*cit <= '~'))
			stm2 << (char) *cit;
		else
			stm2 << '.';

		++totalOffset;

		if (++lineCounter == bytesPerLine)
		{
			stm << " | " << stm2.str();
			stm2.str("");
			lineCounter = 0;
		}
	}

	if (lineCounter)
	{
		while(lineCounter++ < bytesPerLine)
		{
			if (!(lineCounter & 7))
				stm << " ";

			stm << "   ";
		}

		stm << " | " << stm2.str();
	}

	stm << " }";
	return stm.str();
}

string ByteArray::ToASCIIString() const
{
	string result;

	if (size())
	{
		const char* pBegin = (const char*) &this->front();
		result.assign(pBegin,pBegin+size());
	}

	return result;
}

ostream& operator<<(ostream& stm, const ByteArray& ba)
{
	stm << ba.size() << " bytes {";

	for (ByteArray::const_iterator cit = ba.begin(); cit != ba.end(); ++cit)
		stm << " " << ultos(*cit, 16, 2);

	stm << " }";
	return stm;
}

void ByteArray::FromHexString(const char* pHexStr)
{
    resize(0);

    if (!pHexStr)
        throw invalid_argument("Hex string pointer is nil");

    const size_t len = strlen(pHexStr);

    if (len & 1)
        throw invalid_argument("Hex string contains an odd number of bytes");

    //if (!len)
    //    throw invalid_argument("Hex string is empty");

    reserve(len/2);

    try
    {
        while(*pHexStr)
        {
            char buf[3];

            strncpy(buf, pHexStr, 2);
            buf[2] = '\0';

            char* endptr = NULL;
            unsigned long num = strtoul(buf, &endptr, 16);

            if (endptr && *endptr)
                throw invalid_argument("Invalid character in hex string");

            push_back((byte) num);
            pHexStr += 2;
        }
    }
    catch(...)
    {
        resize(0);
        throw;
    }
}

void ByteArray::FromHexString(const string& hexStr)
{
    resize(0);

    const char* pHexStr = hexStr.c_str();
    const size_t len = strlen(pHexStr);

    if (len != hexStr.length())
        throw invalid_argument("Hex string contains a nil");

    FromHexString(pHexStr);
}

string ByteArray::ToHexString() const
{
    stringstream stm;

    for (const_iterator ci = begin(); ci != end(); ++ci)
        stm << utos(*ci, 16, 2);

    string result;
    result = stm.str();
    return result;
}

void ByteArray::InitFromLiteralString(const string& str)
{
	const byte* pStart = (const byte*) &str.at(0);
	assign(pStart, pStart+str.length());
}

}
}
