#include "foundation/event.h"

#include <algorithm>

namespace pp::foundation {

EventDispatcher::EventDispatcher(std::size_t max_subscriptions) noexcept
    : max_subscriptions_(max_subscriptions)
{
    subscriptions_.reserve(std::min(max_subscriptions_, max_event_subscriptions));
}

std::size_t EventDispatcher::size() const noexcept
{
    return subscriptions_.size();
}

bool EventDispatcher::empty() const noexcept
{
    return subscriptions_.empty();
}

std::size_t EventDispatcher::max_subscriptions() const noexcept
{
    return max_subscriptions_;
}

Result<std::uint64_t> EventDispatcher::subscribe(std::uint32_t type, EventCallback callback, void* user_data)
{
    if (max_subscriptions_ == 0U || max_subscriptions_ > max_event_subscriptions) {
        return Result<std::uint64_t>::failure(
            Status::out_of_range("event dispatcher capacity is outside the configured range"));
    }

    if (type == 0U) {
        return Result<std::uint64_t>::failure(Status::invalid_argument("event type must not be zero"));
    }

    if (callback == nullptr) {
        return Result<std::uint64_t>::failure(Status::invalid_argument("event callback must not be null"));
    }

    if (subscriptions_.size() >= max_subscriptions_) {
        return Result<std::uint64_t>::failure(Status::out_of_range("event dispatcher is full"));
    }

    const auto id = next_subscription_id_++;
    subscriptions_.push_back(EventSubscription {
        .id = id,
        .type = type,
        .callback = callback,
        .user_data = user_data,
    });

    return Result<std::uint64_t>::success(id);
}

Status EventDispatcher::unsubscribe(std::uint64_t subscription_id) noexcept
{
    const auto found = std::find_if(
        subscriptions_.begin(),
        subscriptions_.end(),
        [subscription_id](const EventSubscription& subscription) {
            return subscription.id == subscription_id;
        });

    if (found == subscriptions_.end()) {
        return Status::out_of_range("event subscription id was not found");
    }

    subscriptions_.erase(found);
    return Status::success();
}

std::size_t EventDispatcher::publish(const Event& event) const noexcept
{
    if (event.type == 0U) {
        return 0;
    }

    std::size_t delivered = 0;
    for (const auto& subscription : subscriptions_) {
        if (subscription.type == event.type) {
            subscription.callback(event, subscription.user_data);
            ++delivered;
        }
    }

    return delivered;
}

void EventDispatcher::clear() noexcept
{
    subscriptions_.clear();
}

}
