use std::sync::{
    Once,
    atomic::{AtomicU8, Ordering},
};

pub use log;
use log::{Level, LevelFilter, Metadata, Record};

pub const ADEBUG_ENV: &str = "ADEBUG";
pub const DEBUG_ENV: &str = "DEBUG";

static LOGGER: StdoutLogger = StdoutLogger;
static INIT_LOGGER: Once = Once::new();
static INIT_STATE: AtomicU8 = AtomicU8::new(0);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InitStatus {
    Installed,
    AlreadyInstalled,
    ExternalLoggerPresent,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Verbosity {
    Off,
    Info,
    Debug,
    Trace,
}

struct StdoutLogger;

impl log::Log for StdoutLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= log::max_level()
    }

    fn log(&self, record: &Record) {
        if !self.enabled(record.metadata()) {
            return;
        }
        println!("[{} {}] {}", record.level(), record.target(), record.args());
    }

    fn flush(&self) {}
}

pub fn verbosity() -> usize {
    std::env::var(ADEBUG_ENV)
        .or_else(|_| std::env::var(DEBUG_ENV))
        .ok()
        .and_then(|v| v.parse::<usize>().ok())
        .unwrap_or_default()
}

pub fn verbosity_level() -> Verbosity {
    match verbosity() {
        0 => Verbosity::Off,
        1 => Verbosity::Info,
        2 => Verbosity::Debug,
        _ => Verbosity::Trace,
    }
}

pub fn init_from_env() -> InitStatus {
    let mut installed_now = false;
    INIT_LOGGER.call_once(|| {
        if log::set_logger(&LOGGER).is_ok() {
            INIT_STATE.store(1, Ordering::Relaxed);
            installed_now = true;
        } else {
            INIT_STATE.store(2, Ordering::Relaxed);
        }
    });

    match INIT_STATE.load(Ordering::Relaxed) {
        1 => {
            log::set_max_level(level_filter_from_verbosity(verbosity_level()));
            if installed_now {
                InitStatus::Installed
            } else {
                InitStatus::AlreadyInstalled
            }
        }
        _ => InitStatus::ExternalLoggerPresent,
    }
}

fn level_filter_from_verbosity(v: Verbosity) -> LevelFilter {
    match v {
        Verbosity::Off => LevelFilter::Off,
        Verbosity::Info => LevelFilter::Info,
        Verbosity::Debug => LevelFilter::Debug,
        Verbosity::Trace => LevelFilter::Trace,
    }
}

pub fn enabled(level: Level) -> bool {
    let _ = init_from_env();
    log::max_level() >= level.to_level_filter()
}
