Documentation
use std::{
    io::{Write, stderr},
    sync::Mutex,
};

use delay_init::delay;

use crate::HttpError;

#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub enum LogLevel {
    None = 0,
    Error = 1,
    Warn = 2,
    Info = 3,
}

impl TryFrom<u8> for LogLevel {
    type Error = HttpError;

    fn try_from(value: u8) -> crate::Result<Self> {
        Ok(match value {
            0 => LogLevel::None,
            1 => LogLevel::Error,
            2 => LogLevel::Warn,
            3 => LogLevel::Info,
            _ => return Err(format!("Invalid log level: {value}").into()),
        })
    }
}

pub fn get_level() -> LogLevel {
    #[allow(clippy::unwrap_used)]
    LOGGER.lock().unwrap().get_level()
}

pub fn set_level(level: LogLevel) {
    #[allow(clippy::unwrap_used)]
    LOGGER.lock().unwrap().set_level(level);
}

pub trait Logger: Send + Sync {
    fn log(&mut self, level: LogLevel, args: core::fmt::Arguments);
    fn set_level(&mut self, level: LogLevel);
    fn get_level(&self) -> LogLevel;
}

struct StdErrLogger(LogLevel);

impl Logger for StdErrLogger {
    fn log(&mut self, level: LogLevel, args: core::fmt::Arguments) {
        if level <= self.0 {
            #[allow(clippy::unwrap_used)]
            stderr().write_fmt(args).unwrap();
        }
    }
    fn set_level(&mut self, level: LogLevel) {
        self.0 = level;
    }
    fn get_level(&self) -> LogLevel {
        self.0
    }
}

#[cfg(not(debug_assertions))]
const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Warn;

#[cfg(debug_assertions)]
const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Info;

delay! {
    static LOGGER : Mutex<Box<dyn Logger>> = Mutex::new(Box::new(StdErrLogger(DEFAULT_LOG_LEVEL)));
}

#[macro_export]
#[doc(hidden)]
macro_rules! __log {
    ($lev:expr , $($arg:tt)*) => {
        {
            #[allow(clippy::used_underscore_items)]
            $crate::log::_log($lev, format_args!($($arg)*))
        }
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __log_info {
    () => ($crate::log::__log!("\n"));
    ($($arg:tt)*) => ($crate::__log!($crate::log::LogLevel::Info, "INFO: {}\n", format_args!($($arg)*)));
}

#[macro_export]
#[doc(hidden)]
macro_rules! __log_warn {
    () => ($crate::log!("\n"));
    ($($arg:tt)*) => ($crate::__log!($crate::log::LogLevel::Warn, "WARNING: {}\n", format_args!($($arg)*)));
}

#[macro_export]
#[doc(hidden)]
macro_rules! __log_error {
    () => ($crate::log!("\n"));
    ($($arg:tt)*) => ($crate::__log!($crate::log::LogLevel::Error, "ERROR: {}\n", format_args!($($arg)*)));
}

pub mod prelude {
    pub use __log_error as log_error;
    pub use __log_info as log_info;
    pub use __log_warn as log_warn;
}

#[doc(hidden)]
pub fn _log(level: LogLevel, args: core::fmt::Arguments) {
    #[allow(clippy::unwrap_used)]
    LOGGER.lock().unwrap().log(level, args);
}