#include "network/switch/dedicated_buffer.h"

namespace dofs {

DedicatedBuffer::DedicatedBuffer(Simulator* const sim,
                                 NetworkSwitch* const owner,
                                 Bytes total_bytes,
                                 uint16_t ports)
        : SwitchBuffer(sim, owner, SwitchBufferType::DEDICATED, total_bytes, ports),
          _per_port_cap(ports ? total_bytes / ports : Bytes(0)),
          _per_port_queues(ports)
{}

std::array<std::deque<SwitchBuffer::Queued>, SwitchBuffer::PRI_COUNT>&DedicatedBuffer::queues_for(
        PortId p) {
        return _per_port_queues[p];
}

const std::array<std::deque<SwitchBuffer::Queued>, SwitchBuffer::PRI_COUNT>&DedicatedBuffer::queues_for(
        PortId p) const {
        return _per_port_queues[p];
}

bool DedicatedBuffer::on_enqueue_cap_check(PortId port, Bytes sz) {
        if (port >= _port_cnt)
                return false;

        // Guard per-port cap
        if (_per_port_bytes[port] + sz > _per_port_cap)
                return false;

        return true;
}
void DedicatedBuffer::on_enqueue_commit(PortId port, Bytes sz) {
        _per_port_bytes[port] += sz;
}
void DedicatedBuffer::on_dequeue_commit(PortId port, Bytes sz) {
        _per_port_bytes[port] -= sz;
}

bool DedicatedBuffer::enqueue_packet(const Packet& pkt, PortId egress,
                                     FlowPriority prio) {
        if (egress >= _port_cnt)
                return false;

        const Bytes sz = pkt.total_size();

        if (!on_enqueue_cap_check(egress, sz))
                return false;

        Queued q{pkt, egress, prio, _sim->now(), sz};
        auto& qset = queues_for(egress);
        qset[to_idx(prio)].push_back(std::move(q));
        on_enqueue_commit(egress, sz);

        schedule_drain_if_needed(egress);

        return true;
}

bool DedicatedBuffer::drain_one(PortId port) {
        return drain_one_common(port);
}

} // namespace dofs
