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

const LEVEL_TRACE: u8 = 5;
const LEVEL_DEBUG: u8 = 10;
const LEVEL_INFO: u8 = 20;
const LEVEL_WARNING: u8 = 30;
const LEVEL_ERROR: u8 = 40;

static LOG_LEVEL: AtomicU8 = AtomicU8::new(LEVEL_WARNING);

pub fn configure(default_level: u8) {
    let level = match std::env::var("BIOLIB_LOG") {
        Ok(val) if !val.is_empty() => match val.to_uppercase().as_str() {
            "TRACE" => LEVEL_TRACE,
            "DEBUG" => LEVEL_DEBUG,
            "INFO" => LEVEL_INFO,
            "WARNING" | "WARN" => LEVEL_WARNING,
            "ERROR" => LEVEL_ERROR,
            _ => {
                eprintln!("Unknown log level \"{val}\", using default");
                default_level
            }
        },
        _ => default_level,
    };
    LOG_LEVEL.store(level, Ordering::Relaxed);
}

pub fn level() -> u8 {
    LOG_LEVEL.load(Ordering::Relaxed)
}

pub fn is_enabled(msg_level: u8) -> bool {
    msg_level >= LOG_LEVEL.load(Ordering::Relaxed)
}

fn timestamp() -> String {
    let dur = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default();
    let secs = dur.as_secs();
    let days = secs / 86400;
    let time_secs = secs % 86400;
    let hours = time_secs / 3600;
    let minutes = (time_secs % 3600) / 60;
    let seconds = time_secs % 60;
    let millis = dur.subsec_millis();

    // days since 1970-01-01
    let mut y = 1970i64;
    let mut remaining = days as i64;
    loop {
        let year_days = if is_leap(y) { 366 } else { 365 };
        if remaining < year_days {
            break;
        }
        remaining -= year_days;
        y += 1;
    }
    let leap = is_leap(y);
    let month_days: [i64; 12] = [
        31,
        if leap { 29 } else { 28 },
        31,
        30,
        31,
        30,
        31,
        31,
        30,
        31,
        30,
        31,
    ];
    let mut m = 0;
    for &md in &month_days {
        if remaining < md {
            break;
        }
        remaining -= md;
        m += 1;
    }
    let d = remaining + 1;
    format!(
        "{y:04}-{:02}-{d:02} {hours:02}:{minutes:02}:{seconds:02},{millis:03}",
        m + 1
    )
}

fn is_leap(y: i64) -> bool {
    y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
}

fn log_msg(level_name: &str, level_val: u8, msg: &str) {
    if is_enabled(level_val) {
        eprintln!("{} | {} : {}", timestamp(), level_name, msg);
    }
}

pub fn trace(msg: &str) {
    log_msg("TRACE", LEVEL_TRACE, msg);
}

pub fn debug(msg: &str) {
    log_msg("DEBUG", LEVEL_DEBUG, msg);
}

pub fn info(msg: &str) {
    log_msg("INFO", LEVEL_INFO, msg);
}

pub fn warning(msg: &str) {
    log_msg("WARNING", LEVEL_WARNING, msg);
}

pub fn error(msg: &str) {
    log_msg("ERROR", LEVEL_ERROR, msg);
}
