use glob::{GlobError, PatternError};
use std::ffi::OsString;
use std::fmt::{self, Display};
use std::io;
use std::path::PathBuf;

#[derive(Debug)]
pub(crate) enum Error {
    Cargo(io::Error),
    CargoFail,
    GetManifest(PathBuf, Box<Error>),
    Glob(GlobError),
    Io(io::Error),
    Metadata(serde_json::Error),
    Mismatch,
    NoWorkspaceManifest,
    Open(PathBuf, io::Error),
    Pattern(PatternError),
    ProjectDir,
    ReadStderr(io::Error),
    RunFailed,
    ShouldNotHaveCompiled,
    TomlDe(toml::de::Error),
    TomlSer(toml::ser::Error),
    UpdateVar(OsString),
    WriteStderr(io::Error),
}

pub(crate) type Result<T> = std::result::Result<T, Error>;

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::Error::*;

        match self {
            Cargo(e) => write!(f, "failed to execute cargo: {}", e),
            CargoFail => write!(f, "cargo reported an error"),
            GetManifest(path, e) => write!(f, "failed to read manifest {}: {}", path.display(), e),
            Glob(e) => write!(f, "{}", e),
            Io(e) => write!(f, "{}", e),
            Metadata(e) => write!(f, "failed to read cargo metadata: {}", e),
            Mismatch => write!(f, "compiler error does not match expected error"),
            NoWorkspaceManifest => write!(f, "Cargo.toml uses edition.workspace=true, but no edition found in workspace's manifest"),
            Open(path, e) => write!(f, "{}: {}", path.display(), e),
            Pattern(e) => write!(f, "{}", e),
            ProjectDir => write!(f, "failed to determine name of project dir"),
            ReadStderr(e) => write!(f, "failed to read stderr file: {}", e),
            RunFailed => write!(f, "execution of the test case was unsuccessful"),
            ShouldNotHaveCompiled => {
                write!(f, "expected test case to fail to compile, but it succeeded")
            }
            TomlDe(e) => write!(f, "{}", e),
            TomlSer(e) => write!(f, "{}", e),
            UpdateVar(var) => write!(
                f,
                "unrecognized value of TRYBUILD: {:?}",
                var.to_string_lossy(),
            ),
            WriteStderr(e) => write!(f, "failed to write stderr file: {}", e),
        }
    }
}

impl Error {
    pub fn already_printed(&self) -> bool {
        use self::Error::*;

        matches!(
            self,
            CargoFail | Mismatch | RunFailed | ShouldNotHaveCompiled
        )
    }
}

impl From<GlobError> for Error {
    fn from(err: GlobError) -> Self {
        Error::Glob(err)
    }
}

impl From<PatternError> for Error {
    fn from(err: PatternError) -> Self {
        Error::Pattern(err)
    }
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Error::Io(err)
    }
}

impl From<toml::de::Error> for Error {
    fn from(err: toml::de::Error) -> Self {
        Error::TomlDe(err)
    }
}

impl From<toml::ser::Error> for Error {
    fn from(err: toml::ser::Error) -> Self {
        Error::TomlSer(err)
    }
}
