use std::borrow::Cow;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::panic::Location;
#[derive(Debug)]
pub struct ServiceError {
    trace: ErrorTrace,
    kind: ServiceErrorKind,
    source: Option<Box<dyn Error + Send>>,
}
impl Display for ServiceError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match &self.kind {
            ServiceErrorKind::InternalError { code } => {
                write!(f, "internal error [error_code = `{code}`]")?;
            }
            ServiceErrorKind::DependencyError { dependency_name, code, } => {
                write!(f, "dependency error [dependency_name = `{dependency_name}`] [error_code = `{code}`]")?;
            }
        };
        Ok(())
    }
}
impl Error for ServiceError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match &self.source {
            None => { None }
            Some(err) => { Some(&**err) }
        }
    }
}
impl ServiceError {
    pub fn internal_error(
        code: Cow<'static, str>,
        source: Option<Box<dyn Error + Send>>,
        trace: ErrorTrace,
    ) -> ServiceError {
        ServiceError {
            trace,
            source,
            kind: ServiceErrorKind::InternalError { code },
        }
    }
    pub fn dependency_error(
        dependency_name: Cow<'static, str>,
        code: Cow<'static, str>,
        source: Option<Box<dyn Error + Send>>,
        trace: ErrorTrace,
    ) -> ServiceError {
        ServiceError {
            trace,
            source,
            kind: ServiceErrorKind::DependencyError {
                code,
                dependency_name,
            },
        }
    }
    pub fn set_error<T>(&mut self, error: T)
        where T: Error + Send + 'static
    {
        self.source = Some(Box::new(error))
    }
    pub fn with_error<T>(mut self, error: T) -> ServiceError
        where T: Error + Send + 'static
    {
        self.set_error(error);
        self
    }
    pub fn trace(&self) -> &ErrorTrace {
        &self.trace
    }
}
#[derive(Debug)]
enum ServiceErrorKind {
    InternalError {
        code: Cow<'static, str>,
    },
    DependencyError {
        code: Cow<'static, str>,
        dependency_name: Cow<'static, str>,
    },
}
#[derive(Debug)]
pub enum ErrorTrace {
    Location(&'static Location<'static>),
}
pub struct MultilineDisplayAdapter<E: Error>(pub E);
impl<E: Error> Display for MultilineDisplayAdapter<E> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "Error: {}", self.0)?;
        let mut cause = self.0.source();
        if self.0.source().is_some() {
            writeln!(f, "Caused by:")?;
        }
        while let Some(error) = cause {
            writeln!(f, "   {}", error)?;
            cause = error.source();
        }
        Ok(())
    }
}