#pragma once

#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/string.hpp>
#include <vector>

namespace blitz {
namespace protocol {

class PlayerInfo;

#define Operators(Type, GodotType)                                                                                                    \
	ByteBuffer& operator>>(Type& a_Data) {                                                                                            \
		a_Data = m_Buffer.decode_##GodotType(m_ReadOffset);                                                                           \
		m_ReadOffset += sizeof(a_Data);                                                                                               \
		return *this;                                                                                                                 \
	}                                                                                                                                 \
                                                                                                                                      \
	ByteBuffer& operator<<(Type a_Data) {                                                                                             \
		m_Buffer.resize(m_Buffer.size() + sizeof(a_Data));                                                                            \
		m_Buffer.encode_##GodotType(m_Buffer.size() - sizeof(a_Data), a_Data);                                                        \
		return *this;                                                                                                                 \
	}

class ByteBuffer {
  private:
	godot::PackedByteArray m_Buffer;
	std::size_t m_ReadOffset;

  public:
	ByteBuffer(godot::PackedByteArray&& a_Buffer) : m_Buffer(std::move(a_Buffer)), m_ReadOffset(0) {}
	ByteBuffer() : m_ReadOffset(0) {
		m_Buffer.resize(0);
	}

	const godot::PackedByteArray& GetByteArray() const {
		return m_Buffer;
	}

	godot::PackedByteArray& GetByteArray() {
		return m_Buffer;
	}

	// Integers
	Operators(int8_t, s8);
	Operators(uint8_t, u8);
	Operators(int16_t, s16);
	Operators(uint16_t, u16);
	Operators(int32_t, s32);
	Operators(uint32_t, u32);
	Operators(int64_t, s64);
	Operators(uint64_t, u64);

	// Reals
	Operators(float, float);
	Operators(double, double);

	ByteBuffer& operator<<(const godot::String& a_Data);
	ByteBuffer& operator>>(godot::String& a_Data);

	ByteBuffer& operator<<(const godot::Vector3& a_Data);
	ByteBuffer& operator>>(godot::Vector3& a_Data);

	template <typename T>
	ByteBuffer& operator<<(const std::vector<T>& a_Data) {
		*this << static_cast<std::uint32_t>(a_Data.size());
		for (const T& data : a_Data) {
			*this << data;
		}
		return *this;
	}

	template <typename T>
	ByteBuffer& operator>>(std::vector<T>& a_Data) {
		std::uint32_t arraySize;
		*this >> arraySize;
		a_Data.resize(arraySize);
		for (std::uint32_t i = 0; i < arraySize; i++) {
			*this >> a_Data[i];
		}
		return *this;
	}

	ByteBuffer& operator<<(const PlayerInfo& a_Data);
	ByteBuffer& operator>>(PlayerInfo& a_Data);
};

}  // namespace protocol
}  // namespace blitz
