//! The InternalError type, macro for generating it, etc.

use std::fmt::{self, Debug, Display};
use std::panic;
use std::sync::Arc;

use super::*;

#[cfg(feature = "backtrace")]
/// Backtrace implementation for when the feature is enabled
mod ie_backtrace {
    use super::*;
    use backtrace::Backtrace;

    #[derive(Debug, Clone)]
    /// Captured backtrace, if turned on
    pub(crate) struct Captured(Backtrace);

    /// Capture a backtrace, if turned on
    pub(crate) fn capture() -> Captured {
        Captured(Backtrace::new())
    }

    impl Display for Captured {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            Debug::fmt(self, f)
        }
    }
}

#[cfg(not(feature = "backtrace"))]
/// Backtrace implementation for when the feature is disabled
mod ie_backtrace {
    use super::*;

    #[derive(Debug, Clone)]
    /// "Captured backtrace", but actually nothing
    pub(crate) struct Captured;

    /// "Capture a backtrace", but actually return nothing
    pub(crate) fn capture() -> Captured {
        Captured
    }

    impl Display for Captured {
        fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
            Ok(())
        }
    }
}

#[derive(Debug, Clone)]
/// Programming error (a bug)
//
// Boxed because it is fairly large (>=12 words), and will be in a variant in many other errors.
//
// This is a single Bug type containing a kind in BugRepr, rather than separate InternalError and
// BadApiUsage types, primarily because that means that one Bug(#[from] tor_error::Bug) suffices in
// every crate's particular error type.
pub struct Bug(Box<BugRepr>);

/// The source of an Bug
type SourceError = Arc<dyn std::error::Error + Send + Sync + 'static>;

#[derive(Debug, Clone)]
/// Internal error (a bug)
struct BugRepr {
    /// Message, usually from internal!() like format!
    message: String,

    /// File and line number
    location: &'static panic::Location<'static>,

    /// Backtrace, perhaps
    backtrace: ie_backtrace::Captured,

    /// Source, perhaps
    source: Option<SourceError>,

    /// Kind
    ///
    /// `Internal` or `BadApiUsage`
    kind: ErrorKind,
}

impl Bug {
    /// Create a bug error report capturing this call site and backtrace
    ///
    /// Prefer to use [`internal!`],
    /// as that makes it easy to add additional information
    /// via format parameters.
    #[track_caller]
    pub fn new<S: Into<String>>(kind: ErrorKind, message: S) -> Self {
        Bug::new_inner(kind, message.into(), None)
    }

    /// Create an internal error
    #[track_caller]
    fn new_inner(kind: ErrorKind, message: String, source: Option<SourceError>) -> Self {
        Bug(BugRepr {
            kind,
            message,
            source,
            location: panic::Location::caller(),
            backtrace: ie_backtrace::capture(),
        }
        .into())
    }

    /// Create an bug error report from another error, capturing this call site and backtrace
    ///
    /// In `map_err`, and perhaps elsewhere, prefer to use [`into_internal!`],
    /// as that makes it easy to add additional information
    /// via format parameters.
    #[track_caller]
    pub fn from_error<E, S>(kind: ErrorKind, source: E, message: S) -> Self
    where
        S: Into<String>,
        E: std::error::Error + Send + Sync + 'static,
    {
        Bug::new_inner(kind, message.into(), Some(Arc::new(source)))
    }
}

impl std::error::Error for Bug {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.0
            .source
            .as_deref()
            .map(|traitobj| traitobj as _ /* cast away Send and Sync */)
    }
}

impl Display for Bug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(
            f,
            "{} at {}: {}",
            self.0.kind, &self.0.location, &self.0.message
        )?;
        Display::fmt(&self.0.backtrace, f)?;
        Ok(())
    }
}

/// Create an internal error, including a message like `format!`, and capturing this call site
///
/// The calling stack backtrace is also captured,
/// when the `backtrace` cargo feature this is enabled.
///
/// # Examples
///
/// ```
/// use tor_error::internal;
///
/// # fn main() -> Result<(), tor_error::Bug> {
/// # let mut cells = [()].iter();
/// let need_cell = cells.next().ok_or_else(|| internal!("no cells"))?;
/// # Ok(())
/// # }
/// ```
//
// In principle this macro could perhaps support internal!(from=source, "format", ...)
// but there are alternative ways of writing that:
//    Bug::new_from(source, format!(...)) or
//    into_internal!("format", ...)(source)
// Those are not so bad for what we think will be the rare cases not
// covered by internal!(...) or map_err(into_internal!(...))
#[macro_export]
macro_rules! internal {
    { $( $arg:tt )* } => {
        $crate::Bug::new($crate::ErrorKind::Internal, format!($($arg)*))
    }
}

/// Create a bad API usage error, including a message like `format!`, and capturing this call site
///
/// The calling stack backtrace is also captured,
/// when the `backtrace` cargo feature this is enabled.
///
/// # Examples
///
/// ```
/// use tor_error::bad_api_usage;
///
/// # fn main() -> Result<(), tor_error::Bug> {
/// # let mut targets = [()].iter();
/// let need_target = targets.next().ok_or_else(|| bad_api_usage!("no targets"))?;
/// # Ok(())
/// # }
#[macro_export]
macro_rules! bad_api_usage {
    { $( $arg:tt )* } => {
        $crate::Bug::new($crate::ErrorKind::BadApiUsage, format!($($arg)*))
    }
}

/// Helper for converting an error into an internal error
///
/// Returns a closure implementing `FnOnce(E) -> Bug`.
/// The source error `E` must be `std::error::Error + Send + Sync + 'static`.
///
/// # Examples
/// ```
/// use tor_error::into_internal;
///
/// # fn main() -> Result<(), tor_error::Bug> {
/// # let s = b"";
/// let s = std::str::from_utf8(s).map_err(into_internal!("bad bytes: {:?}", s))?;
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! into_internal {
    { $( $arg:tt )* } => {
        |source| $crate::Bug::from_error($crate::ErrorKind::Internal, source, format!($($arg)*))
    }
}

/// Helper for converting an error into an bad API usage error
///
/// Returns a closure implementing `FnOnce(E) -> InternalError`.
/// The source error `E` must be `std::error::Error + Send + Sync + 'static`.
///
/// # Examples
/// ```
/// use tor_error::into_bad_api_usage;
///
/// # fn main() -> Result<(), tor_error::Bug> {
/// # let host = b"";
/// let host = std::str::from_utf8(host).map_err(into_bad_api_usage!("hostname is bad UTF-8: {:?}", host))?;
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! into_bad_api_usage {
    { $( $arg:tt )* } => {
        |source| $crate::Bug::from_error($crate::ErrorKind::BadApiUsage, source, format!($($arg)*))
    }
}

impl HasKind for Bug {
    fn kind(&self) -> ErrorKind {
        self.0.kind
    }
}

#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn internal_macro_test() {
        let start_of_func = line!();

        let e = internal!("Couldn't {} the {}.", "wobble", "wobbling device");
        assert_eq!(e.0.message, "Couldn't wobble the wobbling device.");
        assert!(e.0.location.file().ends_with("internal.rs"));
        assert!(e.0.location.line() > start_of_func);
        assert!(e.0.source.is_none());

        let s = e.to_string();

        assert!(s.starts_with("internal error (bug) at "));
        assert!(s.contains("Couldn't wobble the wobbling device."));
        #[cfg(feature = "backtrace")]
        assert!(s.contains("internal_macro_test"));
    }

    #[test]
    fn source() {
        use std::error::Error;
        use std::str::FromStr;

        let start_of_func = line!();
        let s = "penguin";
        let inner = u32::from_str(s).unwrap_err();
        let outer = u32::from_str(s)
            .map_err(into_internal!("{} is not a number", s))
            .unwrap_err();

        let afterwards = line!();

        assert_eq!(outer.source().unwrap().to_string(), inner.to_string());
        assert!(outer.0.location.line() > start_of_func);
        assert!(outer.0.location.line() < afterwards);
    }
}
