// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Configuration is used to configure the per thread behavior
//! of the uring runtime.
//!

use std::time::Duration;

/// Controls when the runtime event loop exits.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum ExitBehavior {
    /// Exit when there are no tasks left (default).
    #[default]
    WhenEmpty,

    /// Exit when the main task (the future passed to `block_on`) completes,
    /// regardless of whether other spawned tasks are still running.
    WhenMainTaskCompletes,
}

/// Configuration options for the io_uring runtime.
///
/// Use the builder pattern to customize runtime behavior.
#[derive(Default)]
pub struct Configuration {
    /// Adapter around a set of trace buffers which the data path threads will
    /// write trace events to at runtime for debugging and analysis purposes
    pub(crate) trace_buffer_manager: Option<Box<dyn crate::TraceConfiguration>>,

    /// Should the event processing loop busy poll and not block in the kernel
    /// if there are pending IOs. Busy polling avoids the cost of a thread
    /// wakeup when an IO completes, but burns a CPU core instead. Offers
    /// a latency vs. CPU utilization tradeoff. If using this feature, keep it
    /// limited to a small handful of threads, which should be pinned to
    /// specific CPU cores. This is a power user feature that should only be
    /// used with careful testing and experimentation.
    pub(crate) busy_poll: BusyPoll,

    /// Controls when the runtime event loop exits.
    pub(crate) exit_behavior: ExitBehavior,
}

/// Controls whether the event loop busy-polls for I/O completion.
///
/// Busy polling can reduce latency at the cost of CPU usage.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum BusyPoll {
    /// Never busy poll; always suspend the thread until I/O completes.
    #[default]
    Never,

    /// Always busy poll; never suspend the thread.
    Always,

    /// Busy poll for the specified duration, then suspend.
    Until(Duration),
}

impl From<Option<Duration>> for BusyPoll {
    fn from(value: Option<Duration>) -> Self {
        match value {
            None => BusyPoll::Never,
            Some(d) => BusyPoll::Until(d),
        }
    }
}

impl Configuration {
    /// Creates a new `Configuration` with default settings.
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets the trace buffer manager for runtime event tracing.
    pub fn set_trace_buffer_manager(
        mut self,
        trace_buffer_manager: Box<dyn crate::TraceConfiguration>,
    ) -> Self {
        self.trace_buffer_manager = Some(trace_buffer_manager);
        self
    }

    /// Sets the busy polling behavior for the event loop.
    pub fn set_busy_poll(mut self, busy_poll: BusyPoll) -> Self {
        self.busy_poll = busy_poll;
        self
    }

    /// Sets the exit behavior for the runtime event loop.
    pub fn set_exit_behavior(mut self, exit_behavior: ExitBehavior) -> Self {
        self.exit_behavior = exit_behavior;
        self
    }
}

#[cfg(test)]
mod test {
    use crate::configuration::{BusyPoll, ExitBehavior};

    #[test]
    fn configuration_default_test() {
        let config = crate::Configuration::new();
        assert_eq!(config.busy_poll, BusyPoll::Never);
        assert_eq!(config.exit_behavior, ExitBehavior::WhenEmpty);
        assert!(config.trace_buffer_manager.is_none());
    }

    #[test]
    fn configuration_set_exit_behavior() {
        let config =
            crate::Configuration::new().set_exit_behavior(ExitBehavior::WhenMainTaskCompletes);
        assert_eq!(config.exit_behavior, ExitBehavior::WhenMainTaskCompletes);
    }
}
