#include "ReceiveManager.hpp"

#include <cstring>
#include <iostream>

#include "behavior/events/audio/AudioEvent.hpp"
#include "behavior/events/info/InfoEvent.hpp"
#include "behavior/events/pong/PongEvent.hpp"
#include "behavior/events/search/SearchEvent.hpp"


namespace drp::managers {


ReceiveManager::ReceiveManager(const std::shared_ptr<Context>& context) {
    this->context = context;

    // register the different events type
    this->registry = {
        {event::EventType::PONG, std::make_shared<event::PongEvent>()},
        {event::EventType::SEARCH, std::make_shared<event::SearchEvent>()},
        {event::EventType::INFO, std::make_shared<event::InfoEvent>()},
        {event::EventType::AUDIO, std::make_shared<event::AudioEvent>()},
    };
}


void ReceiveManager::run() const {
    // prepare space for the sender address
    sockaddr_storage fromAddress {};
    socklen_t fromAddressLength = sizeof(fromAddress);

    std::array<std::uint8_t, packet::base::maxPacketLength> buffer {};

    // receive new data
    const ssize_t size = recvfrom(
        this->context->socket,
        buffer.data(),
        buffer.size(),
        0,
        reinterpret_cast<sockaddr*>(&fromAddress),
        &fromAddressLength
    );
    if (size == -1)
        throw std::runtime_error("[Receiver] Could not receive the packet: " + std::string(strerror(errno)));

    // deserialize the packet
    std::vector data(buffer.begin(), buffer.end());
    const auto packet = drp::packet::base::Packet::deserialize(data);

    // if the packet channel is neither 0 (all) nor the current one, ignore it
    if (packet.channel != 0 && packet.channel != this->context->me.channel)
        return;

    // decrypt the packet
    // TODO(Faraphel): handle exception ?
    drp::packet::base::PacketContent packetContent = packet.getContent(*this->context);

    // look for a saved peer with the same address
    auto remotePeer = std::ranges::find_if(
        this->context->remotePeers,
        [&](const std::shared_ptr<RemotePeer>& remotePeer) { return
            remotePeer->addressLength == fromAddressLength and
            std::memcmp(&fromAddress, &remotePeer->address, fromAddressLength) == 0;
        }
    );
    // if found, update the latest connection date
    if (remotePeer != this->context->remotePeers.end())
        (*remotePeer)->latestConnection = std::chrono::high_resolution_clock::now();

    // get the corresponding event class
    std::shared_ptr<event::BaseEvent> event;
    try {
        event = this->registry.at(packetContent.eventType);
    } catch (const std::out_of_range& exception) {
        std::cerr << "[Receiver] Unsupported event type: " << std::to_string(static_cast<int>(packetContent.eventType)) << std::endl;
        return;
    }

    std::cout << "[Receiver] handling event: " << std::to_string(static_cast<int>(packetContent.eventType)) << std::endl;

    // ask the event class to handle the event
    try {
        event->handle(
            *this->context,
            packetContent.data,
            fromAddress,
            fromAddressLength
        );
    } catch (const std::exception& exception) {
        std::cerr << "[Receiver] Unhandled exception: " << exception.what() << std::endl;
    }
}

void ReceiveManager::loop() const {
    while (true)
        this->run();
}


}
