//! Utilities for debug information logging

use alloc::{
    string::{String, ToString},
    sync::Arc,
};
use core::{
    fmt,
    str::FromStr,
    sync::atomic::{AtomicBool, AtomicU32, Ordering},
};
use ring::RingLoggerSink;

use libk_util::OneTimeInit;
use sink::DEBUG_SINKS;
use yggdrasil_abi::error::Error;

use crate::{
    arch::Cpu,
    fs::sysfs::{
        attribute::{StringAttribute, StringAttributeOps},
        object::KObject,
    },
    task::{process::Process, thread::Thread},
};

mod panic;
mod ring;
mod sink;

pub use panic::{panic_log, PanicLoggerSink};
pub use ring::add_kernel_log_file;
pub use sink::{add_early_sink, add_serial_sink, add_sink, disable_early_sinks, DebugSink};

static DEBUG_LOCK: AtomicU32 = AtomicU32::new(u32::MAX);
static MUTE_DEBUG: AtomicBool = AtomicBool::new(false);

pub struct MuteGuard(bool);

impl MuteGuard {
    pub fn acquire() -> Self {
        let muted = MUTE_DEBUG.swap(true, Ordering::Acquire);
        Self(muted)
    }
}

impl Drop for MuteGuard {
    fn drop(&mut self) {
        MUTE_DEBUG.store(self.0, Ordering::Release);
    }
}

struct KernelLoggerSink;

/// Defines the severity of the message
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
    /// Very verbose low-level debugging information
    Trace,
    /// Debugging and verbose information
    Debug,
    /// General information about transitions in the system state
    Info,
    /// Non-critical abnormalities or notices
    Warning,
    /// Failures of non-essential components
    Error,
    /// Irrecoverable errors which result in kernel panic
    Fatal,
}

impl LogLevel {
    fn log_prefix(self) -> &'static str {
        match self {
            LogLevel::Trace => "",
            LogLevel::Debug => "",
            LogLevel::Info => "\x1b[36m\x1b[1m",
            LogLevel::Warning => "\x1b[33m\x1b[1m",
            LogLevel::Error => "\x1b[31m\x1b[1m",
            LogLevel::Fatal => "\x1b[38;2;255;0;0m\x1b[1m",
        }
    }

    fn log_suffix(self) -> &'static str {
        match self {
            LogLevel::Trace => "",
            LogLevel::Debug => "",
            LogLevel::Info => "\x1b[0m",
            LogLevel::Warning => "\x1b[0m",
            LogLevel::Error => "\x1b[0m",
            LogLevel::Fatal => "\x1b[0m",
        }
    }
}

impl From<log::Level> for LogLevel {
    fn from(value: log::Level) -> Self {
        match value {
            log::Level::Trace => Self::Trace,
            log::Level::Debug => Self::Debug,
            log::Level::Info => Self::Info,
            log::Level::Warn => Self::Warning,
            log::Level::Error => Self::Error,
        }
    }
}

fn lock_debug() -> bool {
    let cpu = Cpu::try_local().map_or(0, |cpu| cpu.id());
    loop {
        match DEBUG_LOCK.compare_exchange(u32::MAX, cpu, Ordering::Acquire, Ordering::Relaxed) {
            Ok(_) => return true,
            Err(x) if x == cpu => return false,
            _ => core::hint::spin_loop(),
        }
    }
}

impl log::Log for KernelLoggerSink {
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        metadata.target() != "io"
    }

    fn log(&self, record: &log::Record) {
        if MUTE_DEBUG.load(Ordering::Acquire) {
            return;
        }

        if !self.enabled(record.metadata()) {
            return;
        }

        RingLoggerSink.log(record);

        if !lock_debug() {
            return;
        }
        for sink in DEBUG_SINKS.read().iter() {
            if sink.enabled(record.metadata()) {
                sink.log(record);
            }
        }
        DEBUG_LOCK.store(u32::MAX, Ordering::Release);
    }

    fn flush(&self) {}
}

static LOGGER: KernelLoggerSink = KernelLoggerSink;

fn make_sysfs_sink_object(index: usize) -> Arc<KObject<usize>> {
    struct Level;

    impl StringAttributeOps for Level {
        type Data = usize;
        const NAME: &'static str = "level";
        const LIMIT: usize = 16;
        const WRITEABLE: bool = true;

        fn read(state: &Self::Data) -> Result<String, Error> {
            let sinks = DEBUG_SINKS.read();
            let sink = sinks.get(*state).ok_or(Error::InvalidFile)?;
            Ok(sink.level().to_string())
        }

        fn write(state: &Self::Data, value: &str) -> Result<(), Error> {
            let level = LogLevel::from_str(value)?;
            let mut sinks = DEBUG_SINKS.write();
            let sink = sinks.get_mut(*state).ok_or(Error::InvalidFile)?;
            sink.set_level(level);
            Ok(())
        }
    }

    let object = KObject::new(index);

    object.add_attribute(StringAttribute::from(Level)).ok();

    object
}

/// Print a trace message coming from a process
pub fn program_trace(process: &Process, _thread: &Thread, level: log::Level, message: &str) {
    log::log!(
        target: ":program",
        level,
        "{} ({}) {message}\n",
        process.name,
        process.id,
    );
}

/// Initializes kernel logging output. Prior to this call, no log entries are recorded.
pub fn init_logger() {
    static LOGGER_SET_UP: OneTimeInit<()> = OneTimeInit::new();

    LOGGER_SET_UP.or_init_with(|| {
        log::set_logger(&LOGGER)
            .map(|_| log::set_max_level(log::LevelFilter::Trace))
            .ok();
    });
}

/// Resets the debugging terminal by clearing it
pub fn init() {
    init_logger();
}

/// Print a hex dump into the kernel log with given level
pub fn hex_dump(level: log::Level, bytes: &[u8], start_address: u64, with_chars: bool) {
    if bytes.is_empty() {
        return;
    }

    let line_width = 16;
    let address_width = (start_address + bytes.len() as u64).ilog10() as usize + 1;

    for i in (0..bytes.len()).step_by(line_width) {
        log::log!(target: ":raw", level, "{:0width$X}: ", start_address + i as u64, width = address_width);

        for j in 0..line_width {
            if i + j < bytes.len() {
                log::log!(target: ":raw", level, "{:02X}", bytes[i + j]);
            } else {
                log::log!(target: ":raw", level, "  ");
            }

            if j % 2 != 0 {
                log::log!(target: ":raw", level, " ");
            }
        }

        if with_chars {
            for j in 0..line_width {
                if i + j >= bytes.len() {
                    break;
                }
                let ch = if bytes[i + j].is_ascii_graphic() {
                    bytes[i + j]
                } else {
                    b'.'
                };
                log::log!(target: ":raw", level, "{}", ch as char);
            }
        }

        log::log!(target: ":raw", level, "\n");
    }
}

impl fmt::Display for LogLevel {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let level = match self {
            Self::Trace => "trace",
            Self::Debug => "debug",
            Self::Info => "info",
            Self::Warning => "warn",
            Self::Error => "error",
            Self::Fatal => "fatal",
        };

        f.write_str(level)
    }
}

impl FromStr for LogLevel {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "trace" | "t" => Ok(Self::Trace),
            "debug" | "d" => Ok(Self::Debug),
            "info" | "i" => Ok(Self::Info),
            "warn" | "w" => Ok(Self::Warning),
            "error" | "e" => Ok(Self::Error),
            "fatal" | "f" => Ok(Self::Fatal),
            _ => Err(Error::InvalidArgument),
        }
    }
}
