use std::path::PathBuf;

/// This enum represents the whole logging configuration,
/// including all logging destinations and their respective log level filters.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LoggingConfig {
    /// List of destinations to log to.
    /// If the list of destinations is empty, logging is disabled.
    destinations: Vec<LogDestinationConfig>,
}

impl LoggingConfig {
    /// Create a new [LoggingConfig] with the given destinations.
    ///
    /// If the list of destinations is empty, logging is disabled.
    pub fn new(destinations: Vec<LogDestinationConfig>) -> Self {
        Self { destinations }
    }

    /// Create a [LoggingConfig] that disables logging.
    pub fn disabled() -> Self {
        Self {
            destinations: vec![],
        }
    }

    /// Get the list of destinations to log to.
    pub fn destinations(&self) -> &[LogDestinationConfig] {
        &self.destinations
    }
}

/// Configuration for a log destination, containing the destination and the log level.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LogDestinationConfig {
    /// The destination to log to.
    pub destination: LogDestination,

    /// Only log messages at this level or higher to this destination.
    ///
    /// If `None`, the default level is used.
    pub level: Option<log::LevelFilter>,
}

/// A destination that can be logged to, e.g. a file or the system log.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LogDestination {
    /// Log to stderr
    Stderr,

    /// Log to the file at the given path
    File(PathBuf),

    /// Log to the system log
    Syslog,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn disabled() {
        let config = LoggingConfig::disabled();
        assert!(config.destinations().is_empty());
    }

    #[test]
    fn new() {
        let config = LoggingConfig::new(vec![
            LogDestinationConfig {
                destination: LogDestination::Stderr,
                level: Some(log::LevelFilter::Info),
            },
            LogDestinationConfig {
                destination: LogDestination::File(PathBuf::from("/tmp/logfile")),
                level: Some(log::LevelFilter::Debug),
            },
        ]);
        assert_eq!(
            vec![
                LogDestinationConfig {
                    destination: LogDestination::Stderr,
                    level: Some(log::LevelFilter::Info),
                },
                LogDestinationConfig {
                    destination: LogDestination::File(PathBuf::from("/tmp/logfile")),
                    level: Some(log::LevelFilter::Debug),
                },
            ],
            config.destinations()
        );
    }

    #[cfg(feature = "serde")]
    #[test]
    fn logging_config_round_trips_through_postcard() {
        let cfg = LoggingConfig::new(vec![
            LogDestinationConfig {
                destination: LogDestination::Syslog,
                level: Some(log::LevelFilter::Info),
            },
            LogDestinationConfig {
                destination: LogDestination::File(PathBuf::from("/tmp/x.log")),
                level: None,
            },
            LogDestinationConfig {
                destination: LogDestination::Stderr,
                level: Some(log::LevelFilter::Warn),
            },
        ]);
        let bytes = postcard::to_stdvec(&cfg).unwrap();
        let restored: LoggingConfig = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(cfg, restored);
    }
}
