//! 提供可定制实现的 `print!`、`println!` 和 `log::Log`。
//!
//! 教程阅读建议：
//!
//! 1. 先看 [`Console`] trait：理解“真正输出设备”如何被抽象；
//! 2. 再看 `init_console`：理解全局单例如何注入；
//! 3. 最后看 `_print` 与 `Logger::log`：理解格式化输出和日志分级路径。

#![no_std]
#![deny(warnings, missing_docs)]

use core::{
    fmt::{self, Write},
    str::FromStr,
};
use spin::Once;

/// 向用户提供 `log`。
pub extern crate log;

/// 这个接口定义了向控制台“输出”这件事。
pub trait Console: Sync {
    /// 向控制台放置一个字符。
    fn put_char(&self, c: u8);

    /// 向控制台放置一个字符串。
    ///
    /// 如果使用了锁，覆盖这个实现以免反复获取和释放锁。
    #[inline]
    fn put_str(&self, s: &str) {
        for c in s.bytes() {
            self.put_char(c);
        }
    }
}

/// 库找到输出的方法：保存一个对象引用，这是一种单例。
///
/// 各章节会在启动阶段调用 `init_console(&ConsoleImpl)`，
/// 后续所有 `print!`/`println!` 都会走到这个单例。
static CONSOLE: Once<&'static dyn Console> = Once::new();

/// 打印缓冲区大小。
const BUFFER_SIZE: usize = 64;

/// 打印缓冲区，用于收集格式化输出后一次性输出，避免抢占导致输出交错。
///
/// 教学意义：在内核场景中，逐字输出非常容易被抢占打断，
/// 同一行日志可能被多个任务交叉写入；先拼接再一次输出可显著改善可读性。
struct PrintBuffer {
    buffer: [u8; BUFFER_SIZE],
    pos: usize,
}

impl PrintBuffer {
    /// 创建一个新的打印缓冲区。
    const fn new() -> Self {
        Self {
            buffer: [0u8; BUFFER_SIZE],
            pos: 0,
        }
    }

    /// 刷新缓冲区，将内容输出到控制台。
    fn flush(&mut self) {
        if self.pos > 0 {
            if let Some(console) = CONSOLE.get() {
                // SAFETY: buffer 中的内容是从 str 写入的，保证是有效的 UTF-8
                let s = unsafe { core::str::from_utf8_unchecked(&self.buffer[..self.pos]) };
                console.put_str(s);
            }
            self.pos = 0;
        }
    }

    /// 写入字符串到缓冲区，必要时刷新。
    fn write(&mut self, s: &str) {
        let bytes = s.as_bytes();
        let mut offset = 0;

        while offset < bytes.len() {
            let remaining_buffer = BUFFER_SIZE - self.pos;
            let remaining_input = bytes.len() - offset;
            let to_copy = remaining_buffer.min(remaining_input);

            self.buffer[self.pos..self.pos + to_copy]
                .copy_from_slice(&bytes[offset..offset + to_copy]);
            self.pos += to_copy;
            offset += to_copy;

            // 缓冲区满了，刷新
            if self.pos == BUFFER_SIZE {
                self.flush();
            }
        }
    }
}

impl Write for PrintBuffer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.write(s);
        Ok(())
    }
}

/// 用户调用这个函数设置输出的方法。
pub fn init_console(console: &'static dyn Console) {
    // 初始化输出后，也顺带注册 log 框架入口（Logger）。
    CONSOLE.call_once(|| console);
    log::set_logger(&Logger).unwrap();
}

/// 根据环境变量设置日志级别。
pub fn set_log_level(env: Option<&str>) {
    use log::LevelFilter as Lv;
    log::set_max_level(env.and_then(|s| Lv::from_str(s).ok()).unwrap_or(Lv::Trace));
}

/// 打印一些测试信息。
pub fn test_log() {
    println!(
        r"
   ______                       __
  / ____/___  ____  _________  / /__
 / /   / __ \/ __ \/ ___/ __ \/ / _ \
/ /___/ /_/ / / / (__  ) /_/ / /  __/
\____/\____/_/ /_/____/\____/_/\___/
===================================="
    );
    log::trace!("LOG TEST >> Hello, world!");
    log::debug!("LOG TEST >> Hello, world!");
    log::info!("LOG TEST >> Hello, world!");
    log::warn!("LOG TEST >> Hello, world!");
    log::error!("LOG TEST >> Hello, world!");
    println!();
}

/// 打印。
///
/// 给宏用的，用户不会直接调它。
/// 使用栈上缓冲区收集格式化输出，然后一次性输出到控制台。
#[doc(hidden)]
#[inline]
pub fn _print(args: fmt::Arguments) {
    // 注意：这里故意不直接调用底层 put_char，而是经由 PrintBuffer 聚合输出。
    let mut buffer = PrintBuffer::new();
    buffer.write_fmt(args).unwrap();
    buffer.flush();
}

/// 格式化打印。
#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => {
        $crate::_print(core::format_args!($($arg)*));
    }
}

/// 格式化打印并换行。
#[macro_export]
macro_rules! println {
    () => ($crate::print!("\n"));
    ($($arg:tt)*) => {{
        $crate::_print(core::format_args!($($arg)*));
        $crate::println!();
    }}
}

/// 这个 Unit struct 是 `core::fmt` 要求的。
struct Logger;

/// 实现 [`Write`] trait，格式化的基础。
impl Write for Logger {
    #[inline]
    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
        let _ = CONSOLE.get().unwrap().put_str(s);
        Ok(())
    }
}

/// 实现 `log::Log` trait，提供分级日志。
impl log::Log for Logger {
    #[inline]
    fn enabled(&self, _metadata: &log::Metadata) -> bool {
        true
    }

    #[inline]
    fn log(&self, record: &log::Record) {
        use log::Level::*;
        // ANSI 颜色仅用于教学调试体验，不影响日志语义。
        let color_code: u8 = match record.level() {
            Error => 31,
            Warn => 93,
            Info => 34,
            Debug => 32,
            Trace => 90,
        };
        println!(
            "\x1b[{color_code}m[{:>5}] {}\x1b[0m",
            record.level(),
            record.args(),
        );
    }

    fn flush(&self) {}
}
