use core::{fmt, sync::atomic::AtomicUsize};

use crate::io::{
    managed::{stderr, FILE},
    Write,
};

struct PanicPrinter {
    buffer: [u8; 1024],
    pos: usize,

    print: Option<&'static mut FILE>,
}

impl fmt::Write for PanicPrinter {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for ch in s.bytes() {
            self.push(ch);
        }
        Ok(())
    }
}

impl PanicPrinter {
    pub fn new() -> Self {
        let print = unsafe { stderr.as_mut() };

        Self {
            buffer: [0; 1024],
            pos: 0,
            print,
        }
    }

    fn flush(&mut self) {
        let text = &self.buffer[..self.pos];
        if !text.is_empty() {
            // Print to debug trace
            if let Ok(text) = core::str::from_utf8(text) {
                log::error!("{text}");
            }

            // Print to stderr
            if let Some(print) = self.print.as_mut() {
                print.write_all(text).ok();
                print.write_all(b"\n").ok();
            }
        }

        self.pos = 0;
    }

    pub fn push(&mut self, ch: u8) {
        if self.pos >= self.buffer.len() {
            return;
        }

        if ch == b'\n' {
            self.flush();
        } else {
            self.buffer[self.pos] = ch;
            self.pos += 1;
        }
    }
}

#[thread_local]
static PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);

#[cfg(any(not(test), rust_analyzer))]
#[panic_handler]
fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
    use core::{fmt::Write, sync::atomic::Ordering};

    use crate::{error::EResult, process, thread::Thread};

    match PANIC_COUNT.fetch_add(1, Ordering::Relaxed) {
        0 => {
            let mut printer = PanicPrinter::new();

            writeln!(printer, "!!! ygglibc panic !!!").ok();
            if let Some(location) = pi.location() {
                writeln!(printer, "{}:{}:", location.file(), location.line()).ok();
            }
            writeln!(printer, "    {}", pi.message()).ok();

            let pthread_self = Thread::this();
            if let EResult::Ok(this) = pthread_self {
                if this == 0 {
                    writeln!(printer, "* Main thread panicked *").ok();
                } else {
                    writeln!(printer, "* Thread {this} panicked*").ok();
                }
            }

            printer.flush();
        }
        1 => {
            log::error!("{pi:?}");
            log::error!("!!! ygglibc panicked while panicking !!!");
        }
        _ => {}
    }

    process::abort();
}
