#include "libs/enet/include/enet.h"

#include "data/HashMap.h"
#include "network/ENet.h"
#include "network/Server.h"
#include "utils/Logger.h"

static_assert(sizeof(enet_uint16) == sizeof(Server::Port),
              "client port has wrong type");

static ENetHost* server;
static HashMap<Server::Client, ENetPeer*> clients;
static Server::Client idCounter = 1;
static Server::OnConnect onConnect = [](Server::Client) {};
static Server::OnDisconnect onDisconnect = [](Server::Client) {};
static Server::OnPacket onPacket = [](Server::Client, InPacket&) {};

Error Server::start(Port port, int maxClients) {
    if(server != nullptr) {
        return {"already started"};
    } else if(ENet::add()) {
        return {"cannot initialize enet"};
    }

    ENetAddress address;
    memset(&address, 0, sizeof(ENetAddress));
    address.host = ENET_HOST_ANY;
    address.port = port;

    server = enet_host_create(&address, maxClients, 3, 0, 0);
    if(server == nullptr) {
        ENet::remove();
        return {"cannot create enet server host"};
    }
    return {};
}

void Server::stop() {
    if(server == nullptr) {
        return;
    }
    for(ENetPeer* peer : clients.values()) {
        enet_peer_reset(peer);
    }
    enet_host_destroy(server);
    server = nullptr;
    ENet::remove();
}

void writeId(ENetPeer* peer, Server::Client id) {
    static_assert(sizeof(peer->data) >= sizeof(id),
                  "private data not big enough for id");
    memcpy(&(peer->data), &id, sizeof(id));
}

Server::Client getId(ENetPeer* peer) {
    Server::Client id = -1;
    memcpy(&id, &(peer->data), sizeof(id));
    return id;
}

static void handleConnect(ENetEvent& e) {
    Server::Client id = idCounter++;
    if(clients.tryEmplace(id, e.peer)) {
        LOG_WARNING("id is connected twice");
        return;
    }
    writeId(e.peer, id);
    onConnect(id);
}

static void handlePacket(ENetEvent& e) {
    if(e.peer->data == nullptr) {
        LOG_WARNING("client without data sent package");
        return;
    }
    Server::Client id = getId(e.peer);
    InPacket in(e.packet->data, e.packet->dataLength);
    onPacket(id, in);
}

static void handleDisconnect(ENetEvent& e) {
    if(e.peer->data == nullptr) {
        LOG_WARNING("client without data disconnected");
        return;
    }
    Server::Client id = getId(e.peer);
    onDisconnect(id);
    if(clients.remove(id)) {
        LOG_WARNING("removed non existing client");
    }
}

void Server::tick() {
    if(server == nullptr) {
        return;
    }
    ENetEvent e;
    while(enet_host_service(server, &e, 0) > 0) {
        switch(e.type) {
            case ENET_EVENT_TYPE_CONNECT: handleConnect(e); break;
            case ENET_EVENT_TYPE_RECEIVE:
                handlePacket(e);
                enet_packet_destroy(e.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
            case ENET_EVENT_TYPE_DISCONNECT: handleDisconnect(e); break;
            case ENET_EVENT_TYPE_NONE: return;
        }
    }
}

static ENetPacket* fromBuffer(const Buffer& buffer, int index) {
    constexpr enet_uint32 flags[] = {ENET_PACKET_FLAG_RELIABLE, 0,
                                     ENET_PACKET_FLAG_UNSEQUENCED};
    return enet_packet_create(buffer, buffer.getLength(), flags[index]);
}

void Server::send(const OutPacket& p, PacketSendMode mode) {
    if(server != nullptr) {
        int index = static_cast<int>(mode);
        enet_host_broadcast(server, index, fromBuffer(p.buffer, index));
    }
}

void Server::send(Server::Client client, const OutPacket& p,
                  PacketSendMode mode) {
    if(server == nullptr) {
        return;
    }
    ENetPeer** peer = clients.search(client);
    if(peer != nullptr) {
        int index = static_cast<int>(mode);
        enet_peer_send(*peer, index, fromBuffer(p.buffer, index));
    }
}

void Server::disconnect(Client client) {
    ENetPeer** peer = clients.search(client);
    if(peer != nullptr) {
        enet_peer_disconnect(*peer, 0);
    }
}

void Server::setConnectHandler(OnConnect oc) {
    onConnect = oc;
}

void Server::setDisconnectHandler(OnDisconnect od) {
    onDisconnect = od;
}

void Server::setPacketHandler(OnPacket op) {
    onPacket = op;
}

void Server::resetHandler() {
    onConnect = [](Server::Client) {};
    onDisconnect = [](Server::Client) {};
    onPacket = [](Server::Client, InPacket&) {};
}