//! # Debug Assistant (ASSIST_DEBUG)
//!
//! ## Overview
//! Debug Assistant is an auxiliary module that features a set of functions to
//! help locate bugs and issues during software debugging. It includes
//! capabilities such as monitoring stack pointer (SP), monitoring memory
//! regions, and handling interrupts related to debugging.
//!
//!
//! ## Configuration
//! While all the targets support program counter (PC) logging it's API is not
//! exposed here. Instead the ROM bootloader will always enable it and print the
//! last seen PC (e.g. _Saved PC:0x42002ff2_). Make sure the reset was triggered
//! by a TIMG watchdog. Not an RTC or SWD watchdog.
//!
//! ## Examples
//! Visit the [Debug Assist] example for an example of using the Debug
//! Assistant.
//!
//! [Debug Assist]: https://github.com/esp-rs/esp-hal/blob/main/examples/src/bin/debug_assist.rs
//!
//! ## Implementation State
//! - Bus write access logging is not available via this API
//! - This driver has only blocking API

use crate::{
    interrupt::{InterruptConfigurable, InterruptHandler},
    peripheral::{Peripheral, PeripheralRef},
    peripherals::{Interrupt, ASSIST_DEBUG},
};

/// The debug assist driver instance.
pub struct DebugAssist<'d> {
    debug_assist: PeripheralRef<'d, ASSIST_DEBUG>,
}

impl<'d> DebugAssist<'d> {
    /// Create a new instance in [crate::Blocking] mode.
    pub fn new(debug_assist: impl Peripheral<P = ASSIST_DEBUG> + 'd) -> Self {
        crate::into_ref!(debug_assist);

        // NOTE: We should enable the debug assist, however, it's always enabled in ROM
        //       code already.

        DebugAssist { debug_assist }
    }
}

impl crate::private::Sealed for DebugAssist<'_> {}

impl InterruptConfigurable for DebugAssist<'_> {
    fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
        for core in crate::Cpu::other() {
            crate::interrupt::disable(core, Interrupt::ASSIST_DEBUG);
        }
        unsafe { crate::interrupt::bind_interrupt(Interrupt::ASSIST_DEBUG, handler.handler()) };
        unwrap!(crate::interrupt::enable(
            Interrupt::ASSIST_DEBUG,
            handler.priority()
        ));
    }
}

#[cfg(assist_debug_sp_monitor)]
impl DebugAssist<'_> {
    /// Enable SP monitoring on main core. When the SP exceeds the
    /// `lower_bound` or `upper_bound` threshold, the module will record the PC
    /// pointer and generate an interrupt.
    pub fn enable_sp_monitor(&mut self, lower_bound: u32, upper_bound: u32) {
        self.debug_assist
            .core_0_sp_min()
            .write(|w| unsafe { w.core_0_sp_min().bits(lower_bound) });

        self.debug_assist
            .core_0_sp_max()
            .write(|w| unsafe { w.core_0_sp_max().bits(upper_bound) });

        self.debug_assist.core_0_montr_ena().modify(|_, w| {
            w.core_0_sp_spill_min_ena()
                .set_bit()
                .core_0_sp_spill_max_ena()
                .set_bit()
        });

        self.clear_sp_monitor_interrupt();

        self.debug_assist.core_0_intr_ena().modify(|_, w| {
            w.core_0_sp_spill_max_intr_ena()
                .set_bit()
                .core_0_sp_spill_min_intr_ena()
                .set_bit()
        });
    }

    /// Disable SP monitoring on main core.
    pub fn disable_sp_monitor(&mut self) {
        self.debug_assist.core_0_intr_ena().modify(|_, w| {
            w.core_0_sp_spill_max_intr_ena()
                .clear_bit()
                .core_0_sp_spill_min_intr_ena()
                .clear_bit()
        });

        self.debug_assist.core_0_montr_ena().modify(|_, w| {
            w.core_0_sp_spill_min_ena()
                .clear_bit()
                .core_0_sp_spill_max_ena()
                .clear_bit()
        });
    }

    /// Clear SP monitoring interrupt on main core.
    pub fn clear_sp_monitor_interrupt(&mut self) {
        self.debug_assist.core_0_intr_clr().write(|w| {
            w.core_0_sp_spill_max_clr()
                .set_bit()
                .core_0_sp_spill_min_clr()
                .set_bit()
        });
    }

    /// Check, if SP monitoring interrupt is set on main core.
    pub fn is_sp_monitor_interrupt_set(&self) -> bool {
        self.debug_assist
            .core_0_intr_raw()
            .read()
            .core_0_sp_spill_max_raw()
            .bit_is_set()
            || self
                .debug_assist
                .core_0_intr_raw()
                .read()
                .core_0_sp_spill_min_raw()
                .bit_is_set()
    }

    /// Get SP monitoring PC value on main core.
    pub fn sp_monitor_pc(&self) -> u32 {
        self.debug_assist
            .core_0_sp_pc()
            .read()
            .core_0_sp_pc()
            .bits()
    }
}

#[cfg(all(assist_debug_sp_monitor, multi_core))]
impl<'d> DebugAssist<'d> {
    /// Enable SP monitoring on secondary core. When the SP exceeds the
    /// `lower_bound` or `upper_bound` threshold, the module will record the PC
    /// pointer and generate an interrupt.
    pub fn enable_core1_sp_monitor(&mut self, lower_bound: u32, upper_bound: u32) {
        self.debug_assist
            .core_1_sp_min
            .write(|w| w.core_1_sp_min().bits(lower_bound));

        self.debug_assist
            .core_1_sp_max
            .write(|w| w.core_1_sp_max().bits(upper_bound));

        self.debug_assist.core_1_montr_ena.modify(|_, w| {
            w.core_1_sp_spill_min_ena()
                .set_bit()
                .core_1_sp_spill_max_ena()
                .set_bit()
        });

        self.clear_core1_sp_monitor_interrupt();

        self.debug_assist.core_1_intr_ena.modify(|_, w| {
            w.core_1_sp_spill_max_intr_ena()
                .set_bit()
                .core_1_sp_spill_min_intr_ena()
                .set_bit()
        });
    }

    /// Disable SP monitoring on secondary core.
    pub fn disable_core1_sp_monitor(&mut self) {
        self.debug_assist.core_1_intr_ena.modify(|_, w| {
            w.core_1_sp_spill_max_intr_ena()
                .clear_bit()
                .core_1_sp_spill_min_intr_ena()
                .clear_bit()
        });

        self.debug_assist.core_1_montr_ena.modify(|_, w| {
            w.core_1_sp_spill_min_ena()
                .clear_bit()
                .core_1_sp_spill_max_ena()
                .clear_bit()
        });
    }

    /// Clear SP monitoring interrupt on secondary core.
    pub fn clear_core1_sp_monitor_interrupt(&mut self) {
        self.debug_assist.core_1_intr_clr.write(|w| {
            w.core_1_sp_spill_max_clr()
                .set_bit()
                .core_1_sp_spill_min_clr()
                .set_bit()
        });
    }

    /// Check, if SP monitoring interrupt is set on secondary core.
    pub fn is_core1_sp_monitor_interrupt_set(&self) -> bool {
        self.debug_assist
            .core_1_intr_raw
            .read()
            .core_1_sp_spill_max_raw()
            .bit_is_set()
            || self
                .debug_assist
                .core_1_intr_raw
                .read()
                .core_1_sp_spill_min_raw()
                .bit_is_set()
    }

    /// Get SP monitoring PC value on secondary core.
    pub fn core1_sp_monitor_pc(&self) -> u32 {
        self.debug_assist.core_1_sp_pc.read().core_1_sp_pc().bits()
    }
}

#[cfg(assist_debug_region_monitor)]
impl DebugAssist<'_> {
    /// Enable region monitoring of read/write performed by the main CPU in a
    /// certain memory region0. Whenever the bus reads or writes in the
    /// specified memory region, an interrupt will be triggered. Two memory
    /// regions (region0, region1) can be monitored at the same time.
    pub fn enable_region0_monitor(
        &mut self,
        lower_bound: u32,
        upper_bound: u32,
        reads: bool,
        writes: bool,
    ) {
        self.debug_assist
            .core_0_area_dram0_0_min()
            .write(|w| unsafe { w.core_0_area_dram0_0_min().bits(lower_bound) });

        self.debug_assist
            .core_0_area_dram0_0_max()
            .write(|w| unsafe { w.core_0_area_dram0_0_max().bits(upper_bound) });

        self.debug_assist.core_0_montr_ena().modify(|_, w| {
            w.core_0_area_dram0_0_rd_ena()
                .bit(reads)
                .core_0_area_dram0_0_wr_ena()
                .bit(writes)
        });

        self.clear_region0_monitor_interrupt();

        self.debug_assist.core_0_intr_ena().modify(|_, w| {
            w.core_0_area_dram0_0_rd_intr_ena()
                .set_bit()
                .core_0_area_dram0_0_wr_intr_ena()
                .set_bit()
        });
    }

    /// Disable region0 monitoring on main core.
    pub fn disable_region0_monitor(&mut self) {
        self.debug_assist.core_0_intr_ena().modify(|_, w| {
            w.core_0_area_dram0_0_rd_intr_ena()
                .clear_bit()
                .core_0_area_dram0_0_wr_intr_ena()
                .clear_bit()
        });

        self.debug_assist.core_0_montr_ena().modify(|_, w| {
            w.core_0_area_dram0_0_rd_ena()
                .clear_bit()
                .core_0_area_dram0_0_wr_ena()
                .clear_bit()
        });
    }

    /// Clear region0 monitoring interrupt on main core.
    pub fn clear_region0_monitor_interrupt(&mut self) {
        self.debug_assist.core_0_intr_clr().write(|w| {
            w.core_0_area_dram0_0_rd_clr()
                .set_bit()
                .core_0_area_dram0_0_wr_clr()
                .set_bit()
        });
    }

    /// Check, if region0 monitoring interrupt is set on main core.
    pub fn is_region0_monitor_interrupt_set(&self) -> bool {
        self.debug_assist
            .core_0_intr_raw()
            .read()
            .core_0_area_dram0_0_rd_raw()
            .bit_is_set()
            || self
                .debug_assist
                .core_0_intr_raw()
                .read()
                .core_0_area_dram0_0_wr_raw()
                .bit_is_set()
    }

    /// Enable region monitoring of read/write performed by the main CPU in a
    /// certain memory region1. Whenever the bus reads or writes in the
    /// specified memory region, an interrupt will be triggered.
    pub fn enable_region1_monitor(
        &mut self,
        lower_bound: u32,
        upper_bound: u32,
        reads: bool,
        writes: bool,
    ) {
        self.debug_assist
            .core_0_area_dram0_1_min()
            .write(|w| unsafe { w.core_0_area_dram0_1_min().bits(lower_bound) });

        self.debug_assist
            .core_0_area_dram0_1_max()
            .write(|w| unsafe { w.core_0_area_dram0_1_max().bits(upper_bound) });

        self.debug_assist.core_0_montr_ena().modify(|_, w| {
            w.core_0_area_dram0_1_rd_ena()
                .bit(reads)
                .core_0_area_dram0_1_wr_ena()
                .bit(writes)
        });

        self.clear_region1_monitor_interrupt();

        self.debug_assist.core_0_intr_ena().modify(|_, w| {
            w.core_0_area_dram0_1_rd_intr_ena()
                .set_bit()
                .core_0_area_dram0_1_wr_intr_ena()
                .set_bit()
        });
    }

    /// Disable region1 monitoring on main core.
    pub fn disable_region1_monitor(&mut self) {
        self.debug_assist.core_0_intr_ena().modify(|_, w| {
            w.core_0_area_dram0_1_rd_intr_ena()
                .clear_bit()
                .core_0_area_dram0_1_wr_intr_ena()
                .clear_bit()
        });

        self.debug_assist.core_0_montr_ena().modify(|_, w| {
            w.core_0_area_dram0_1_rd_ena()
                .clear_bit()
                .core_0_area_dram0_1_wr_ena()
                .clear_bit()
        });
    }

    /// Clear region1 monitoring interrupt on main core.
    pub fn clear_region1_monitor_interrupt(&mut self) {
        self.debug_assist.core_0_intr_clr().write(|w| {
            w.core_0_area_dram0_1_rd_clr()
                .set_bit()
                .core_0_area_dram0_1_wr_clr()
                .set_bit()
        });
    }

    /// Check, if region1 monitoring interrupt is set on main core.
    pub fn is_region1_monitor_interrupt_set(&self) -> bool {
        self.debug_assist
            .core_0_intr_raw()
            .read()
            .core_0_area_dram0_1_rd_raw()
            .bit_is_set()
            || self
                .debug_assist
                .core_0_intr_raw()
                .read()
                .core_0_area_dram0_1_wr_raw()
                .bit_is_set()
    }

    /// Get region monitoring PC value on main core.
    pub fn region_monitor_pc(&self) -> u32 {
        self.debug_assist
            .core_0_area_pc()
            .read()
            .core_0_area_pc()
            .bits()
    }
}

#[cfg(all(assist_debug_region_monitor, multi_core))]
impl DebugAssist<'_> {
    /// Enable region monitoring of read/write performed by the secondary CPU in
    /// a certain memory region0. Whenever the bus reads or writes in the
    /// specified memory region, an interrupt will be triggered.
    pub fn enable_core1_region0_monitor(
        &mut self,
        lower_bound: u32,
        upper_bound: u32,
        reads: bool,
        writes: bool,
    ) {
        self.debug_assist
            .core_1_area_dram0_0_min()
            .write(|w| unsafe { w.core_1_area_dram0_0_min().bits(lower_bound) });

        self.debug_assist
            .core_1_area_dram0_0_max()
            .write(|w| unsafe { w.core_1_area_dram0_0_max().bits(upper_bound) });

        self.debug_assist.core_1_montr_ena().modify(|_, w| {
            w.core_1_area_dram0_0_rd_ena()
                .bit(reads)
                .core_1_area_dram0_0_wr_ena()
                .bit(writes)
        });

        self.clear_core1_region0_monitor_interrupt();

        self.debug_assist.core_1_intr_ena().modify(|_, w| {
            w.core_1_area_dram0_0_rd_intr_ena()
                .set_bit()
                .core_1_area_dram0_0_wr_intr_ena()
                .set_bit()
        });
    }

    /// Disable region0 monitoring on secondary core.
    pub fn disable_core1_region0_monitor(&mut self) {
        self.debug_assist.core_1_intr_ena().modify(|_, w| {
            w.core_1_area_dram0_0_rd_intr_ena()
                .clear_bit()
                .core_1_area_dram0_0_wr_intr_ena()
                .clear_bit()
        });

        self.debug_assist.core_1_montr_ena().modify(|_, w| {
            w.core_1_area_dram0_0_rd_ena()
                .clear_bit()
                .core_1_area_dram0_0_wr_ena()
                .clear_bit()
        });
    }

    /// Clear region0 monitoring interrupt on secondary core.
    pub fn clear_core1_region0_monitor_interrupt(&mut self) {
        self.debug_assist.core_1_intr_clr().write(|w| {
            w.core_1_area_dram0_0_rd_clr()
                .set_bit()
                .core_1_area_dram0_0_wr_clr()
                .set_bit()
        });
    }

    /// Check, if region0 monitoring interrupt is set on secondary core.
    pub fn is_core1_region0_monitor_interrupt_set(&self) -> bool {
        self.debug_assist
            .core_1_intr_raw()
            .read()
            .core_1_area_dram0_0_rd_raw()
            .bit_is_set()
            || self
                .debug_assist
                .core_1_intr_raw()
                .read()
                .core_1_area_dram0_0_wr_raw()
                .bit_is_set()
    }

    /// Enable region monitoring of read/write performed by the secondary CPU in
    /// a certain memory region1. Whenever the bus reads or writes in the
    /// specified memory region, an interrupt will be triggered.
    pub fn enable_core1_region1_monitor(
        &mut self,
        lower_bound: u32,
        upper_bound: u32,
        reads: bool,
        writes: bool,
    ) {
        self.debug_assist
            .core_1_area_dram0_1_min()
            .write(|w| unsafe { w.core_1_area_dram0_1_min().bits(lower_bound) });

        self.debug_assist
            .core_1_area_dram0_1_max()
            .write(|w| unsafe { w.core_1_area_dram0_1_max().bits(upper_bound) });

        self.debug_assist.core_1_montr_ena().modify(|_, w| {
            w.core_1_area_dram0_1_rd_ena()
                .bit(reads)
                .core_1_area_dram0_1_wr_ena()
                .bit(writes)
        });

        self.clear_core1_region1_monitor_interrupt();

        self.debug_assist.core_1_intr_ena().modify(|_, w| {
            w.core_1_area_dram0_1_rd_intr_ena()
                .set_bit()
                .core_1_area_dram0_1_wr_intr_ena()
                .set_bit()
        });
    }

    /// Disable region1 monitoring on secondary core.
    pub fn disable_core1_region1_monitor(&mut self) {
        self.debug_assist.core_1_intr_ena().modify(|_, w| {
            w.core_1_area_dram0_1_rd_intr_ena()
                .clear_bit()
                .core_1_area_dram0_1_wr_intr_ena()
                .clear_bit()
        });

        self.debug_assist.core_1_montr_ena().modify(|_, w| {
            w.core_1_area_dram0_1_rd_ena()
                .clear_bit()
                .core_1_area_dram0_1_wr_ena()
                .clear_bit()
        });
    }

    /// Clear region1 monitoring interrupt on secondary core.
    pub fn clear_core1_region1_monitor_interrupt(&mut self) {
        self.debug_assist.core_1_intr_clr().write(|w| {
            w.core_1_area_dram0_1_rd_clr()
                .set_bit()
                .core_1_area_dram0_1_wr_clr()
                .set_bit()
        });
    }

    /// Check, if region1 monitoring interrupt is set on secondary core.
    pub fn is_core1_region1_monitor_interrupt_set(&self) -> bool {
        self.debug_assist
            .core_1_intr_raw()
            .read()
            .core_1_area_dram0_1_rd_raw()
            .bit_is_set()
            || self
                .debug_assist
                .core_1_intr_raw()
                .read()
                .core_1_area_dram0_1_wr_raw()
                .bit_is_set()
    }

    /// Get region monitoring PC value on secondary core.
    pub fn core1_region_monitor_pc(&self) -> u32 {
        self.debug_assist
            .core_1_area_pc()
            .read()
            .core_1_area_pc()
            .bits()
    }
}
