/// Status of a test
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Status {
    /// Test has been initialized
    Init,
    /// Test will be skipped
    Skip,
    /// Test will be skipped and marked as TODO
    Todo,
    /// Test has been passed successfully
    Pass,
    /// Test failed but this failure will be ignored
    Degraded,
    /// Test failed
    Fail,
    /// Test panicked (e.g. by an assert!())
    Panic,
    /// Test has been aborted
    Abort,
}

impl Status {
    fn as_ord(&self) -> i32
    {
	match self {
	    Self::Init =>	10,
	    Self::Skip =>	20,
	    Self::Todo =>	25,
	    Self::Pass =>	30,
	    Self::Degraded =>	40,
	    Self::Fail =>	50,
	    Self::Panic =>	60,
	    Self::Abort	=>	70
	}
    }

    pub fn combine(a: Self, b: Self) -> Self {
	if a.as_ord() < b.as_ord() {
	    b
	} else {
	    a
	}
    }

    pub fn update(&mut self, a: Self) -> Self {
	let old = *self;

	*self = Self::combine(old, a);

	old
    }

    fn is_valid_result(self) -> bool {
	match self {
	    Self::Init |
	    Self::Skip => false,
	    _          => true,
	}
    }

    pub fn is_ok(&self) -> bool {
	match self {
	    &Self::Pass |
	    &Self::Degraded |
	    &Self::Skip |
	    &Self::Todo	=> true,
	    _ => false,
	}
    }

    pub fn when_degraded(&self) -> Self {
	match self.is_ok() {
	    true  => *self,
	    false => Self::Degraded,
	}
    }
}

impl Default for Status {
    fn default() -> Self {
	Self::Init
    }
}

impl From<()> for Status {
    fn from(_: ()) -> Self {
	Status::Pass
    }
}

impl From<bool> for Status {
    fn from(v: bool) -> Self {
	match v {
	    true =>	Status::Pass,
	    false =>	Status::Fail,
	}
    }
}

pub trait AsStatus {
    fn as_status(&self) -> Status;
    fn reason(&self) -> Option<String> {
	None
    }
}

impl AsStatus for Status {
    fn as_status(&self) -> Status {
	*self
    }
}

impl AsStatus for () {
    fn as_status(&self) -> Status {
	Status::Pass
    }
}

impl AsStatus for bool {
    fn as_status(&self) -> Status {
	Status::from(*self)
    }
}

#[cfg(not(use_specialization))]
mod nospecialize {
    use super:: *;

    pub trait AsStatusErr {
	fn as_status(&self) -> Status;
    }

    impl AsStatusErr for Status {
	fn as_status(&self) -> Status {
	    if ! self.is_valid_result() {
		panic!("invalid result {:?}", self);
	    }
	    *self
	}
    }

    impl <T,E> AsStatus for Result<T, E>
    where
	E: AsStatusErr + std::fmt::Debug,
    {
	fn as_status(&self) -> Status {
	    match self {
		Ok(_)  => Status::Pass,
		Err(e) => e.as_status(),
	    }
	}

	fn reason(&self) -> Option<String> {
	    match self {
		Ok(_)  => None,
		Err(e) => Some(format!("{:?}", e)),
	    }
	}
    }

    #[macro_export]
    macro_rules! test_error {
	($t:ty) => {
	    impl $crate::util::status::AsStatusErr for $t {
		fn as_status(&self) -> $crate::util::status::Status {
		    $crate::util::status::Status::Fail
		}
	    }
	};
    }

    test_error!(());
    test_error!(std::io::Error);

    #[cfg(feature = "with-anyhow")]
    test_error!(anyhow::Error);
}
#[cfg(not(use_specialization))]
pub use nospecialize::AsStatusErr;

#[cfg(use_specialization)]
mod specialize {
    use super::*;

    impl <T> AsStatus for Result<T, Status> {
	fn as_status(&self) -> Status {
	    match self {
		Ok(_)  => Status::Pass,
		Err(e) if e.is_valid_result() => *e,
		Err(e) => panic!("invalid result {:?}", e),
	    }
	}
    }

    default impl <T,E> AsStatus for Result<T, E> {
	fn as_status(&self) -> Status {
	    match self {
		Ok(_)  => Status::Pass,
		Err(_) => Status::Fail,
	    }
	}
    }

    #[macro_export]
    macro_rules! test_error {
    }
}

#[test]
fn test_as_status() {
    let res_0: Result<_,()> = Ok(());
    let res_1: Result<(),_> = Err(Status::Abort);

    assert_eq!(res_0.as_status(), Status::Pass);
    assert_eq!(res_1.as_status(), Status::Abort);
}
