// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Type-safe bindings for Zircon status.

use fuchsia_zircon_sys as sys;
use std::error;
use std::ffi::NulError;
use std::fmt;
use std::io;

pub use sys::zx_status_t;

// Creates associated constants of TypeName of the form
// `pub const NAME: TypeName = TypeName(path::to::value);`
// and provides a private `assoc_const_name` method and a `Debug` implementation
// for the type based on `$name`.
// If multiple names match, the first will be used in `name` and `Debug`.
macro_rules! assoc_values {
    ($typename:ident, [$($(#[$attr:meta])* $name:ident = $value:path;)*]) => {
        #[allow(non_upper_case_globals)]
        impl $typename {
            $(
                $(#[$attr])*
                pub const $name: $typename = $typename($value);
            )*

            fn assoc_const_name(&self) -> Option<&'static str> {
                match self.0 {
                    $(
                        $(#[$attr])*
                        $value => Some(stringify!($name)),
                    )*
                    _ => None,
                }
            }
        }

        impl ::std::fmt::Debug for $typename {
            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                f.write_str(concat!(stringify!($typename), "("))?;
                match self.assoc_const_name() {
                    Some(name) => f.write_str(&name)?,
                    None => ::std::fmt::Debug::fmt(&self.0, f)?,
                }
                f.write_str(")")
            }
        }
    }
}

/// Status type indicating the result of a Fuchsia syscall.
///
/// This type is generally used to indicate the reason for an error.
/// While this type can contain `Status::OK` (`ZX_OK` in C land), elements of this type are
/// generally constructed using the `ok` method, which checks for `ZX_OK` and returns a
/// `Result<(), Status>` appropriately.
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct Status(sys::zx_status_t);
impl Status {
    /// Returns `Ok(())` if the status was `OK`,
    /// otherwise returns `Err(status)`.
    pub fn ok(raw: sys::zx_status_t) -> Result<(), Status> {
        if raw == Status::OK.0 {
            Ok(())
        } else {
            Err(Status(raw))
        }
    }

    pub fn from_raw(raw: sys::zx_status_t) -> Self {
        Status(raw)
    }

    pub fn into_raw(self) -> sys::zx_status_t {
        self.0
    }
}

assoc_values!(Status, [
    OK                     = sys::ZX_OK;
    INTERNAL               = sys::ZX_ERR_INTERNAL;
    NOT_SUPPORTED          = sys::ZX_ERR_NOT_SUPPORTED;
    NO_RESOURCES           = sys::ZX_ERR_NO_RESOURCES;
    NO_MEMORY              = sys::ZX_ERR_NO_MEMORY;
    INTERRUPTED_RETRY      = sys::ZX_ERR_INTERRUPTED_RETRY;
    INVALID_ARGS           = sys::ZX_ERR_INVALID_ARGS;
    BAD_HANDLE             = sys::ZX_ERR_BAD_HANDLE;
    WRONG_TYPE             = sys::ZX_ERR_WRONG_TYPE;
    BAD_SYSCALL            = sys::ZX_ERR_BAD_SYSCALL;
    OUT_OF_RANGE           = sys::ZX_ERR_OUT_OF_RANGE;
    BUFFER_TOO_SMALL       = sys::ZX_ERR_BUFFER_TOO_SMALL;
    BAD_STATE              = sys::ZX_ERR_BAD_STATE;
    TIMED_OUT              = sys::ZX_ERR_TIMED_OUT;
    SHOULD_WAIT            = sys::ZX_ERR_SHOULD_WAIT;
    CANCELED               = sys::ZX_ERR_CANCELED;
    PEER_CLOSED            = sys::ZX_ERR_PEER_CLOSED;
    NOT_FOUND              = sys::ZX_ERR_NOT_FOUND;
    ALREADY_EXISTS         = sys::ZX_ERR_ALREADY_EXISTS;
    ALREADY_BOUND          = sys::ZX_ERR_ALREADY_BOUND;
    UNAVAILABLE            = sys::ZX_ERR_UNAVAILABLE;
    ACCESS_DENIED          = sys::ZX_ERR_ACCESS_DENIED;
    IO                     = sys::ZX_ERR_IO;
    IO_REFUSED             = sys::ZX_ERR_IO_REFUSED;
    IO_DATA_INTEGRITY      = sys::ZX_ERR_IO_DATA_INTEGRITY;
    IO_DATA_LOSS           = sys::ZX_ERR_IO_DATA_LOSS;
    IO_NOT_PRESENT         = sys::ZX_ERR_IO_NOT_PRESENT;
    IO_OVERRUN             = sys::ZX_ERR_IO_OVERRUN;
    IO_MISSED_DEADLINE     = sys::ZX_ERR_IO_MISSED_DEADLINE;
    IO_INVALID             = sys::ZX_ERR_IO_INVALID;
    BAD_PATH               = sys::ZX_ERR_BAD_PATH;
    NOT_DIR                = sys::ZX_ERR_NOT_DIR;
    NOT_FILE               = sys::ZX_ERR_NOT_FILE;
    FILE_BIG               = sys::ZX_ERR_FILE_BIG;
    NO_SPACE               = sys::ZX_ERR_NO_SPACE;
    NOT_EMPTY              = sys::ZX_ERR_NOT_EMPTY;
    STOP                   = sys::ZX_ERR_STOP;
    NEXT                   = sys::ZX_ERR_NEXT;
    ASYNC                  = sys::ZX_ERR_ASYNC;
    PROTOCOL_NOT_SUPPORTED = sys::ZX_ERR_PROTOCOL_NOT_SUPPORTED;
    ADDRESS_UNREACHABLE    = sys::ZX_ERR_ADDRESS_UNREACHABLE;
    ADDRESS_IN_USE         = sys::ZX_ERR_ADDRESS_IN_USE;
    NOT_CONNECTED          = sys::ZX_ERR_NOT_CONNECTED;
    CONNECTION_REFUSED     = sys::ZX_ERR_CONNECTION_REFUSED;
    CONNECTION_RESET       = sys::ZX_ERR_CONNECTION_RESET;
    CONNECTION_ABORTED     = sys::ZX_ERR_CONNECTION_ABORTED;
]);

impl Status {
    pub fn into_io_error(self) -> io::Error {
        self.into()
    }

    pub fn from_result(res: Result<(), Self>) -> Self {
        res.into()
    }
}

impl From<io::ErrorKind> for Status {
    fn from(kind: io::ErrorKind) -> Self {
        use std::io::ErrorKind::*;
        match kind {
            NotFound => Status::NOT_FOUND,
            PermissionDenied => Status::ACCESS_DENIED,
            ConnectionRefused => Status::IO_REFUSED,
            ConnectionAborted => Status::PEER_CLOSED,
            AddrInUse => Status::ALREADY_BOUND,
            AddrNotAvailable => Status::UNAVAILABLE,
            BrokenPipe => Status::PEER_CLOSED,
            AlreadyExists => Status::ALREADY_EXISTS,
            WouldBlock => Status::SHOULD_WAIT,
            InvalidInput => Status::INVALID_ARGS,
            TimedOut => Status::TIMED_OUT,
            Interrupted => Status::INTERRUPTED_RETRY,
            UnexpectedEof | WriteZero | ConnectionReset | NotConnected | Other | _ => Status::IO,
        }
    }
}

impl From<Status> for io::ErrorKind {
    fn from(status: Status) -> io::ErrorKind {
        use std::io::ErrorKind::*;
        match status {
            Status::INTERRUPTED_RETRY => Interrupted,
            Status::BAD_HANDLE => BrokenPipe,
            Status::TIMED_OUT => TimedOut,
            Status::SHOULD_WAIT => WouldBlock,
            Status::PEER_CLOSED => ConnectionAborted,
            Status::NOT_FOUND => NotFound,
            Status::ALREADY_EXISTS => AlreadyExists,
            Status::ALREADY_BOUND => AlreadyExists,
            Status::UNAVAILABLE => AddrNotAvailable,
            Status::ACCESS_DENIED => PermissionDenied,
            Status::IO_REFUSED => ConnectionRefused,
            Status::IO_DATA_INTEGRITY => InvalidData,

            Status::BAD_PATH | Status::INVALID_ARGS | Status::OUT_OF_RANGE | Status::WRONG_TYPE => {
                InvalidInput
            }

            Status::OK
            | Status::NEXT
            | Status::STOP
            | Status::NO_SPACE
            | Status::FILE_BIG
            | Status::NOT_FILE
            | Status::NOT_DIR
            | Status::IO_DATA_LOSS
            | Status::IO
            | Status::CANCELED
            | Status::BAD_STATE
            | Status::BUFFER_TOO_SMALL
            | Status::BAD_SYSCALL
            | Status::INTERNAL
            | Status::NOT_SUPPORTED
            | Status::NO_RESOURCES
            | Status::NO_MEMORY
            | _ => Other,
        }
    }
}

impl fmt::Display for Status {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.assoc_const_name() {
            Some(name) => name.fmt(f),
            None => write!(f, "Unknown zircon status code: {}", self.0),
        }
    }
}

impl error::Error for Status {}

impl From<io::Error> for Status {
    fn from(err: io::Error) -> Status {
        err.kind().into()
    }
}

impl From<Status> for io::Error {
    fn from(status: Status) -> io::Error {
        io::Error::from(io::ErrorKind::from(status))
    }
}

impl From<NulError> for Status {
    fn from(_error: NulError) -> Status {
        Status::INVALID_ARGS
    }
}

impl From<Result<(), Status>> for Status {
    fn from(res: Result<(), Status>) -> Status {
        match res {
            Ok(()) => Self::OK,
            Err(status) => status,
        }
    }
}

impl From<Status> for Result<(), Status> {
    fn from(src: Status) -> Result<(), Status> {
        Status::ok(src.into_raw())
    }
}

#[cfg(test)]
mod test {
    use super::Status;

    #[test]
    fn status_debug_format() {
        let cases = [
            ("Status(OK)", Status::OK),
            ("Status(BAD_SYSCALL)", Status::BAD_SYSCALL),
            ("Status(NEXT)", Status::NEXT),
            ("Status(-5050)", Status(-5050)),
        ];
        for &(expected, value) in &cases {
            assert_eq!(expected, format!("{:?}", value));
        }
    }

    #[test]
    fn status_into_result() {
        let ok_result: Result<(), Status> = Status::OK.into();
        assert_eq!(ok_result, Ok(()));

        let err_result: Result<(), Status> = Status::BAD_SYSCALL.into();
        assert_eq!(err_result, Err(Status::BAD_SYSCALL));
    }
}
