#include "core/simulator.h"

#include <memory>

#include "core/rng.h"
#include "network/link.h"

namespace dofs {

bool Simulator::Cmp::operator()(const Item& a, const Item& b) const noexcept {
        if (a.when != b.when)
                return a.when > b.when;

        return a.id > b.id;
}

Time Simulator::now() const noexcept {
        return _now;
}

bool Simulator::cancel(EventId id) {
        return _cancelled.insert(id).second;
}

bool Simulator::run_next() {
        while (!_event_pq.empty()) {
                Item it = _event_pq.top();
                _event_pq.pop();

                if (_cancelled.erase(it.id) > 0)
                        continue;

                _now = it.when;
                it.fn();
                return true;
        }

        return false;
}

void Simulator::run_until(Time end_time) {
        while (!_event_pq.empty()) {
                const Item& top = _event_pq.top();

                if (end_time < top.when)
                        break;

                run_next();
        }
}

void Simulator::lock() noexcept {
        _locked = true;
}

void Simulator::flush_after(Time grace) noexcept {
        _locked = true;

        const Time deadline = _now + grace;
        run_until(deadline);

        while (!_event_pq.empty()) {
                _event_pq.pop();
        }

        _cancelled.clear();
}


std::vector<std::unique_ptr<Simulator>>& Simulator::_registry() {
        static std::vector<std::unique_ptr<Simulator>> reg;
        return reg;
}

std::pair<InstanceId, Simulator *> Simulator::create_simulator(InstanceId id) {
        if (id == INVALID_INSTANCE_ID) {
                return {INVALID_INSTANCE_ID, nullptr};
        }

        auto& reg = _registry();

        if (static_cast<size_t>(id) >= reg.size()) {
                reg.resize(static_cast<size_t>(id) + 1);
        }

        if (!reg[static_cast<size_t>(id)]) {
                auto sim = std::make_unique<Simulator>();
                Simulator* ptr = sim.get();
                reg[static_cast<size_t>(id)] = std::move(sim);
                return {id, ptr};
        }

        return {id, reg[static_cast<size_t>(id)].get()};
}

Simulator * Simulator::get_simulator(InstanceId id) noexcept {
        if (id == INVALID_INSTANCE_ID)
                return nullptr;

        auto& reg = _registry();
        const size_t idx = static_cast<size_t>(id);

        if (idx >= reg.size())
                return nullptr;

        return reg[idx].get();
}

Rng * Simulator::create_rng(std::uint64_t seed) {
        if (_rng)
                return nullptr;

        _rng = std::make_unique<Rng>(seed);
        return _rng.get();
}
Rng * Simulator::get_rng() noexcept {
        return _rng.get();
}

Rng const * Simulator::get_rng() const noexcept {
        return _rng.get();
}

std::pair<LinkId, Link *> Simulator::create_link(
        NetworkNode* a, PortId a_port,
        NetworkNode* b, PortId b_port,
        Time latency,
        double bandwidth_gbps) {

        if (!a || !b)
                return {INVALID_LINK_ID, nullptr};

        if (bandwidth_gbps <= 0.0)
                return {INVALID_LINK_ID, nullptr};

        if (latency < Time{0})
                return {INVALID_LINK_ID, nullptr};

        const LinkId id = static_cast<LinkId>(_links.size());
        auto up =
                std::unique_ptr<Link>(new Link(this, id,
                                               a, a_port, b, b_port,
                                               latency,
                                               bandwidth_gbps));

        Link* raw = up.get();
        _links.push_back(std::move(up));
        return {id, raw};
}

Link * Simulator::get_link(LinkId id) noexcept {
        if (id == INVALID_LINK_ID)
                return nullptr;

        const size_t idx = static_cast<size_t>(id);

        if (idx >= _links.size())
                return nullptr;

        return _links[idx].get();
}

Link const * Simulator::get_link(LinkId id) const noexcept {
        if (id == INVALID_LINK_ID)
                return nullptr;

        const size_t idx = static_cast<size_t>(id);

        if (idx >= _links.size())
                return nullptr;

        return _links[idx].get();
}

} // namespace dofs
