#![no_std]

extern crate alloc;

use alloc::string::{String, ToString};
use core::fmt::Write;

use lazy_static::lazy_static;
use tracing_core::Subscriber;
use tracing_subscriber::Layer;
use uart_16550::SerialPort;
use x86_64::instructions::interrupts;

lazy_static! {
    pub static ref SERIAL1: spin::Mutex<SerialPort> = {
        let mut serial_port = unsafe { SerialPort::new(0x3F8) };
        serial_port.init();

        spin::Mutex::new(serial_port)
    };
}

/// Tracing subscriber that does nothing
// TODO: figure out how to use standard library so can replace with actual subscribers
pub struct MockSubscriber;

/// A layer that prints all events to the serial console
pub struct SerialLayer;

/// Simple visitor that formats any fields attached to spans
struct PrintVisitor(String);

impl<S> Layer<S> for SerialLayer
where
    S: tracing_core::Subscriber,
{
    fn on_event(
        &self,
        event: &tracing_core::Event<'_>,
        _ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        let metadata = event.metadata();
        let mut msg = metadata.level().to_string();

        // Attach any location context
        if let Some(location) = metadata.file().or(metadata.module_path()) {
            msg.push(' ');
            msg.push_str(location);

            if let Some(line) = metadata.line() {
                msg.push(':');
                msg.push_str(&line.to_string());
            }
        }

        let mut visitor = PrintVisitor(String::new());
        event.record(&mut visitor);

        msg.push_str(&visitor.0);
        msg.push('\n');

        interrupts::without_interrupts(|| {
            SERIAL1
                .lock()
                .write_str(&msg)
                .expect("Printing to serial failed");
        });
    }
}

impl tracing::field::Visit for PrintVisitor {
    fn record_debug(&mut self, field: &tracing_core::Field, value: &dyn core::fmt::Debug) {
        self.0.push(' ');

        if field.name() != "message" {
            self.0.push_str(field.name());
            self.0.push('=');
        }

        write!(self.0, "{:#?}", value).unwrap();
    }
}

impl Subscriber for MockSubscriber {
    fn enabled(&self, _metadata: &tracing_core::Metadata<'_>) -> bool {
        true
    }

    fn new_span(&self, _span: &tracing_core::span::Attributes<'_>) -> tracing_core::span::Id {
        tracing_core::span::Id::from_u64(0)
    }

    fn record(&self, _span: &tracing_core::span::Id, _values: &tracing_core::span::Record<'_>) {
        ()
    }

    fn record_follows_from(
        &self,
        _span: &tracing_core::span::Id,
        _follows: &tracing_core::span::Id,
    ) {
        ()
    }

    fn event(&self, _event: &tracing_core::Event<'_>) {
        ()
    }

    fn enter(&self, _span: &tracing_core::span::Id) {
        ()
    }

    fn exit(&self, _span: &tracing_core::span::Id) {
        ()
    }
}