use crate::error::ErrorKind;
use crate::error::{Error, Result};
use std::net::SocketAddr;

/// Utility methods to map errors.
pub struct ErrorMapper;

impl ErrorMapper {
    /// Convert [`ErrorKind::InProgress`] to [`Ok`].
    pub fn in_progress(err: Error) -> Result<()> {
        match err {
            Error::IoError(io_err) => match io_err.kind() {
                ErrorKind::InProgress => Ok(()),
                _ => Err(Error::IoError(io_err)),
            },
            err => Err(err),
        }
    }

    /// Convert [`io::ErrorKind::AddrInUse`] to [`Error::AddressInUse`].
    #[must_use]
    pub fn addr_in_use(err: Error, addr: SocketAddr) -> Error {
        match err {
            Error::IoError(io_err) => match io_err.kind() {
                ErrorKind::Std(std::io::ErrorKind::AddrInUse) => Error::AddressInUse(addr),
                _ => Error::IoError(io_err),
            },
            err => err,
        }
    }

    /// Convert a given [`ErrorKind`] to [`Error::ProbeFailed`].
    #[allow(clippy::needless_pass_by_value)]
    pub fn probe_failed(err: Error, kind: ErrorKind) -> Error {
        match err {
            Error::IoError(io_err) if io_err.kind() == kind => Error::ProbeFailed(io_err),
            _ => err,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::IoError;
    use std::io;
    use std::net::{Ipv4Addr, SocketAddrV4};

    const ADDR: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0));

    #[test]
    fn test_in_progress() {
        let io_err = io::Error::from(ErrorKind::InProgress);
        let err = Error::IoError(IoError::Bind(io_err, ADDR));
        assert!(ErrorMapper::in_progress(err).is_ok());
    }

    #[test]
    fn test_not_in_progress() {
        let io_err = io::Error::from(ErrorKind::Std(io::ErrorKind::Other));
        let err = Error::IoError(IoError::Bind(io_err, ADDR));
        assert!(ErrorMapper::in_progress(err).is_err());
    }

    #[test]
    fn test_addr_in_use() {
        let io_err = io::Error::from(ErrorKind::Std(io::ErrorKind::AddrInUse));
        let err = Error::IoError(IoError::Bind(io_err, ADDR));
        let addr_in_use_err = ErrorMapper::addr_in_use(err, ADDR);
        assert!(matches!(addr_in_use_err, Error::AddressInUse(ADDR)));
    }

    #[test]
    fn test_not_addr_in_use() {
        let io_err = io::Error::from(ErrorKind::Std(io::ErrorKind::Other));
        let err = Error::IoError(IoError::Bind(io_err, ADDR));
        let addr_in_use_err = ErrorMapper::addr_in_use(err, ADDR);
        assert!(matches!(addr_in_use_err, Error::IoError(_)));
    }

    #[test]
    fn test_probe_failed() {
        let io_err = io::Error::from(ErrorKind::HostUnreachable);
        let err = Error::IoError(IoError::Bind(io_err, ADDR));
        let probe_err = ErrorMapper::probe_failed(err, ErrorKind::HostUnreachable);
        assert!(matches!(probe_err, Error::ProbeFailed(_)));
    }
}
