//! Base debugging operations for multi threaded targets.

use crate::arch::Arch;
use crate::common::Signal;
use crate::common::Tid;
use crate::target::Target;
use crate::target::TargetResult;

/// Base required debugging operations for multi threaded targets.
pub trait MultiThreadBase: Target {
    /// Read the target's registers.
    ///
    /// If the registers could not be accessed, an appropriate non-fatal error
    /// should be returned.
    fn read_registers(
        &mut self,
        regs: &mut <Self::Arch as Arch>::Registers,
        tid: Tid,
    ) -> TargetResult<(), Self>;

    /// Write the target's registers.
    ///
    /// If the registers could not be accessed, an appropriate non-fatal error
    /// should be returned.
    fn write_registers(
        &mut self,
        regs: &<Self::Arch as Arch>::Registers,
        tid: Tid,
    ) -> TargetResult<(), Self>;

    /// Support for single-register access.
    /// See [`SingleRegisterAccess`] for more details.
    ///
    /// While this is an optional feature, it is **highly recommended** to
    /// implement it when possible, as it can significantly improve performance
    /// on certain architectures.
    ///
    /// [`SingleRegisterAccess`]:
    /// super::single_register_access::SingleRegisterAccess
    #[inline(always)]
    fn support_single_register_access(
        &mut self,
    ) -> Option<super::single_register_access::SingleRegisterAccessOps<'_, Tid, Self>> {
        None
    }

    /// Read bytes from the specified address range and return the number of
    /// bytes that were read.
    ///
    /// Implementations may return a number `n` that is less than `data.len()`
    /// to indicate that memory starting at `start_addr + n` cannot be
    /// accessed.
    ///
    /// Implemenations may also return an appropriate non-fatal error if the
    /// requested address range could not be accessed (e.g: due to MMU
    /// protection, unhanded page fault, etc...).
    ///
    /// Implementations must guarantee that the returned number is less than or
    /// equal `data.len()`.
    fn read_addrs(
        &mut self,
        start_addr: <Self::Arch as Arch>::Usize,
        data: &mut [u8],
        tid: Tid,
    ) -> TargetResult<usize, Self>;

    /// Write bytes to the specified address range.
    ///
    /// If the requested address range could not be accessed (e.g: due to
    /// MMU protection, unhanded page fault, etc...), an appropriate non-fatal
    /// error should be returned.
    fn write_addrs(
        &mut self,
        start_addr: <Self::Arch as Arch>::Usize,
        data: &[u8],
        tid: Tid,
    ) -> TargetResult<(), Self>;

    /// List all currently active threads.
    ///
    /// See [the section above](#bare-metal-targets) on implementing
    /// thread-related methods on bare-metal (threadless) targets.
    ///
    /// _Note_: Implementors should mark this method as `#[inline(always)]`, as
    /// this will result in better codegen (namely, by sidestepping any of the
    /// `dyn FnMut` closure machinery).
    fn list_active_threads(
        &mut self,
        thread_is_active: &mut dyn FnMut(Tid),
    ) -> Result<(), Self::Error>;

    /// Check if the specified thread is alive.
    ///
    /// As a convenience, this method provides a default implementation which
    /// uses `list_active_threads` to do a linear-search through all active
    /// threads. On thread-heavy systems, it may be more efficient
    /// to override this method with a more direct query.
    #[allow(clippy::wrong_self_convention)] // requires breaking change to fix
    fn is_thread_alive(&mut self, tid: Tid) -> Result<bool, Self::Error> {
        let mut found = false;
        self.list_active_threads(&mut |active_tid| {
            if tid == active_tid {
                found = true;
            }
        })?;
        Ok(found)
    }

    /// Support for resuming the target (e.g: via `continue` or `step`)
    #[inline(always)]
    fn support_resume(&mut self) -> Option<MultiThreadResumeOps<'_, Self>> {
        None
    }

    /// Support for providing thread extra information.
    #[inline(always)]
    fn support_thread_extra_info(
        &mut self,
    ) -> Option<crate::target::ext::thread_extra_info::ThreadExtraInfoOps<'_, Self>> {
        None
    }
}

/// Target extension - support for resuming multi threaded targets.
pub trait MultiThreadResume: Target {
    /// Resume execution on the target.
    ///
    /// Prior to calling `resume`, `gdbstub` will call `clear_resume_actions`,
    /// followed by zero or more calls to the `set_resume_action_XXX` methods,
    /// specifying any thread-specific resume actions.
    ///
    /// Upon returning from the `resume` method, the target being debugged
    /// should be configured to run according to whatever resume actions the
    /// GDB client had specified using any of the `set_resume_action_XXX`
    /// methods.
    ///
    /// # Default Resume Behavior
    ///
    /// By default, any thread that wasn't explicitly resumed by a
    /// `set_resume_action_XXX` method should be resumed as though it was
    /// resumed with `set_resume_action_continue`.
    ///
    /// **However**, if [`support_scheduler_locking`] is implemented and
    /// [`set_resume_action_scheduler_lock`] has been called for the current
    /// resume cycle, this default changes: **unmentioned threads must remain
    /// stopped.**
    ///
    /// [`support_scheduler_locking`]: Self::support_scheduler_locking
    /// [`set_resume_action_scheduler_lock`]: MultiThreadSchedulerLocking::set_resume_action_scheduler_lock
    ///
    /// # Protocol Extensions
    ///
    /// A basic target implementation only needs to implement support for
    /// `set_resume_action_continue`, with all other resume actions requiring
    /// their corresponding protocol extension to be implemented:
    ///
    /// Action                      | Protocol Extension
    /// ----------------------------|------------------------------
    /// Optimized [Single Stepping] | See [`support_single_step()`]
    /// Optimized [Range Stepping]  | See [`support_range_step()`]
    /// [Scheduler Locking]         | See [`support_scheduler_locking()`]
    /// "Stop"                      | Used in "Non-Stop" mode \*
    ///
    /// \* "Non-Stop" mode is currently unimplemented in `gdbstub`
    ///
    /// [Single stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#index-stepi
    /// [Range Stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping
    /// [Scheduler Locking]: https://sourceware.org/gdb/current/onlinedocs/gdb#index-scheduler-locking-mode
    /// [`support_single_step()`]: Self::support_single_step
    /// [`support_range_step()`]: Self::support_range_step
    /// [`support_scheduler_locking()`]: Self::support_scheduler_locking
    ///
    /// # Additional Considerations
    ///
    /// ### Adjusting PC after a breakpoint is hit
    ///
    /// The [GDB remote serial protocol documentation](https://sourceware.org/gdb/current/onlinedocs/gdb/Stop-Reply-Packets.html#swbreak-stop-reason)
    /// notes the following:
    ///
    /// > On some architectures, such as x86, at the architecture level, when a
    /// > breakpoint instruction executes the program counter points at the
    /// > breakpoint address plus an offset. On such targets, the stub is
    /// > responsible for adjusting the PC to point back at the breakpoint
    /// > address.
    ///
    /// Omitting PC adjustment may result in unexpected execution flow and/or
    /// breakpoints not appearing to work correctly.
    ///
    /// ### Bare-Metal Targets
    ///
    /// On bare-metal targets (such as microcontrollers or emulators), it's
    /// common to treat individual _CPU cores_ as a separate "threads". e.g:
    /// in a dual-core system, [CPU0, CPU1] might be mapped to [TID1, TID2]
    /// (note that TIDs cannot be zero).
    ///
    /// In this case, the `Tid` argument of `read/write_addrs` becomes quite
    /// relevant, as different cores may have different memory maps.
    fn resume(&mut self) -> Result<(), Self::Error>;

    /// Clear all previously set resume actions.
    fn clear_resume_actions(&mut self) -> Result<(), Self::Error>;

    /// Continue the specified thread.
    ///
    /// See the [`resume`](Self::resume) docs for information on when this is
    /// called.
    ///
    /// The GDB client may also include a `signal` which should be passed to the
    /// target.
    fn set_resume_action_continue(
        &mut self,
        tid: Tid,
        signal: Option<Signal>,
    ) -> Result<(), Self::Error>;

    /// Support for optimized [single stepping].
    ///
    /// [single stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#index-stepi
    #[inline(always)]
    fn support_single_step(&mut self) -> Option<MultiThreadSingleStepOps<'_, Self>> {
        None
    }

    /// Support for optimized [range stepping].
    ///
    /// [range stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping
    #[inline(always)]
    fn support_range_step(&mut self) -> Option<MultiThreadRangeSteppingOps<'_, Self>> {
        None
    }

    /// Support for [reverse stepping] a target.
    ///
    /// [reverse stepping]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
    #[inline(always)]
    fn support_reverse_step(
        &mut self,
    ) -> Option<super::reverse_exec::ReverseStepOps<'_, Tid, Self>> {
        None
    }

    /// Support for [reverse continuing] a target.
    ///
    /// [reverse continuing]: https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html
    #[inline(always)]
    fn support_reverse_cont(
        &mut self,
    ) -> Option<super::reverse_exec::ReverseContOps<'_, Tid, Self>> {
        None
    }

    /// Support for [scheduler locking].
    ///
    /// [scheduler locking]: https://sourceware.org/gdb/current/onlinedocs/gdb#index-scheduler-locking-mode
    #[inline(always)]
    fn support_scheduler_locking(&mut self) -> Option<MultiThreadSchedulerLockingOps<'_, Self>> {
        None
    }
}

define_ext!(MultiThreadResumeOps, MultiThreadResume);

/// Target Extension - Optimized single stepping for multi threaded targets.
/// See [`MultiThreadResume::support_single_step`].
pub trait MultiThreadSingleStep: Target + MultiThreadResume {
    /// [Single step] the specified target thread.
    ///
    /// Single stepping will step the target a single "step" - typically a
    /// single instruction.
    ///
    /// The GDB client may also include a `signal` which should be passed to the
    /// target.
    ///
    /// If your target does not support signals (e.g: the target is a bare-metal
    /// microcontroller / emulator), the recommended behavior is to return a
    /// target-specific fatal error
    ///
    /// [Single step]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#index-stepi
    fn set_resume_action_step(
        &mut self,
        tid: Tid,
        signal: Option<Signal>,
    ) -> Result<(), Self::Error>;
}

define_ext!(MultiThreadSingleStepOps, MultiThreadSingleStep);

/// Target Extension - Optimized range stepping for multi threaded targets.
/// See [`MultiThreadResume::support_range_step`].
pub trait MultiThreadRangeStepping: Target + MultiThreadResume {
    /// [Range step] the specified target thread.
    ///
    /// Range Stepping will step the target once, and keep stepping the target
    /// as long as execution remains between the specified start (inclusive)
    /// and end (exclusive) addresses, or another stop condition is met
    /// (e.g: a breakpoint it hit).
    ///
    /// If the range is empty (`start` == `end`), then the action becomes
    /// equivalent to the ‘s’ action. In other words, single-step once, and
    /// report the stop (even if the stepped instruction jumps to start).
    ///
    /// _Note:_ A stop reply may be sent at any point even if the PC is still
    /// within the stepping range; for example, it is valid to implement range
    /// stepping in a degenerate way as a single instruction step operation.
    ///
    /// [Range step]: https://sourceware.org/gdb/current/onlinedocs/gdb/Continuing-and-Stepping.html#range-stepping
    fn set_resume_action_range_step(
        &mut self,
        tid: Tid,
        start: <Self::Arch as Arch>::Usize,
        end: <Self::Arch as Arch>::Usize,
    ) -> Result<(), Self::Error>;
}

define_ext!(MultiThreadRangeSteppingOps, MultiThreadRangeStepping);

/// Target Extension - support for GDB's "Scheduler Locking" mode.
/// See [`MultiThreadResume::support_scheduler_locking`].
pub trait MultiThreadSchedulerLocking: Target + MultiThreadResume {
    /// Configure the target to enable "Scheduler Locking" for the upcoming
    /// resume.
    ///
    /// This method is invoked when the GDB client expects only a specific set
    /// of threads to run, while all other threads remain frozen. This behavior
    /// is typically toggled in GDB using the `set scheduler-locking on`
    /// command.
    ///
    /// When this method is called, the implementation must ensure that any
    /// threads not explicitly resumed via previous `set_resume_action_...`
    /// calls **remain stopped**.
    ///
    /// This prevents any "implicit continue" behavior for unmentioned threads,
    /// satisfying GDB's expectation that only the designated threads will
    /// advance during the next resume.
    fn set_resume_action_scheduler_lock(&mut self) -> Result<(), Self::Error>;
}

define_ext!(MultiThreadSchedulerLockingOps, MultiThreadSchedulerLocking);
