use core::ffi::c_void;
use core::fmt;

/// Inspects the current call-stack, passing all active frames into the closure
/// provided to calculate a stack trace.
///
/// This function is the workhorse of this library in calculating the stack
/// traces for a program. The given closure `cb` is yielded instances of a
/// `Frame` which represent information about that call frame on the stack. The
/// closure is yielded frames in a top-down fashion (most recently called
/// functions first).
///
/// The closure's return value is an indication of whether the backtrace should
/// continue. A return value of `false` will terminate the backtrace and return
/// immediately.
///
/// Once a `Frame` is acquired you will likely want to call `backtrace::resolve`
/// to convert the `ip` (instruction pointer) or symbol address to a `Symbol`
/// through which the name and/or filename/line number can be learned.
///
/// Note that this is a relatively low-level function and if you'd like to, for
/// example, capture a backtrace to be inspected later, then the `Backtrace`
/// type may be more appropriate.
///
/// # Required features
///
/// This function requires the `std` feature of the `backtrace` crate to be
/// enabled, and the `std` feature is enabled by default.
///
/// # Panics
///
/// This function strives to never panic, but if the `cb` provided panics then
/// some platforms will force a double panic to abort the process. Some
/// platforms use a C library which internally uses callbacks which cannot be
/// unwound through, so panicking from `cb` may trigger a process abort.
///
/// # Example
///
/// ```
/// extern crate backtrace;
///
/// fn main() {
///     backtrace::trace(|frame| {
///         // ...
///
///         true // continue the backtrace
///     });
/// }
/// ```
#[cfg(feature = "std")]
pub fn trace<F: FnMut(&Frame) -> bool>(cb: F) {
    let _guard = crate::lock::lock();
    unsafe { trace_unsynchronized(cb) }
}

/// Same as `trace`, only unsafe as it's unsynchronized.
///
/// This function does not have synchronization guarantees but is available
/// when the `std` feature of this crate isn't compiled in. See the `trace`
/// function for more documentation and examples.
///
/// # Panics
///
/// See information on `trace` for caveats on `cb` panicking.
pub unsafe fn trace_unsynchronized<F: FnMut(&Frame) -> bool>(mut cb: F) {
    trace_imp(&mut cb)
}

/// A trait representing one frame of a backtrace, yielded to the `trace`
/// function of this crate.
///
/// The tracing function's closure will be yielded frames, and the frame is
/// virtually dispatched as the underlying implementation is not always known
/// until runtime.
#[derive(Clone)]
pub struct Frame {
    pub(crate) inner: FrameImp,
}

impl Frame {
    /// Returns the current instruction pointer of this frame.
    ///
    /// This is normally the next instruction to execute in the frame, but not
    /// all implementations list this with 100% accuracy (but it's generally
    /// pretty close).
    ///
    /// It is recommended to pass this value to `backtrace::resolve` to turn it
    /// into a symbol name.
    pub fn ip(&self) -> *mut c_void {
        self.inner.ip()
    }

    /// Returns the current stack pointer of this frame.
    ///
    /// In the case that a backend cannot recover the stack pointer for this
    /// frame, a null pointer is returned.
    pub fn sp(&self) -> *mut c_void {
        self.inner.sp()
    }

    /// Returns the starting symbol address of the frame of this function.
    ///
    /// This will attempt to rewind the instruction pointer returned by `ip` to
    /// the start of the function, returning that value. In some cases, however,
    /// backends will just return `ip` from this function.
    ///
    /// The returned value can sometimes be used if `backtrace::resolve` failed
    /// on the `ip` given above.
    pub fn symbol_address(&self) -> *mut c_void {
        self.inner.symbol_address()
    }

    /// Returns the base address of the module to which the frame belongs.
    pub fn module_base_address(&self) -> Option<*mut c_void> {
        self.inner.module_base_address()
    }
}

impl fmt::Debug for Frame {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Frame")
            .field("ip", &self.ip())
            .field("symbol_address", &self.symbol_address())
            .finish()
    }
}

#[cfg(all(target_env = "sgx", target_vendor = "fortanix", not(feature = "std")))]
mod sgx_no_std_image_base {
    use core::ffi::c_void;
    use core::sync::atomic::{AtomicUsize, Ordering::SeqCst};

    static IMAGE_BASE: AtomicUsize = AtomicUsize::new(0);

    /// Set the image base address. This is only available for Fortanix SGX
    /// target when the `std` feature is not enabled. This can be used in the
    /// standard library to set the correct base address.
    #[doc(hidden)]
    pub fn set_image_base(base_addr: *mut c_void) {
        IMAGE_BASE.store(base_addr as _, SeqCst);
    }

    pub(crate) fn get_image_base() -> *mut c_void {
        IMAGE_BASE.load(SeqCst) as _
    }
}

#[cfg(all(target_env = "sgx", target_vendor = "fortanix", not(feature = "std")))]
pub use self::sgx_no_std_image_base::set_image_base;

#[cfg(all(target_env = "sgx", target_vendor = "fortanix", not(feature = "std")))]
#[deny(unused)]
pub(crate) use self::sgx_no_std_image_base::get_image_base;

#[cfg(all(target_env = "sgx", target_vendor = "fortanix", feature = "std"))]
#[deny(unused)]
pub(crate) fn get_image_base() -> *mut c_void {
    std::os::fortanix_sgx::mem::image_base() as _
}

cfg_if::cfg_if! {
    // This needs to come first, to ensure that
    // Miri takes priority over the host platform
    if #[cfg(miri)] {
        pub(crate) mod miri;
        use self::miri::trace as trace_imp;
        pub(crate) use self::miri::Frame as FrameImp;
    } else if #[cfg(
        any(
            all(
                unix,
                not(target_os = "emscripten"),
                not(all(target_os = "ios", target_arch = "arm")),
                not(all(target_os = "nto", target_env = "nto70")),
            ),
            all(
                target_env = "sgx",
                target_vendor = "fortanix",
            ),
        )
    )] {
        mod libunwind;
        use self::libunwind::trace as trace_imp;
        pub(crate) use self::libunwind::Frame as FrameImp;
    } else if #[cfg(all(windows, not(target_vendor = "uwp")))] {
        mod dbghelp;
        use self::dbghelp::trace as trace_imp;
        pub(crate) use self::dbghelp::Frame as FrameImp;
    } else {
        mod noop;
        use self::noop::trace as trace_imp;
        pub(crate) use self::noop::Frame as FrameImp;
    }
}
