use core::{
    fmt,
    sync::atomic::{AtomicBool, Ordering},
};

use alloc::{format, sync::Arc};
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, StaticVector};
use yggdrasil_abi::error::Error;

use crate::{arch::Cpu, config, fs::sysfs, time::monotonic_time};

use super::{make_sysfs_sink_object, LogLevel};

/// Generic interface for debug output
pub trait DebugSink: Sync {
    /// Sends a single byte to the output
    fn putc(&self, c: u8) -> Result<(), Error>;

    /// Sends a string of bytes to the output
    fn puts(&self, s: &str) -> Result<(), Error> {
        for &byte in s.as_bytes() {
            self.putc(byte)?;
        }
        Ok(())
    }

    /// Returns `true` if the device supports vt100-like control sequences
    fn supports_control_sequences(&self) -> bool {
        false
    }
}

#[derive(Clone)]
pub enum DebugSinkWrapper {
    Arc(LogLevel, Arc<dyn DebugSink>),
    Static(LogLevel, &'static dyn DebugSink),
}

struct SinkWriter<'a> {
    sink: &'a dyn DebugSink,
}

unsafe impl Send for DebugSinkWrapper {}
unsafe impl Sync for DebugSinkWrapper {}

impl fmt::Write for SinkWriter<'_> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.sink.puts(s).map_err(|_| fmt::Error)
    }
}

impl DebugSinkWrapper {
    #[inline]
    pub fn sink(&self) -> &dyn DebugSink {
        match self {
            Self::Arc(_, arc) => arc.as_ref(),
            Self::Static(_, sink) => *sink,
        }
    }

    #[inline]
    pub fn level(&self) -> LogLevel {
        match self {
            Self::Arc(level, _) => *level,
            Self::Static(level, _) => *level,
        }
    }

    pub fn set_level(&mut self, target: LogLevel) {
        match self {
            Self::Arc(level, _) => *level = target,
            Self::Static(level, _) => *level = target,
        }
    }
}

impl log::Log for DebugSinkWrapper {
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        if LogLevel::from(metadata.level()) < self.level() {
            return false;
        }
        if metadata.target() == "program" && config::get().debug.disable_program_trace {
            return false;
        }

        true
    }

    fn log(&self, record: &log::Record) {
        use core::fmt::Write;

        let level = LogLevel::from(record.level());
        let sink = self.sink();
        let cpu = Cpu::try_local().map(|c| c.id());
        let line = record.line().unwrap_or(0);
        let args = record.args();

        let (prefix, suffix) = if sink.supports_control_sequences() {
            (level.log_prefix(), level.log_suffix())
        } else {
            ("", "")
        };

        let mut writer = SinkWriter { sink };
        let now = monotonic_time();
        let s = now.seconds();

        match record.target() {
            ":program" => {
                write!(writer, "{prefix}{s:06}:").ok();
                if let Some(cpu) = cpu {
                    write!(writer, "{cpu}:").ok();
                } else {
                    write!(writer, "?:").ok();
                }
                write!(writer, "ptrace:{args}").ok();
            }
            ":raw" => {
                write!(writer, "{prefix}{args}{suffix}").ok();
            }
            target => {
                write!(writer, "{prefix}{s:06}:").ok();
                if let Some(cpu) = cpu {
                    write!(writer, "{cpu}:").ok();
                } else {
                    write!(writer, "?:").ok();
                }
                writeln!(writer, "{target}:{line}: {args}{suffix}").ok();
            }
        }
    }

    fn flush(&self) {}
}

pub(super) const MAX_DEBUG_SINKS: usize = 8;

pub(super) static DEBUG_SINKS: IrqSafeRwLock<StaticVector<DebugSinkWrapper, MAX_DEBUG_SINKS>> =
    IrqSafeRwLock::new(StaticVector::new());

static SERIAL_SINK_SET_UP: AtomicBool = AtomicBool::new(false);

/// Adds a debugging output sink
pub fn add_sink(sink: Arc<dyn DebugSink>, level: LogLevel) {
    add_sink_inner(DebugSinkWrapper::Arc(level, sink.clone()));
}

/// Adds a serial debug output sink. Only one will be added, rest are ignored.
pub fn add_serial_sink(sink: Arc<dyn DebugSink>, level: LogLevel) {
    if SERIAL_SINK_SET_UP.swap(true, Ordering::Acquire) {
        return;
    }

    add_sink(sink, level);
}

/// Adds an early output sink, which is disabled by a call to [disable_early_sinks].
pub fn add_early_sink(sink: &'static dyn DebugSink, level: LogLevel) {
    add_sink_inner(DebugSinkWrapper::Static(level, sink));
}

/// Disables eraly output sinks
pub fn disable_early_sinks() {
    let mut sinks = DEBUG_SINKS.write();
    // TODO proper sink storage/manipulation
    for sink in sinks.iter_mut() {
        if let DebugSinkWrapper::Static(level, _) = sink {
            *level = LogLevel::Fatal;
        }
    }
}

fn add_sink_inner(sink: DebugSinkWrapper) {
    let index = {
        let mut sinks = DEBUG_SINKS.write();
        let index = sinks.len();
        sinks.push(sink);
        index
    };

    if let Some(debug) = sysfs::debug() {
        debug
            .add_object(format!("{index}"), make_sysfs_sink_object(index))
            .ok();
    }
}
