use core::fmt::Arguments;

use log::LevelFilter;

// TODO: Replace with `log`'s `STATIC_MAX_LEVEL` set via crate features.
pub const LOG_LEVEL_FILTER: LevelFilter = {
    if let Some(string) = option_env!("ESP_LOG") {
        if string.eq_ignore_ascii_case("OFF") {
            LevelFilter::Off
        } else if string.eq_ignore_ascii_case("ERROR") {
            LevelFilter::Error
        } else if string.eq_ignore_ascii_case("WARN") {
            LevelFilter::Warn
        } else if string.eq_ignore_ascii_case("INFO") {
            LevelFilter::Info
        } else if string.eq_ignore_ascii_case("DEBUG") {
            LevelFilter::Debug
        } else if string.eq_ignore_ascii_case("TRACE") {
            LevelFilter::Trace
        } else {
            panic!(
                "Unknown `ESP_LOG` value. Only `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`, or `OFF` may be used."
            );
        }
    } else {
        LevelFilter::Off
    }
};

const RESET: &str = "\u{001B}[0m";
const RED: &str = "\u{001B}[31m";
const GREEN: &str = "\u{001B}[32m";
const YELLOW: &str = "\u{001B}[33m";
const BLUE: &str = "\u{001B}[34m";
const CYAN: &str = "\u{001B}[35m";

fn with_formatted_log_record<R>(
    record: &log::Record,
    callback: impl FnOnce(Arguments<'_>) -> R,
) -> R {
    let color = match record.level() {
        log::Level::Error => RED,
        log::Level::Warn => YELLOW,
        log::Level::Info => GREEN,
        log::Level::Debug => BLUE,
        log::Level::Trace => CYAN,
    };
    let args = format_args!(
        "{}{:>5} - {}{}\n",
        color,
        record.level(),
        record.args(),
        RESET
    );
    (callback)(args)
}

/// The default USB logger.
#[cfg(feature = "usb-log")]
pub mod usb {
    use super::*;

    pub fn setup_logging() {
        esp_println::logger::init_logger(LOG_LEVEL_FILTER);
        log::info!("Logger initialized!");
    }
}

/// Alternative logger via UART.
#[cfg(feature = "alt-log")]
#[macro_use]
pub mod uart {
    use super::*;

    use core::{cell::RefCell, fmt::Write};
    use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
    use esp_hal::{Blocking, uart::UartTx};
    use log::{Log, info};

    #[cfg(feature = "racy-logging")]
    static ALT_LOGGER_UART: embassy_sync::mutex::Mutex<
        CriticalSectionRawMutex,
        RefCell<Option<UartTx<'static, Blocking>>>,
    > = embassy_sync::mutex::Mutex::new(RefCell::new(None));

    #[cfg(not(feature = "racy-logging"))]
    static ALT_LOGGER_UART: embassy_sync::blocking_mutex::Mutex<
        CriticalSectionRawMutex,
        RefCell<Option<UartTx<'static, Blocking>>>,
    > = embassy_sync::blocking_mutex::Mutex::new(RefCell::new(None));

    #[cfg(feature = "racy-logging")]
    pub fn with_uart_tx<R>(f: impl FnOnce(&'_ mut UartTx<'static, Blocking>) -> R) -> R {
        use crate::util::MutexExt;

        // Safety:
        // * The guard is not held across yield points.
        // * **CARE MUST BE TAKEN NOT TO INVOKE THIS FUNCTION FROM AN INTERRUPT HANDLER.**
        let uart = unsafe { ALT_LOGGER_UART.lock_blocking() };
        let mut uart = uart.borrow_mut();
        let uart = uart.as_mut().unwrap();

        (f)(uart)
    }

    #[cfg(not(feature = "racy-logging"))]
    pub fn with_uart_tx<R>(f: impl FnOnce(&'_ mut UartTx<'static, Blocking>) -> R) -> R {
        ALT_LOGGER_UART.lock(|uart| {
            let mut uart = uart.borrow_mut();
            let uart = uart.as_mut().unwrap();

            (f)(uart)
        })
    }

    #[allow(unused)]
    macro_rules! println {
        () => {{
            do_print(Default::default());
        }};

        ($($arg:tt)*) => {{
            do_print(::core::format_args!($($arg)*));
        }};
    }

    #[allow(unused)]
    fn do_print(args: core::fmt::Arguments<'_>) {
        with_uart_tx(|uart| {
            uart.write_fmt(format_args!("{}\n", args)).unwrap();
            uart.flush().unwrap();
        })
    }

    struct UartLogger;

    impl Log for UartLogger {
        #[allow(unused)]
        fn enabled(&self, _: &log::Metadata) -> bool {
            // Filtered by `log` already
            true
        }

        #[allow(unused)]
        fn log(&self, record: &log::Record) {
            with_uart_tx(|uart| {
                with_formatted_log_record(record, |args| uart.write_fmt(args)).unwrap();
                uart.flush().unwrap();
            })
        }

        fn flush(&self) {}
    }

    #[panic_handler]
    fn panic_handler(info: &core::panic::PanicInfo) -> ! {
        use super::{RED, RESET};
        use esp_backtrace::Backtrace;

        println!("{RED}");
        println!("=============== CUSTOM PANIC HANDLER ==============");
        println!("{info}{RESET}");
        println!("");
        println!("Backtrace:");
        println!("");

        let backtrace = Backtrace::capture();

        for frame in backtrace.frames() {
            println!("0x{:x}", frame.program_counter());
        }

        loop {}
    }

    pub fn setup_logging(uart_tx: UartTx<'static, Blocking>) {
        {
            #[cfg(feature = "racy-logging")]
            {
                use crate::util::MutexExt;

                // Safety:
                // * The guard is not held across yield points.
                // * This function is not invoked from an interrupt handler.
                let uart = unsafe { ALT_LOGGER_UART.lock_blocking() };
                *uart.borrow_mut() = Some(uart_tx);
            }

            #[cfg(not(feature = "racy-logging"))]
            ALT_LOGGER_UART.lock(move |uart| {
                *uart.borrow_mut() = Some(uart_tx);
            });
        }

        unsafe {
            log::set_logger_racy(&UartLogger).unwrap();
            log::set_max_level_racy(LOG_LEVEL_FILTER);
        }

        info!("Logger initialized!");
    }
}

/// Logging via RTT for probe-rs.
#[cfg(feature = "rtt-log")]
pub mod rtt {
    use super::*;
    #[allow(unused)]
    pub use ::rtt_target::{rprint as print, rprintln as println};
    use panic_rtt_target as _; // Use the RTT panic handler.
    use rtt_target::ChannelMode;

    pub fn setup_logging() {
        rtt_target::rtt_init_log!(LOG_LEVEL_FILTER, ChannelMode::BlockIfFull);
        log::info!("Logger initialized!");
    }
}

// #[macro_export]
// macro_rules! dbg {
//     () => {
//         $crate::logging::implementation::println!("[{}:{}:{}]", $crate::file!(), $crate::line!(), $crate::column!())
//     };
//     ($val:expr $(,)?) => {
//         match $val {
//             tmp => {
//                 $crate::logging::uart::println!("[{}:{}:{}] {} = {:#?}",
//                     file!(),
//                     line!(),
//                     column!(),
//                     stringify!($val),
//                     // The `&T: Debug` check happens here (not in the format literal desugaring)
//                     // to avoid format literal related messages and suggestions.
//                     &&tmp as &dyn ::core::fmt::Debug,
//                 );
//                 tmp
//             }
//         }
//     };
//     ($($val:expr),+ $(,)?) => {
//         ($($crate::dbg!($val)),+,)
//     };
// }

// #[cfg(feature = "alt-log")]
// pub use uart as implementation;

// #[cfg(feature = "rtt-log")]
// pub use rtt as implementation;
