use std::error::Error;
use std::fmt;
use std::marker;

/// An error returned from the `proc_exit` host syscall.
///
/// Embedders can test if an error returned from wasm is this error, in which
/// case it may signal a non-fatal trap.
#[derive(Debug)]
pub struct I32Exit(pub i32);

impl I32Exit {
    /// Accessor for an exit code appropriate for calling `std::process::exit` with,
    /// when interpreting this `I32Exit` as an exit for the parent process.
    ///
    /// This method masks off exit codes which are illegal on Windows.
    pub fn process_exit_code(&self) -> i32 {
        if cfg!(windows) && self.0 >= 3 {
            1
        } else {
            self.0
        }
    }
}

impl fmt::Display for I32Exit {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Exited with i32 exit status {}", self.0)
    }
}

impl std::error::Error for I32Exit {}

/// A helper error type used by many other modules through type aliases.
///
/// This type is an `Error` itself and is intended to be a representation of
/// either:
///
/// * A custom error type `T`
/// * A trap, represented as `anyhow::Error`
///
/// This error is created through either the `::trap` constructor representing a
/// full-fledged trap or the `From<T>` constructor which is intended to be used
/// with `?`. The goal is to make normal errors `T` "automatic" but enable error
/// paths to return a `::trap` error optionally still as necessary without extra
/// boilerplate everywhere else.
///
/// Note that this type isn't used directly but instead is intended to be used
/// as:
///
/// ```rust,ignore
/// type MyError = TrappableError<bindgen::TheError>;
/// ```
///
/// where `MyError` is what you'll use throughout bindings code and
/// `bindgen::TheError` is the type that this represents as generated by the
/// `bindgen!` macro.
#[repr(transparent)]
pub struct TrappableError<T> {
    err: anyhow::Error,
    _marker: marker::PhantomData<T>,
}

impl<T> TrappableError<T> {
    pub fn trap(err: impl Into<anyhow::Error>) -> TrappableError<T> {
        TrappableError {
            err: err.into(),
            _marker: marker::PhantomData,
        }
    }

    pub fn downcast(self) -> anyhow::Result<T>
    where
        T: Error + Send + Sync + 'static,
    {
        self.err.downcast()
    }

    pub fn downcast_ref(&self) -> Option<&T>
    where
        T: Error + Send + Sync + 'static,
    {
        self.err.downcast_ref()
    }
}

impl<T> From<T> for TrappableError<T>
where
    T: Error + Send + Sync + 'static,
{
    fn from(error: T) -> Self {
        Self {
            err: error.into(),
            _marker: marker::PhantomData,
        }
    }
}

impl<T> fmt::Debug for TrappableError<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.err.fmt(f)
    }
}

impl<T> fmt::Display for TrappableError<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.err.fmt(f)
    }
}

impl<T> Error for TrappableError<T> {}
