#pragma once

/**
 * \file Packets.h
 * \brief File containing the definitions of the packets
 */

#include <blitz/common/NonCopyable.h>
#include <blitz/protocol/PacketData.h>
#include <blitz/protocol/PacketDeclare.h>

namespace blitz {
namespace protocol {

class PacketVisitor;

/** A Packet id is 8 bits wide */
using PacketID = std::uint8_t;

#define DeclarePacket(PacketName, ...) /** PacketName */ PacketName,

/**
 * \enum PacketType
 * \brief Map a Packet to an id
 */
enum class PacketType : PacketID {

	DeclareAllPacket()

	/** The number of packets */
	PACKET_COUNT
};


#undef DeclarePacket


class Packet : private NonCopyable {
  public:
	/**
	 * \return The real type of the packet
	 */
	virtual PacketType GetType() const = 0;

	/**
	 * \brief The network peer who sent the packet
	 */
	PeerID m_Sender;

  private:
	/** Use a PacketVisitor to make double-dispatch possible */
	virtual void Accept(PacketVisitor& a_Visitor) const = 0;

	friend class PacketVisitor;
};





namespace packets {

/**
 * \class ConcretePacket
 * \brief A Packet associated with an id and holding data
 * \tparam PT The packet type
 * \tparam Data The structure holding the data of the packet (in blitz::protocol::data namespace)
 */
template <PacketType PT, typename Data>
class ConcretePacket : public Packet {
  public:
	/** The type of the struct holding the data */
	using PacketDataType = Data;

	/** The structure holding the actual data */
	PacketDataType m_Data;

	/** Construct the packet with data of type PacketDataType */
	ConcretePacket(const PacketDataType& a_Data = {});

	constexpr PacketType GetType() const override {
		return PT;
	};

  private:
	void Accept(PacketVisitor& a_Visitor) const override;
};





// define BLITZ_INSTANCIATE_PACKETS
// before including this file
// if you want to instantiate templates
#ifdef BLITZ_INSTANCIATE_PACKETS
#define DeclarePacket(PacketName, ...)                                                                                                \
	using PacketName = ConcretePacket<PacketType::PacketName, data::PacketName>;                                                      \
	template class ConcretePacket<PacketType::PacketName, data::PacketName>;
#else
#define DeclarePacket(PacketName, ...) /** Defines the PacketName packet */                                                           \
	using PacketName = ConcretePacket<PacketType::PacketName, data::PacketName>;
#endif

DeclareAllPacket()

#undef DeclarePacket

}  // namespace packets

}  // namespace protocol
}  // namespace blitz
