use super::super::{attachment::*, error::*, problem::*, result::*};

use std::process::*;

impl From<&Problem> for ExitCode {
    fn from(problem: &Problem) -> Self {
        problem
            .attachment_of_type::<Self>()
            .map(|exit_code| exit_code.clone())
            .unwrap_or(Self::FAILURE)
    }
}

impl From<Problem> for ExitCode {
    fn from(problem: Problem) -> Self {
        (&problem).into()
    }
}

//
// ExitError
//

gloss_error!(ExitError, "");

impl ExitError {
    /// Problem for [ExitError] with an [ExitCode] attachment.
    #[track_caller]
    pub fn code<ExitCodeT>(exit_code: ExitCodeT) -> Problem
    where
        ExitCodeT: Into<ExitCode>,
    {
        Self::default_as_problem().with_exit_code(exit_code)
    }

    /// Problem for [ExitError] with an [ExitCode] attachment.
    #[track_caller]
    pub fn code_message<ExitCodeT, ToStringT>(exit_code: ExitCodeT, message: ToStringT) -> Problem
    where
        ExitCodeT: Into<ExitCode>,
        ToStringT: ToString,
    {
        Self::as_problem(message).with_exit_code(exit_code)
    }

    /// Problem for [ExitError] with an [ExitCode::FAILURE] attachment.
    #[track_caller]
    pub fn failure() -> Problem {
        Self::default_as_problem().with_failure_exit_code()
    }

    /// Problem for [ExitError] with an [ExitCode::FAILURE] attachment.
    #[track_caller]
    pub fn failure_message<ToStringT>(message: ToStringT) -> Problem
    where
        ToStringT: ToString,
    {
        Self::as_problem(message).with_failure_exit_code()
    }

    /// Problem for [ExitError] with an [ExitCode::SUCCESS] attachment.
    #[track_caller]
    pub fn success() -> Problem {
        Self::default_as_problem().with_success_exit_code()
    }
}

//
// WithExitCode
//

/// With exit code.
pub trait WithExitCode {
    /// With an [ExitCode] attachment.
    fn with_exit_code<ExitCodeT>(self, exit_code: ExitCodeT) -> Self
    where
        ExitCodeT: Into<ExitCode>;

    /// With an [ExitCode::FAILURE] attachment.
    fn with_failure_exit_code(self) -> Self;

    /// With an [ExitCode::SUCCESS] attachment.
    fn with_success_exit_code(self) -> Self;
}

impl WithExitCode for Problem {
    fn with_exit_code<ExitCodeT>(self, exit_code: ExitCodeT) -> Self
    where
        ExitCodeT: Into<ExitCode>,
    {
        let exit_code: ExitCode = exit_code.into();
        self.with(exit_code)
    }

    fn with_failure_exit_code(self) -> Self {
        self.with(ExitCode::FAILURE)
    }

    fn with_success_exit_code(self) -> Self {
        self.with(ExitCode::SUCCESS)
    }
}

//
// WithExitCodeResult
//

/// With exit code.
pub trait WithExitCodeResult<OkT> {
    /// With an [ExitCode] attachment.
    fn with_exit_code<ExitCodeT>(self, exit_code: ExitCodeT) -> Result<OkT, Problem>
    where
        ExitCodeT: Into<ExitCode>;

    /// With an [ExitCode::FAILURE] attachment.
    fn with_failure_exit_code(self) -> Result<OkT, Problem>;

    /// With an [ExitCode::SUCCESS] attachment.
    fn with_success_exit_code(self) -> Result<OkT, Problem>;
}

// Note that we are *not* using map_err() here because a closure cannot be annotated with #[track_caller]

impl<ResultT, OkT> WithExitCodeResult<OkT> for ResultT
where
    ResultT: IntoProblemResult<OkT>,
{
    #[track_caller]
    fn with_exit_code<ExitCodeT>(self, exit_code: ExitCodeT) -> Result<OkT, Problem>
    where
        ExitCodeT: Into<ExitCode>,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with_exit_code(exit_code)),
        }
    }

    #[track_caller]
    fn with_failure_exit_code(self) -> Result<OkT, Problem> {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with_failure_exit_code()),
        }
    }

    #[track_caller]
    fn with_success_exit_code(self) -> Result<OkT, Problem> {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with_success_exit_code()),
        }
    }
}
