#include <iostream>
#include <vector>
#include <stdexcept>
#include <cstdint>

class Packet
{
private:
	std::vector<uint8_t> buffer;
	int byteIndex;
	int maxByteIndex;

public:
	Packet() : byteIndex(0), maxByteIndex(0)
	{
	}

	char* GetBuffer()
	{
		return reinterpret_cast<char*>(&buffer[0]);
	}

	int GetLength()
	{
		return buffer.size();
	}

	int GetByteIndex()
	{
		return byteIndex;
	}

	void SetByteIndex(int value)
	{
		if (value < 0 || value >= buffer.size())
		{
			throw std::out_of_range("ByteIndex must be an index in the Buffer");
		}
		byteIndex = value;
	}

	bool HasHeader()
	{
		return byteIndex >= 2;
	}

	bool HasData()
	{
		return byteIndex == maxByteIndex;
	}

	void Begin()
	{
		buffer.push_back(0);
		buffer.push_back(0);
		byteIndex += 2;
	}

	void End()
	{
		buffer[0] = static_cast<uint8_t>(buffer.size()) >> 8;
		buffer[1] = static_cast<uint8_t>(buffer.size());
	}

	bool ReadHeader()
	{
		int previousByteIndex = byteIndex;
		byteIndex = 0;
		uint16_t length;
		ReadUInt16(length);
		maxByteIndex = length - 1;
		byteIndex = previousByteIndex;
		return maxByteIndex > 2;
	}

	void WriteEventID(uint8_t value)
	{
		buffer.push_back(value);
		byteIndex++;
	}

	void WriteByte(uint8_t value)
	{
		buffer.push_back(value);
		byteIndex++;
	}

	void WriteUInt16(uint16_t value)
	{
		buffer.push_back(static_cast<uint8_t>(value >> 8));
		buffer.push_back(static_cast<uint8_t>(value));
		byteIndex += 2;
	}

	void WriteString(std::string value, int maxLength = 255)
	{
		if (maxLength < 0 || maxLength > 65535)
		{
			throw std::out_of_range("maxLength must be in the range [0, 65535]");
		}
		if (value.length() > maxLength)
		{
			throw std::out_of_range("String length was over the given max length");
		}
		WriteUInt16(static_cast<uint16_t>(value.length()));
		for (int i = 0; i < value.length(); ++i)
		{
			WriteByte(value[i]);
		}
	}

	bool ReadEventID(uint8_t& value)
	{
		return ReadByte(value);
	}

	bool ReadByte(uint8_t& value)
	{
		value = 0;
		if (byteIndex >= buffer.size())
		{
			return false;
		}
		value = buffer[byteIndex];
		byteIndex++;
		return true;
	}

	bool ReadUInt16(uint16_t& value)
	{
		value = 0;
		if (byteIndex + 1 >= buffer.size())
		{
			return false;
		}
		value = static_cast<uint16_t>(buffer[byteIndex] << 8);
		value |= static_cast<uint16_t>(buffer[byteIndex + 1]);
		byteIndex += 2;
		return true;
	}

	bool ReadString(std::string& value, int maxLength = 255)
	{
		value = "";
		uint16_t length;
		if (!ReadUInt16(length) || length > maxLength || byteIndex + length > buffer.size())
		{
			return false;
		}
		for (int i = 0; i < length; ++i)
		{
			value += static_cast<char>(buffer[byteIndex + i]);
		}
		byteIndex += length;
		return true;
	}
};

int main()
{
	Packet packet;
	packet.Begin();
	packet.WriteEventID(42);
	packet.WriteByte(43);
	packet.WriteUInt16(65535);
	packet.WriteString("Hello World", 20);
	packet.End();

	packet.SetByteIndex(2);
	uint8_t eventID;
	if (!packet.ReadEventID(eventID)) { std::cout << "Error reading" << std::endl; return -1; }
	std::cout << static_cast<int>(eventID) << std::endl;
	uint8_t byteValue;
	if (!packet.ReadByte(byteValue)) { std::cout << "Error reading" << std::endl; return -1; }
	std::cout << static_cast<int>(byteValue) << std::endl;
	uint16_t ushortValue;
	if (!packet.ReadUInt16(ushortValue)) { std::cout << "Error reading" << std::endl; return -1; }
	std::cout << ushortValue << std::endl;
	std::string stringValue;
	if (!packet.ReadString(stringValue)) { std::cout << "Error reading" << std::endl; return -1; }
	std::cout << stringValue << std::endl;

	/*
	To read from a TCP stream:
	// packet is a per end point object that persists through read callbacks
	for (int readByteIndex = 0; readByteIndex < receiveBufferSize; ++readByteIndex)
	{
		packet.WriteByte(receiveBuffer[readByteIndex]);
		if (!packet.HasHeader())
		{
			if (!packet.ReadHeader())
			{
				DisconnectEndPoint();
			}
		}
		if (!packet.HasData())
		{
			// Packets is a ConcurrentQueue
			endPoint.Packets.Enqueue(packet);
			packet = new Packet();
		}
	}
	*/

	return 0;
}