use core::cell::RefCell;
use core::fmt::Write;

use critical_section::{CriticalSection, Mutex};
use esp_hal::Blocking;
use esp_hal::uart::UartTx;
use log::{LevelFilter, Log};

pub const LOG_LEVEL_FILTER: LevelFilter = {
    if let Some(string) = option_env!("ESP_LOG") {
        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
    }
};

static ALT_LOGGER_UART: Mutex<RefCell<Option<UartTx<'static, Blocking>>>> =
    Mutex::new(RefCell::new(None));

pub fn with_uart_tx<R>(
    f: impl FnOnce(CriticalSection<'_>, &'_ mut UartTx<'static, Blocking>) -> R,
) -> R {
    critical_section::with(|cs| {
        let mut uart = ALT_LOGGER_UART.borrow(cs).borrow_mut();
        let uart = uart.as_mut().unwrap();

        (f)(cs, uart)
    })
}

struct AlternativeLogger;

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

    #[allow(unused)]
    fn log(&self, record: &log::Record) {
        with_uart_tx(|cs, uart| {
            print_log_record(uart, record);
        })
    }

    fn flush(&self) {}
}

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";

#[cfg(feature = "rtt-log")]
#[allow(unused)]
use ::rtt_target::{rprint as print, rprintln as println};

#[cfg(feature = "alt-log")]
#[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(args).unwrap();
        uart.write_str("\n").unwrap();
        uart.flush().unwrap();
    })
}

fn print_log_record(uart: &mut UartTx<'_, Blocking>, record: &log::Record) {
    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 reset = RESET;
    let args = format_args!(
        "{}{:>5} - {}{}\n",
        color,
        record.level(),
        record.args(),
        reset
    );
    uart.write_fmt(args).unwrap();
    uart.flush().unwrap();
}

#[cfg(feature = "rtt-log")]
use panic_rtt_target as _;

#[cfg(feature = "alt-log")]
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
    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 {}
}

#[cfg(feature = "alt-log")]
pub fn setup_alternative_logging(
    alt_uart: UartTx<'static, Blocking>,
    level_filter: log::LevelFilter,
) {
    critical_section::with(|cs| {
        *ALT_LOGGER_UART.borrow(cs).borrow_mut() = Some(alt_uart);
    });

    unsafe {
        log::set_logger_racy(&AlternativeLogger).unwrap();
        log::set_max_level_racy(level_filter);
    }
}
