/// Separator for gloss [Display](std::fmt::Display).
pub const GLOSS_DISPLAY_SEPARATOR: &str = ": ";

/// Define an error type with a [String] representation for [Display](std::fmt::Display).
///
/// The first argument is the type name. The second optional argument is a prefix for the
/// [Display](std::fmt::Display) representation.
///
/// Implemented as a trivial [Option] newtype with `new(ToString)` as well as
/// `as_problem(ToString)` constructors.
///
/// Note that the inner string is intended to be *only* representational. It is not recommended to
/// associate other meanings with it because string comparisons are inefficient.
///
/// When the inner value is [None] (as is constructed by [default](Default::default)) it behaves
/// like a [tag_error!](super::tag::tag_error) type.
#[macro_export]
macro_rules! gloss_error {
    ( $type:ident $(,)? ) => {
        $crate::gloss_error!($type, "");
    };

    ( $type:ident, $display_prefix:expr $(,)? ) => {
        #[doc = concat!(stringify!($type), " gloss error.")]
        #[derive(Clone, Debug, Default, Hash)]
        pub struct $type(pub ::std::option::Option<::std::string::String>);

        impl $type {
            /// Constructor.
            pub fn new<ToStringT>(gloss: ToStringT) -> Self
            where
                ToStringT: ::std::string::ToString,
            {
                gloss.to_string().into()
            }

            /// Problem constructor.
            #[track_caller]
            pub fn as_problem<ToStringT>(gloss: ToStringT) -> $crate::Problem
            where
                ToStringT: ::std::string::ToString,
            {
                $crate::Problem::from(Self::new(gloss))
            }

            /// Problem constructor.
            #[track_caller]
            pub fn default_as_problem() -> $crate::Problem {
                $crate::Problem::from(Self::default())
            }
        }

        impl ::std::fmt::Display for $type {
            fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                match self.0.as_ref().filter(|gloss| !gloss.is_empty()) {
                    ::std::option::Option::Some(gloss) => {
                        if $display_prefix.is_empty() {
                            ::std::fmt::Display::fmt(gloss, formatter)
                        } else {
                            ::std::write!(
                                formatter,
                                "{}{}{}",
                                $display_prefix,
                                $crate::GLOSS_DISPLAY_SEPARATOR,
                                gloss
                            )
                        }
                    }

                    ::std::option::Option::None => {
                        if $display_prefix.is_empty() {
                            ::std::fmt::Display::fmt(stringify!($type), formatter)
                        } else {
                            ::std::fmt::Display::fmt($display_prefix, formatter)
                        }
                    }
                }
            }
        }

        impl ::std::error::Error for $type {}

        impl ::std::cmp::PartialEq for $type {
            fn eq(&self, other: &Self) -> bool {
                self.0.eq(&other.0)
            }
        }

        impl ::std::cmp::Eq for $type {}

        impl ::std::cmp::PartialOrd for $type {
            fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
                self.0.partial_cmp(&other.0)
            }
        }

        impl ::std::cmp::Ord for $type {
            fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
                self.0.cmp(&other.0)
            }
        }

        impl ::std::convert::From<::std::string::String> for $type {
            fn from(gloss: ::std::string::String) -> Self {
                Self(::std::option::Option::Some(gloss))
            }
        }
    };
}

/// Define an error type with a `&'static str` representation for [Display](std::fmt::Display).
///
/// The first argument is the type name. The second optional argument is a prefix for the
/// [Display](std::fmt::Display) representation.
///
/// Implemented as a trivial [Option] newtype with `new(Into<&'static str>)` as well as
/// `as_problem(Into<&'static str>)` constructors.
///
/// Note that the inner string is intended to be *only* representational. It is not recommended to
/// associate other meanings with it because string comparisons are inefficient.
///
/// When the inner value is [None] (as is constructed by [default](Default::default)) it behaves
/// like a [tag_error!](super::tag::tag_error) type.
#[macro_export]
macro_rules! static_gloss_error {
    ( $type:ident $(,)? ) => {
        $crate::static_gloss_error!($type, "");
    };

    ( $type:ident, $display_prefix:expr $(,)? ) => {
        #[doc = concat!(stringify!($type), " gloss error.")]
        #[derive(Clone, Debug, Default, Hash)]
        pub struct $type(pub ::std::option::Option<&'static str>);

        impl $type {
            /// Constructor.
            pub fn new<IntoStringT>(gloss: IntoStringT) -> Self
            where
                IntoStringT: ::std::convert::Into<&'static str>,
            {
                let gloss: &'static str = gloss.into();
                gloss.into()
            }

            /// Problem constructor.
            #[track_caller]
            pub fn as_problem<IntoStringT>(gloss: IntoStringT) -> $crate::Problem
            where
                IntoStringT: ::std::convert::Into<&'static str>,
            {
                $crate::Problem::from(Self::new(gloss))
            }

            /// Problem constructor.
            #[track_caller]
            pub fn default_as_problem() -> $crate::Problem {
                $crate::Problem::from(Self::default())
            }
        }

        impl ::std::fmt::Display for $type {
            fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                match self.0.as_ref().filter(|gloss| !gloss.is_empty()) {
                    ::std::option::Option::Some(gloss) => {
                        if $display_prefix.is_empty() {
                            ::std::fmt::Display::fmt(gloss, formatter)
                        } else {
                            ::std::write!(
                                formatter,
                                "{}{}{}",
                                $display_prefix,
                                $crate::GLOSS_DISPLAY_SEPARATOR,
                                gloss
                            )
                        }
                    }

                    ::std::option::Option::None => {
                        if $display_prefix.is_empty() {
                            ::std::fmt::Display::fmt(stringify!($type), formatter)
                        } else {
                            ::std::fmt::Display::fmt($display_prefix, formatter)
                        }
                    }
                }
            }
        }

        impl ::std::error::Error for $type {}

        impl ::std::cmp::PartialEq for $type {
            fn eq(&self, other: &Self) -> bool {
                self.0.eq(&other.0)
            }
        }

        impl ::std::cmp::Eq for $type {}

        impl ::std::cmp::PartialOrd for $type {
            fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
                self.0.partial_cmp(&other.0)
            }
        }

        impl ::std::cmp::Ord for $type {
            fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
                self.0.cmp(&other.0)
            }
        }

        impl ::std::convert::From<&'static str> for $type {
            fn from(gloss: &'static str) -> Self {
                Self(::std::option::Option::Some(gloss))
            }
        }
    };
}

#[allow(unused_imports)]
pub use {gloss_error, static_gloss_error};
