//! Queue kinds.
//!
//! The reactor's event queue uses different queues to group events by priority and polls them in a
//! round-robin manner. This way, events are only competing for time within one queue, non-congested
//! queues can always assume to be speedily processed.

use std::{fmt::Display, num::NonZeroUsize};

use enum_iterator::IntoEnumIterator;
use serde::Serialize;

/// Scheduling priority.
///
/// Priorities are ordered from lowest to highest.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, IntoEnumIterator, PartialOrd, Ord, Serialize)]
pub enum QueueKind {
    /// Control messages for the runtime itself.
    Control,
    /// Network events that were initiated outside of this node.
    ///
    /// Their load may vary and grouping them together in one queue aides DoS protection.
    NetworkIncoming,
    /// Network events that were initiated by the local node, such as outgoing messages.
    Network,
    /// Events of unspecified priority.
    ///
    /// This is the default queue.
    Regular,
    /// Reporting events on the local node.
    ///
    /// Metric events take precedence over most other events since missing a request for metrics
    /// might cause the requester to assume that the node is down and forcefully restart it.
    Api,
}

impl Display for QueueKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str_value = match self {
            QueueKind::Control => "Control",
            QueueKind::NetworkIncoming => "NetworkIncoming",
            QueueKind::Network => "Network",
            QueueKind::Regular => "Regular",
            QueueKind::Api => "Api",
        };
        write!(f, "{}", str_value)
    }
}

impl Default for QueueKind {
    fn default() -> Self {
        QueueKind::Regular
    }
}

impl QueueKind {
    /// Returns the weight of a specific queue.
    ///
    /// The weight determines how many events are at most processed from a specific queue during
    /// each event processing round.
    fn weight(self) -> NonZeroUsize {
        NonZeroUsize::new(match self {
            // Note: Control events should be very rare, but we do want to process them right away.
            QueueKind::Control => 32,
            QueueKind::NetworkIncoming => 4,
            QueueKind::Network => 4,
            QueueKind::Regular => 8,
            QueueKind::Api => 16,
        })
        .expect("weight must be positive")
    }

    /// Return weights of all possible `Queue`s.
    pub(crate) fn weights() -> Vec<(Self, NonZeroUsize)> {
        QueueKind::into_enum_iter()
            .map(|q| (q, q.weight()))
            .collect()
    }

    pub(crate) fn metrics_name(&self) -> &str {
        match self {
            QueueKind::Control => "control",
            QueueKind::NetworkIncoming => "network_incoming",
            QueueKind::Network => "network",
            QueueKind::Regular => "regular",
            QueueKind::Api => "api",
        }
    }
}
