#ifndef PROTOCOL_HPP_
#define PROTOCOL_HPP_

#include <cstring>
#include <Version/Version.hpp>
#include <Utilities/Extern.hpp>

/**
 * @brief Namespace containing all structures used by the protocol.
 */
namespace Protocol
{
    /**
     * @brief Timeout in seconds.
     */
    static constexpr int timeout = 20;

    /**
     * @brief The number of controller channels.
     */
    static constexpr uint8_t numControllerChannels = 8;

    /**
     * @brief Client greeting code.
     */
    enum class ClientGreetingCode : uint8_t
    {
        /**
         * @brief Ping; server should respond with a Pong greeting.
         */
        Ping = 0,

        /**
         * @brief Client logging into the game.
         */
        Login = 1,

        /**
         * @brief Client requesting to register a new account.
         */
        Register = 2,
    };

    /**
     * @brief Server greeting code.
     */
    enum class ServerGreetingCode : uint8_t
    {
        /**
         * @brief Pong; response to a ping, followed by 32 "motto" bytes.
         */
        Pong = 0,

        /**
         * @brief Greeting sent when the login fails.
         */
        LoginFailed = 1,

        /**
         * @brief Greeting when login succeeded.
         */
        LoginOK = 2,

        /**
         * @brief Greeting when a login was attempted with an incompatible version.
         */
        IncompatibleVersion = 3,

        /**
         * @brief Greeting when login with session ID fails.
         */
        SessionExpired = 4,

        /**
         * @brief Wrong username or password during password authentication.
         */
        WrongUsernameOrPassword = 5,

        /**
         * @brief Banned user attempted to log in using password authentication.
         */
        UserBanned = 6,

        /**
         * @brief The user is still pending (not accepted by admin).
         */
        UserPending = 7,

        /**
         * @brief An account was successfully created (possibly pending).
         */
        AccountCreated = 8,

        /**
         * @brief Account was not created.
         */
        AccountNotCreated = 9,

        /**
         * @brief Login attempted by a user that's already in the game.
         */
        AlreadyLoggedIn = 10,
    };

    /**
     * @brief Greeting message header.
     * 
     * This is a header with which the client and server initiate communication.
     */
    template<typename TCode>
    struct GreetingHeader
    {
        /**
         * @brief The ASCII string "VXLV".
         */
        char magic[4];

        /**
         * @brief The version of the sender.
         * 
         * The client sets this to their own version, and the servers to its version. It is
         * allowed for incompatible versions to exchange greetings, but the game cannot be
         * joined. This means that a client can still ping an incompatible server. In this case,
         * the server will report its version in the greeting it sends back.
         */
        Version version;

        /**
         * @brief Greeting code.
         */
        TCode code;

        /**
         * @brief Default constructor, make an invalid header.
         */
        GreetingHeader() : version{0xFF, 0xFF, 0xFF} {};

        /**
         * @brief Construct a greeting with the specified code.
         * 
         * @param code The greeting code.
         */
        GreetingHeader(TCode code) : version(Version::ofThisBuild), code(code)
        {
            memcpy(magic, "VXLV", 4);
        }

        /**
         * @brief Returns true if the magic string is correct.
         */
        bool isValid()
        {
            return memcmp(magic, "VXLV", 4) == 0;
        }
    };

    typedef GreetingHeader<ClientGreetingCode> ClientGreetingHeader;
    typedef GreetingHeader<ServerGreetingCode> ServerGreetingHeader;

    /**
     * @brief Authentication types for logins.
     */
    enum class AuthenticationType : uint8_t
    {
        SessionID = 1,
        Password = 2,
    };

    /**
     * @brief A login header, sent after a Login greeting.
     */
    struct LoginHeader
    {
        AuthenticationType authType;
        uint8_t payloadSize;
    };

    /**
     * @brief A registration header, sent after a Register greeting.
     */
    struct RegisterHeader
    {
        uint8_t userNameSize;
        uint8_t passwordSize;
    };

    /**
     * @brief Message types (used in the message header).
     *
     * The message protocol is version-gated, so the enum values can be different in every version.
     * Therefore we do not explicitly number them.
     */
    enum class MessageType : uint8_t
    {
        KeepAlive,
        SetVisibleChunkBox,
        ChunkData,
        ProtocolError,
        Logout,
        EntityMessage,
        BlockUpdateList,
        CreateEntity,
        DeleteEntity,
        Chat,
        PlaySound,
        BeltViewUpdated,
        BeltViewRemoved,
        KickedFromServer,
        AttachController,
        DetachController,
        RemoteCall,
        RemoteReturn,
        GuiClosed,
        SetClientPrefs,
    };

    /**
     * @brief Header prepended to all messages.
     */
    struct MessageHeader
    {
        static constexpr uint8_t messageTypeMask = 0x7F;
        static constexpr uint8_t compressed = 0x80;

        uint8_t typeAndFlags;
        uint8_t payloadSize[2];
    };

    /**
     * @brief Payload for an `EntityMessage` message.
     */
    struct EntityMessage
    {
        uint64_t entityID;
        char payload[];
    };

    /**
     * @brief Represents a block update.
     */
    struct BlockUpdate
    {
        glm::ivec3 pos;
        int32_t blockId;
        uint8_t meta;
        uint8_t resv[3];
    };

    /**
     * @brief Payload for a `DeleteEntity` message.
     */
    struct DeleteEntityMessage
    {
        uint64_t id;
    };

    /**
     * @brief Client preferences, payload of a SetClientPrefs message.
     */
    struct ClientPrefs
    {
        uint8_t horizontalViewDistance = 0;
        uint8_t verticalViewDistance = 0;
    };
};

#endif