//   Copyright 2022. The Tari Project
//
//   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
//   following conditions are met:
//
//   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
//   disclaimer.
//
//   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
//   following disclaimer in the documentation and/or other materials provided with the distribution.
//
//   3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
//   products derived from this software without specific prior written permission.
//
//   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
//   USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

//! Hook that implements common behavior when a panic happens during template execution

#[cfg(feature = "std")]
mod _panic_handler {
    use std::boxed::Box;

    use tari_template_abi::{on_panic, rust::string::String};

    fn hook(info: &std::panic::PanicHookInfo<'_>) {
        let error_msg = info
            .payload()
            .downcast_ref::<String>()
            .map(String::as_str)
            .or_else(|| info.payload().downcast_ref::<&'static str>().copied())
            .unwrap_or("");
        let location = info.location();

        unsafe {
            match location {
                Some(loc) => {
                    let line = loc.line();
                    let column = loc.column();

                    on_panic(error_msg.as_ptr(), error_msg.len() as u32, line, column)
                },
                None => on_panic(error_msg.as_ptr(), error_msg.len() as u32, 0, 0),
            };
        }
    }

    /// Registers a `std::panic` hook that tries to include the error line and column to the error
    pub fn register_panic_hook() {
        use std::sync::Once;
        static SET_HOOK: Once = Once::new();
        SET_HOOK.call_once(|| {
            std::panic::set_hook(Box::new(hook));
        });
    }
}

#[cfg(all(not(feature = "std"), target_arch = "wasm32"))]
mod _panic_handler {
    use core::panic::PanicInfo;

    use tari_template_abi::{on_panic, rust::format, wrap_ptr};

    #[panic_handler]
    fn panic_handler(info: &PanicInfo) -> ! {
        unsafe {
            let location = info.location();
            let (line, column) = location.map(|loc| (loc.line(), loc.column())).unwrap_or((0, 0));
            let msg = format!("{}", info.message()).into_bytes();
            let len = msg.len();
            let error_msg = wrap_ptr(msg);
            on_panic(error_msg, len as u32, line, column)
        }

        loop {}
    }

    /// No-op for no_std WASM targets as the panic handler is registered via the attribute
    pub fn register_panic_hook() {}
}

pub use _panic_handler::register_panic_hook;
