// Copyright 2017-2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// https://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Error types

use core::fmt;

#[cfg(feature="std")]
use std::error::Error as stdError;
#[cfg(feature="std")]
use std::io;

/// Error kind which can be matched over.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum ErrorKind {
    /// Feature is not available; not recoverable.
    /// 
    /// This is the most permanent failure type and implies the error cannot be
    /// resolved simply by retrying (e.g. the feature may not exist in this
    /// build of the application or on the current platform).
    Unavailable,
    /// General failure; there may be a chance of recovery on retry.
    /// 
    /// This is the catch-all kind for errors from known and unknown sources
    /// which do not have a more specific kind / handling method.
    /// 
    /// It is suggested to retry a couple of times or retry later when
    /// handling; some error sources may be able to resolve themselves,
    /// although this is not likely.
    Unexpected,
    /// A transient failure which likely can be resolved or worked around.
    /// 
    /// This error kind exists for a few specific cases where it is known that
    /// the error likely can be resolved internally, but is reported anyway.
    Transient,
    /// Not ready yet: recommended to try again a little later.
    /// 
    /// This error kind implies the generator needs more time or needs some
    /// other part of the application to do something else first before it is
    /// ready for use; for example this may be used by external generators
    /// which require time for initialization.
    NotReady,
    #[doc(hidden)]
    __Nonexhaustive,
}

impl ErrorKind {
    /// True if this kind of error may resolve itself on retry.
    /// 
    /// See also `should_wait()`.
    pub fn should_retry(self) -> bool {
        self != ErrorKind::Unavailable
    }
    
    /// True if we should retry but wait before retrying
    /// 
    /// This implies `should_retry()` is true.
    pub fn should_wait(self) -> bool {
        self == ErrorKind::NotReady
    }
    
    /// A description of this error kind
    pub fn description(self) -> &'static str {
        match self {
            ErrorKind::Unavailable => "permanently unavailable",
            ErrorKind::Unexpected => "unexpected failure",
            ErrorKind::Transient => "transient failure",
            ErrorKind::NotReady => "not ready yet",
            ErrorKind::__Nonexhaustive => unreachable!(),
        }
    }
}


/// Error type of random number generators
/// 
/// This is a relatively simple error type, designed for compatibility with and
/// without the Rust `std` library. It embeds a "kind" code, a message (static
/// string only), and an optional chained cause (`std` only). The `kind` and
/// `msg` fields can be accessed directly; cause can be accessed via
/// `std::error::Error::cause` or `Error::take_cause`. Construction can only be
/// done via `Error::new` or `Error::with_cause`.
#[derive(Debug)]
pub struct Error {
    /// The error kind
    pub kind: ErrorKind,
    /// The error message
    pub msg: &'static str,
    #[cfg(feature="std")]
    cause: Option<Box<stdError + Send + Sync>>,
}

impl Error {
    /// Create a new instance, with specified kind and a message.
    pub fn new(kind: ErrorKind, msg: &'static str) -> Self {
        #[cfg(feature="std")] {
            Error { kind, msg, cause: None }
        }
        #[cfg(not(feature="std"))] {
            Error { kind, msg }
        }
    }
    
    /// Create a new instance, with specified kind, message, and a
    /// chained cause.
    /// 
    /// Note: `stdError` is an alias for `std::error::Error`.
    /// 
    /// If not targetting `std` (i.e. `no_std`), this function is replaced by
    /// another with the same prototype, except that there are no bounds on the
    /// type `E` (because both `Box` and `stdError` are unavailable), and the
    /// `cause` is ignored.
    #[cfg(feature="std")]
    pub fn with_cause<E>(kind: ErrorKind, msg: &'static str, cause: E) -> Self
        where E: Into<Box<stdError + Send + Sync>>
    {
        Error { kind, msg, cause: Some(cause.into()) }
    }
    
    /// Create a new instance, with specified kind, message, and a
    /// chained cause.
    /// 
    /// In `no_std` mode the *cause* is ignored.
    #[cfg(not(feature="std"))]
    pub fn with_cause<E>(kind: ErrorKind, msg: &'static str, _cause: E) -> Self {
        Error { kind, msg }
    }
    
    /// Take the cause, if any. This allows the embedded cause to be extracted.
    /// This uses `Option::take`, leaving `self` with no cause.
    #[cfg(feature="std")]
    pub fn take_cause(&mut self) -> Option<Box<stdError + Send + Sync>> {
        self.cause.take()
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        #[cfg(feature="std")] {
            if let Some(ref cause) = self.cause {
                return write!(f, "{} ({}); cause: {}",
                        self.msg, self.kind.description(), cause);
            }
        }
        write!(f, "{} ({})", self.msg, self.kind.description())
    }
}

#[cfg(feature="std")]
impl stdError for Error {
    fn description(&self) -> &str {
        self.msg
    }

    fn cause(&self) -> Option<&stdError> {
        self.cause.as_ref().map(|e| e.as_ref() as &stdError)
    }
}

#[cfg(feature="std")]
impl From<Error> for io::Error {
    fn from(error: Error) -> Self {
        use std::io::ErrorKind::*;
        match error.kind {
            ErrorKind::Unavailable => io::Error::new(NotFound, error),
            ErrorKind::Unexpected |
            ErrorKind::Transient => io::Error::new(Other, error),
            ErrorKind::NotReady => io::Error::new(WouldBlock, error),
            ErrorKind::__Nonexhaustive => unreachable!(),
        }
    }
}
