/* GPC (C) 2017-2018 Stephane Charette <stephanecharette@gmail.com>
 * $Id: BinaryProtocol.cpp 2492 2018-03-21 06:22:15Z stephane $
 */

#include "GPC.hpp"


BinaryProtocol::BinaryProtocol(void) :
	sequence_number(0),
	code(ECode::kInvalid)
{
	return;
}


BinaryProtocol::~BinaryProtocol(void)
{
	return;
}


BinaryProtocol::BinaryProtocol(const VBytes &v, const size_t number_of_bytes) :
	BinaryProtocol()
{
	parse(v, number_of_bytes);

	return;
}


BinaryProtocol &BinaryProtocol::parse(const VBytes &v, const size_t number_of_bytes)
{
	code = ECode::kInvalid;
	content = VBytes(v.begin(), v.begin() + number_of_bytes);

	// minimum message size is 9 bytes
	if (content.size() < 9)
	{
		LOG_MSG("content size of " + std::to_string(number_of_bytes) + " bytes is too small to be a valid binary protocol message");
	}
	else
	{
		if (*content.begin() != 0x02)
		{
			LOG_MSG("binary protocol message must start with STX (0x02)");
		}
		else
		{
			if (*content.rbegin() != 0x03)
			{
				LOG_MSG("binary protocol message must end with ETX (0x03)");
			}
			else
			{
				uint32_t *ptr = reinterpret_cast<uint32_t*>(content.data() + 1); // +1 since the length starts at offset 0x01
				uint32_t len = ByteOrder::swap(*ptr);	// remember the length is in big-endian

				// len does not include the STX nor the number of bytes, so need to add 5
				if (5 + len > number_of_bytes)
				{
					LOG_MSG("binary protocol message has an invalid length");
				}
				else
				{
					sequence_number = content[6];
					code = (ECode)content[7];

					LOG_MSG("parsed " << number_of_bytes << " bytes: " << describe());
				}
			}
		}
	}

	if (code == ECode::kInvalid)
	{
		/// @throw Lox::Exception if the data cannot be parsed
		throw Lox::Exception(LOX_WHERE, "Failed to parse binary protocol message from device.");
	}

	return *this;
}


VBytes BinaryProtocol::message_data(void) const
{
	VBytes v;

	if (code != ECode::kInvalid && content.size() > 9)
	{
		auto iter1 = content.begin() + 8;
		auto iter2 = content.begin() + content.size() - 2;
		v = VBytes(iter1, iter2);
	}

	return v;
}


std::string BinaryProtocol::describe(void) const
{
	const VBytes v = message_data();
	std::stringstream ss;

	switch (code)
	{
		case ECode::kInvalid:
		{
			ss << "Invalid message.";
			break;
		}
		case ECode::kPowerUpMsg:
		{
			const uint32_t *firmware_revision = reinterpret_cast<const uint32_t*>(v.data());
			const uint8_t *protocol_rev = v.data() + 4;
			ss << "0x01: Power Up.  Imager firmware revision #" << ByteOrder::swap(*firmware_revision) << ", protocol revision " << protocol_rev << ".";
			break;
		}
		case ECode::kAcknowledgeMsg:
		{
			const uint8_t original_message_id		= v.data()[0];
//			const uint8_t original_sequence_number	= v.data()[1];
//			const uint32_t *encoder_rate			= reinterpret_cast<const uint32_t*>(v.data() + 2);
			const uint32_t *response_code			= reinterpret_cast<const uint32_t*>(v.data() + 6);
//			const uint32_t *encoder_count			= reinterpret_cast<const uint32_t*>(v.data() + 10);
			ss << "0x09: Acknowledge of previous command 0x" << std::hex << std::setfill('0') << std::setw(2) << (int)original_message_id;
			if (*response_code != 0)
			{
				ss << ", but response code indicates an error";
			}
			ss << ".";
			break;
		}
		case ECode::kDoneWithFiles:
		{
			ss << "0x70: EOF.";
			break;
		}
		case ECode::kSendIjsFile:
		{
			ss << "0x72: Send .IJS print job file.";
			break;
		}
		case ECode::kSendIjbFile:
		{
			ss << "0x7d: Send .IJB bitmap image file.";
			break;
		}
		default:
		{
			ss << "0x" << std::hex << std::setfill('0') << std::setw(2) << (int)code << ": unknown message id.";
			break;
		}
	}

	return ss.str();
}


BinaryProtocol &BinaryProtocol::create(const ECode new_code)
{
	static uint8_t incrementing_sequence_number = 0x00;
	incrementing_sequence_number ++;

	code = new_code;
	sequence_number = incrementing_sequence_number;
	content.clear();

	content =
	{
		0x02,	// STX
		0x00,
		0x00,
		0x00,
		0x04,	// number of bytes (starting with the next byte and going to the final ETX byte)
		0x00,	// checksum
		0x00,	// sequence
		0x00,	// message id (code) will be manually updated below
		0x03	// ETX
	};

	content[7] = (uint8_t)code; // message id (code) which was initially set to zero a few lines above

	return *this;
}


BinaryProtocol &BinaryProtocol::append(const VBytes &file_contents)
{
	if (content.size() < 9)
	{
		/// @throw Lox::Exception if the binary protocol message is invalid (too small)
		throw Lox::Exception(LOX_WHERE, "Cannot append bytes to an invalid binary protocol message (too small).");
	}

	// remove the old ETX byte at the end of the message
	size_t idx = content.size() - 1;
	if (content[idx] != 0x03)
	{
		/// @throw Lox::Exception if the binary protocol message does not end with ETX (0x03)
		throw Lox::Exception(LOX_WHERE, "Cannot append bytes to an invalid binary protocol message (does not end with 0x03).");
	}
	content.resize(idx);
	content.reserve(content.size() + file_contents.size() + 1);
	content.insert(content.end(), file_contents.begin(), file_contents.end());
	content.push_back(0x03);

	// update the NumBytes value at position 0x01-0x04
	// (but remember to reverse the bytes since the protocol uses big endian)
	const uint32_t len = content.size() - 5;
	const uint8_t *ptr = reinterpret_cast<const uint8_t*>(&len);
	content[4] = ptr[0];
	content[3] = ptr[1];
	content[2] = ptr[2];
	content[1] = ptr[3];

	return *this;
}
