#![allow(rustdoc::bare_urls, unused_macros)]
#![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))]
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![no_std]

#[cfg(feature = "defmt")]
use defmt as _;
#[cfg(feature = "println")]
use esp_println as _;

const MAX_BACKTRACE_ADDRESSES: usize = 10;

#[cfg(feature = "colors")]
const RESET: &str = "\u{001B}[0m";
#[cfg(feature = "colors")]
const RED: &str = "\u{001B}[31m";

#[cfg(feature = "defmt")]
macro_rules! println {
    ("") => {
        // Do nothing if the string is just a space
    };
    ($($arg:tt)*) => {
        defmt::error!($($arg)*);
    };
}

#[cfg(all(feature = "println", not(feature = "defmt")))]
macro_rules! println {
    ($($arg:tt)*) => {
        esp_println::println!($($arg)*);
    };
}

#[allow(unused, unused_variables)]
fn set_color_code(code: &str) {
    #[cfg(feature = "println")]
    {
        println!("{}", code);
    }
}

#[cfg_attr(target_arch = "riscv32", path = "riscv.rs")]
#[cfg_attr(target_arch = "xtensa", path = "xtensa.rs")]
pub mod arch;

#[cfg(feature = "panic-handler")]
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
    pre_backtrace();

    #[cfg(feature = "colors")]
    set_color_code(RED);

    println!("");
    println!("====================== PANIC ======================");

    #[cfg(not(feature = "defmt"))]
    println!("{}", info);

    #[cfg(feature = "defmt")]
    println!("{}", defmt::Display2Format(info));

    println!("");
    println!("Backtrace:");
    println!("");

    let backtrace = crate::arch::backtrace();
    #[cfg(target_arch = "riscv32")]
    if backtrace.iter().filter(|e| e.is_some()).count() == 0 {
        println!("No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)");
    }
    for addr in backtrace.into_iter().flatten() {
        #[cfg(all(feature = "colors", feature = "println"))]
        println!("{}0x{:x}", RED, addr - crate::arch::RA_OFFSET);

        #[cfg(not(all(feature = "colors", feature = "println")))]
        println!("0x{:x}", addr - crate::arch::RA_OFFSET);
    }

    #[cfg(feature = "colors")]
    set_color_code(RESET);

    #[cfg(feature = "semihosting")]
    semihosting::process::abort();

    #[cfg(not(feature = "semihosting"))]
    halt();
}

#[cfg(all(feature = "exception-handler", target_arch = "xtensa"))]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe fn __user_exception(cause: arch::ExceptionCause, context: arch::Context) {
    pre_backtrace();

    #[cfg(feature = "colors")]
    set_color_code(RED);

    // Unfortunately, a different formatter string is used
    #[cfg(not(feature = "defmt"))]
    esp_println::println!("\n\nException occurred '{}'", cause);

    #[cfg(feature = "defmt")]
    defmt::error!("\n\nException occurred '{}'", cause);

    println!("{:?}", context);

    let backtrace = crate::arch::backtrace_internal(context.A1, 0);
    for e in backtrace {
        if let Some(addr) = e {
            println!("0x{:x}", addr);
        }
    }
    println!("");
    println!("");
    println!("");

    #[cfg(feature = "colors")]
    set_color_code(RESET);

    #[cfg(feature = "semihosting")]
    semihosting::process::abort();

    #[cfg(not(feature = "semihosting"))]
    halt();
}

#[cfg(all(feature = "exception-handler", target_arch = "riscv32"))]
#[export_name = "ExceptionHandler"]
fn exception_handler(context: &arch::TrapFrame) -> ! {
    pre_backtrace();

    let mepc = context.pc;
    let code = context.mcause & 0xff;
    let mtval = context.mtval;

    #[cfg(feature = "colors")]
    set_color_code(RED);

    if code == 14 {
        println!("");
        println!(
            "Stack overflow detected at 0x{:x} called by 0x{:x}",
            mepc, context.ra
        );
        println!("");
    } else {
        let code = match code {
            0 => "Instruction address misaligned",
            1 => "Instruction access fault",
            2 => "Illegal instruction",
            3 => "Breakpoint",
            4 => "Load address misaligned",
            5 => "Load access fault",
            6 => "Store/AMO address misaligned",
            7 => "Store/AMO access fault",
            8 => "Environment call from U-mode",
            9 => "Environment call from S-mode",
            10 => "Reserved",
            11 => "Environment call from M-mode",
            12 => "Instruction page fault",
            13 => "Load page fault",
            14 => "Reserved",
            15 => "Store/AMO page fault",
            _ => "UNKNOWN",
        };

        println!(
            "Exception '{}' mepc=0x{:08x}, mtval=0x{:08x}",
            code, mepc, mtval
        );
        #[cfg(not(feature = "defmt"))]
        println!("{:x?}", context);

        #[cfg(feature = "defmt")]
        println!("{:?}", context);

        let backtrace = crate::arch::backtrace_internal(context.s0 as u32, 0);
        if backtrace.iter().filter(|e| e.is_some()).count() == 0 {
            println!("No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)");
        }
        for addr in backtrace.into_iter().flatten() {
            #[cfg(all(feature = "colors", feature = "println"))]
            println!("{}0x{:x}", RED, addr - crate::arch::RA_OFFSET);

            #[cfg(not(all(feature = "colors", feature = "println")))]
            println!("0x{:x}", addr - crate::arch::RA_OFFSET);
        }
    }

    println!("");
    println!("");
    println!("");

    #[cfg(feature = "colors")]
    set_color_code(RESET);

    #[cfg(feature = "semihosting")]
    semihosting::process::abort();

    #[cfg(not(feature = "semihosting"))]
    halt();
}

// Ensure that the address is in DRAM and that it is 16-byte aligned.
//
// Based loosely on the `esp_stack_ptr_in_dram` function from
// `components/esp_hw_support/include/esp_memory_utils.h` in ESP-IDF.
//
// Address ranges can be found in `components/soc/$CHIP/include/soc/soc.h` as
// `SOC_DRAM_LOW` and `SOC_DRAM_HIGH`.
fn is_valid_ram_address(address: u32) -> bool {
    if (address & 0xF) != 0 {
        return false;
    }

    #[cfg(feature = "esp32")]
    if !(0x3FFA_E000..=0x4000_0000).contains(&address) {
        return false;
    }

    #[cfg(feature = "esp32c2")]
    if !(0x3FCA_0000..=0x3FCE_0000).contains(&address) {
        return false;
    }

    #[cfg(feature = "esp32c3")]
    if !(0x3FC8_0000..=0x3FCE_0000).contains(&address) {
        return false;
    }

    #[cfg(feature = "esp32c6")]
    if !(0x4080_0000..=0x4088_0000).contains(&address) {
        return false;
    }

    #[cfg(feature = "esp32h2")]
    if !(0x4080_0000..=0x4085_0000).contains(&address) {
        return false;
    }

    #[cfg(feature = "esp32p4")]
    if !(0x4FF0_0000..=0x4FFC_0000).contains(&address) {
        return false;
    }

    #[cfg(feature = "esp32s2")]
    if !(0x3FFB_0000..=0x4000_0000).contains(&address) {
        return false;
    }

    #[cfg(feature = "esp32s3")]
    if !(0x3FC8_8000..=0x3FD0_0000).contains(&address) {
        return false;
    }

    true
}

#[cfg(all(
    any(
        not(any(feature = "esp32", feature = "esp32p4", feature = "esp32s3")),
        not(feature = "halt-cores")
    ),
    not(feature = "custom-halt")
))]
#[allow(unused)]
fn halt() -> ! {
    loop {
        continue;
    }
}

#[cfg(feature = "custom-halt")]
fn halt() -> ! {
    extern "Rust" {
        fn custom_halt() -> !;
    }
    unsafe { custom_halt() }
}

// TODO: Enable `halt` function for `esp32p4` feature once implemented
#[cfg(all(any(feature = "esp32", feature = "esp32s3"), feature = "halt-cores"))]
#[allow(unused)]
fn halt() -> ! {
    #[cfg(feature = "esp32")]
    mod registers {
        pub(crate) const OPTIONS0: u32 = 0x3ff48000;
        pub(crate) const SW_CPU_STALL: u32 = 0x3ff480ac;
    }

    #[cfg(feature = "esp32p4")]
    mod registers {
        pub(crate) const SW_CPU_STALL: u32 = 0x50115200;
    }

    #[cfg(feature = "esp32s3")]
    mod registers {
        pub(crate) const OPTIONS0: u32 = 0x60008000;
        pub(crate) const SW_CPU_STALL: u32 = 0x600080bc;
    }

    let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32;

    #[cfg(feature = "esp32p4")]
    unsafe {}

    #[cfg(not(feature = "esp32p4"))]
    unsafe {
        // We need to write the value "0x86" to stall a particular core. The write
        // location is split into two separate bit fields named "c0" and "c1", and the
        // two fields are located in different registers. Each core has its own pair of
        // "c0" and "c1" bit fields.

        let options0 = registers::OPTIONS0 as *mut u32;

        options0.write_volatile(options0.read_volatile() & !(0b1111) | 0b1010);

        sw_cpu_stall.write_volatile(
            sw_cpu_stall.read_volatile() & !(0b111111 << 20) & !(0b111111 << 26)
                | (0x21 << 20)
                | (0x21 << 26),
        );
    }

    loop {}
}

#[cfg(not(feature = "custom-pre-backtrace"))]
#[allow(unused)]
fn pre_backtrace() {}

#[cfg(feature = "custom-pre-backtrace")]
fn pre_backtrace() {
    extern "Rust" {
        fn custom_pre_backtrace();
    }
    unsafe { custom_pre_backtrace() }
}
