#ifndef SERVER_H
#define SERVER_H

#include "network/ENet.h"
#include "network/Packet.h"
#include "utils/HashMap.h"
#include "utils/StringBuffer.h"
#include "utils/Types.h"

struct Server final {
    typedef enet_uint16 Port;
    typedef StringBuffer<256> Error;

    class Client final {
        friend class Server;

        ENetPeer* peer;
        int id;

        friend HashMap<int, Client>;

        Client(ENetPeer* peer, int id);
        Client(const Client&) = delete;
        Client(Client&& other);
        Client& operator=(const Client&) = delete;
        Client& operator=(Client&& other);

    public:
        ~Client();

        int getId() const;
        void send(OutPacket& p);
    };

private:
    ENetHost* server;
    Error error;
    HashMap<int, Client> clients;
    int idCounter;

public:
    Server(Port port, int maxClients);
    Server(const Server&) = delete;
    Server(Server&&) = delete;
    ~Server();
    Server& operator=(const Server&) = delete;
    Server& operator=(Server&&) = delete;

    bool hasError() const;
    const Error& getError() const;

    template<typename T>
    void consumeEvents(T& consumer) {
        ENetEvent e;
        while(!hasError() && enet_host_service(server, &e, 0) > 0) {
            switch(e.type) {
                case ENET_EVENT_TYPE_CONNECT: onConnect(e, consumer); break;
                case ENET_EVENT_TYPE_RECEIVE:
                    onPacket(e, consumer);
                    enet_packet_destroy(e.packet);
                    break;
                case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
                case ENET_EVENT_TYPE_DISCONNECT:
                    onDisconnect(e, consumer);
                    break;
                case ENET_EVENT_TYPE_NONE: return;
            }
        }
    }

    void send(OutPacket& p);
    void disconnect(Client& client);

private:
    template<typename T>
    void onConnect(ENetEvent& e, T& consumer) {
        int id = idCounter++;
        if(clients.tryEmplace(id, e.peer, id)) {
            error.clear().append("id is connected twice");
            return;
        }
        static_assert(sizeof(e.peer->data) >= sizeof(id),
                      "private data not big enough for id");
        memcpy(&(e.peer->data), &id, sizeof(id));
        Client* client = clients.search(id);
        if(client != nullptr) {
            consumer.onConnect(*client);
        } else {
            error.clear().append("cannot find added client");
        }
    }

    template<typename T>
    void onPacket(ENetEvent& e, T& consumer) {
        if(e.peer->data == nullptr) {
            error.clear().append("client without data sent package");
            return;
        }
        int id = -1;
        memcpy(&id, &(e.peer->data), sizeof(id));
        Client* client = clients.search(id);
        if(client != nullptr) {
            InPacket in(e.packet);
            consumer.onPacket(*client, in);
        } else {
            error.clear().append("client with invalid id sent package");
        }
    }

    template<typename T>
    void onDisconnect(ENetEvent& e, T& consumer) {
        if(e.peer->data == nullptr) {
            error.clear().append("client without data disconnected");
            return;
        }
        int id = -1;
        memcpy(&id, &(e.peer->data), sizeof(id));
        Client* client = clients.search(id);
        if(client != nullptr) {
            consumer.onDisconnect(*client);
            clients.remove(id);
        } else {
            error.clear().append("client has invalid id");
        }
    }
};

#endif