// Copyright 2018 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
// http://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD
// https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied,
// modified, or distributed except according to those terms. Please review the Licences for the
// specific language governing permissions and limitations relating to use of the SAFE Network
// Software.

macro_rules! newtype_clone (($newtype:ident) => (
        #[allow(clippy::expl_impl_clone_on_copy)]
        impl Clone for $newtype {
            fn clone(&self) -> $newtype {
                let &$newtype(v) = self;
                $newtype(v)
            }
        }
        ));

macro_rules! newtype_from_slice (($newtype:ident, $len:expr) => (
/// `from_slice()` creates an object from a byte slice
///
/// This function will fail and return `None` if the length of
/// the byte-slice isn't equal to the length of the object
    pub fn from_slice(bs: &[u8]) -> Option<$newtype> {
        if bs.len() != $len {
            return None;
        }
        let mut n = $newtype([0; $len]);
        {
            let $newtype(ref mut b) = n;
            for (bi, &bsi) in b.iter_mut().zip(bs.iter()) {
                *bi = bsi
            }
        }
        Some(n)
    }
    ));

macro_rules! newtype_traits (($newtype:ident, $len:expr) => (
    impl ::std::cmp::PartialEq for $newtype {
        fn eq(&self, &$newtype(ref other): &$newtype) -> bool {
            use crate::utils::memcmp;
            let &$newtype(ref this) = self;
            memcmp(this, other)
        }
    }
    impl ::std::cmp::Eq for $newtype {}

    impl ::serde::Serialize for $newtype {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where S: ::serde::Serializer
        {
            serializer.serialize_bytes(&self[..])
        }
    }

    impl<'de> ::serde::Deserialize<'de> for $newtype {
        fn deserialize<D>(deserializer: D) -> Result<$newtype, D::Error>
            where D: ::serde::Deserializer<'de>
        {
            struct NewtypeVisitor;
            impl<'de> ::serde::de::Visitor<'de> for NewtypeVisitor {
                type Value = $newtype;
                fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                    write!(formatter, stringify!($newtype))
                }
                fn visit_seq<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
                    where V: ::serde::de::SeqAccess<'de>
                {
                    let mut res = $newtype([0; $len]);
                    {
                        let $newtype(ref mut arr) = res;
                        for r in arr.iter_mut() {
                            if let Some(value) = (visitor.next_element())? {
                                *r = value;
                            }
                        }
                    }
                    Ok(res)
                }

                fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
                    where E: ::serde::de::Error
                {
                    $newtype::from_slice(v).ok_or(::serde::de::Error::invalid_length(v.len(),
                                                                                     &self))
                }
            }
            deserializer.deserialize_bytes(NewtypeVisitor)
        }
    }

/// Allows a user to access the byte contents of an object as a slice.
///
/// WARNING: it might be tempting to do comparisons on objects
/// by using `x[a..b] == y[a..b]`. This will open up for timing attacks
/// when comparing for example authenticator tags. Because of this only
/// use the comparison functions exposed by the `rust_sodium` API.
    impl ::std::ops::Index<::std::ops::Range<usize>> for $newtype {
        type Output = [u8];
        fn index(&self, _index: ::std::ops::Range<usize>) -> &[u8] {
            let &$newtype(ref b) = self;
            b.index(_index)
        }
    }
/// Allows a user to access the byte contents of an object as a slice.
///
/// WARNING: it might be tempting to do comparisons on objects
/// by using `x[..b] == y[..b]`. This will open up for timing attacks
/// when comparing for example authenticator tags. Because of this only
/// use the comparison functions exposed by the `rust_sodium` API.
    impl ::std::ops::Index<::std::ops::RangeTo<usize>> for $newtype {
        type Output = [u8];
        fn index(&self, _index: ::std::ops::RangeTo<usize>) -> &[u8] {
            let &$newtype(ref b) = self;
            b.index(_index)
        }
    }
/// Allows a user to access the byte contents of an object as a slice.
///
/// WARNING: it might be tempting to do comparisons on objects
/// by using `x[a..] == y[a..]`. This will open up for timing attacks
/// when comparing for example authenticator tags. Because of this only
/// use the comparison functions exposed by the `rust_sodium` API.
    impl ::std::ops::Index<::std::ops::RangeFrom<usize>> for $newtype {
        type Output = [u8];
        fn index(&self, _index: ::std::ops::RangeFrom<usize>) -> &[u8] {
            let &$newtype(ref b) = self;
            b.index(_index)
        }
    }
/// Allows a user to access the byte contents of an object as a slice.
///
/// WARNING: it might be tempting to do comparisons on objects
/// by using `x[] == y[]`. This will open up for timing attacks
/// when comparing for example authenticator tags. Because of this only
/// use the comparison functions exposed by the `rust_sodium` API.
    impl ::std::ops::Index<::std::ops::RangeFull> for $newtype {
        type Output = [u8];
        fn index(&self, _index: ::std::ops::RangeFull) -> &[u8] {
            let &$newtype(ref b) = self;
            b.index(_index)
        }
    }
    ));

macro_rules! public_newtype_traits (($newtype:ident) => (
    impl AsRef<[u8]> for $newtype {
        #[inline]
        fn as_ref(&self) -> &[u8] {
            &self[..]
        }
    }
    impl ::std::cmp::PartialOrd for $newtype {
        #[inline]
        fn partial_cmp(&self,
                       other: &$newtype) -> Option<::std::cmp::Ordering> {
            ::std::cmp::PartialOrd::partial_cmp(self.as_ref(), other.as_ref())
        }
        #[inline]
        fn lt(&self, other: &$newtype) -> bool {
            ::std::cmp::PartialOrd::lt(self.as_ref(), other.as_ref())
        }
        #[inline]
        fn le(&self, other: &$newtype) -> bool {
            ::std::cmp::PartialOrd::le(self.as_ref(), other.as_ref())
        }
        #[inline]
        fn ge(&self, other: &$newtype) -> bool {
            ::std::cmp::PartialOrd::ge(self.as_ref(), other.as_ref())
        }
        #[inline]
        fn gt(&self, other: &$newtype) -> bool {
            ::std::cmp::PartialOrd::gt(self.as_ref(), other.as_ref())
        }
    }
    impl ::std::cmp::Ord for $newtype {
        #[inline]
        fn cmp(&self, other: &$newtype) -> ::std::cmp::Ordering {
            ::std::cmp::Ord::cmp(self.as_ref(), other.as_ref())
        }
    }
    impl ::std::hash::Hash for $newtype {
        fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
            ::std::hash::Hash::hash(self.as_ref(), state)
        }
    }
    ));

/// Macro used for generating newtypes of byte-arrays
///
/// Usage:
/// Generating secret datatypes, e.g. keys
/// ```
/// new_type! {
///     /// This is some documentation for our type
///     secret Key(KEYBYTES);
/// }
/// ```
/// Generating public datatypes, e.g. public keys
/// ```
/// new_type! {
///     /// This is some documentation for our type
///     public PublicKey(PUBLICKEYBYTES);
/// }
/// ```
/// Generating nonce types
/// ```
/// new_type! {
///     /// This is some documentation for our type
///     nonce Nonce(NONCEBYTES);
/// }
/// ```
macro_rules! new_type {
    ( $(#[$meta:meta])*
      secret $name:ident($bytes:expr);
      ) => (
        $(#[$meta])*
        #[must_use]
        pub struct $name(pub [u8; $bytes]);
        newtype_clone!($name);
        newtype_traits!($name, $bytes);
        impl $name {
            newtype_from_slice!($name, $bytes);
        }
        impl Drop for $name {
            fn drop(&mut self) {
                use crate::utils::memzero;
                let &mut $name(ref mut v) = self;
                memzero(v);
            }
        }
        impl ::std::fmt::Debug for $name {
            fn fmt(&self,
                   formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                // Hide secrets from debug output.
                write!(formatter, "{}(****)", stringify!($name))
            }
        }
        );
    ( $(#[$meta:meta])*
      public $name:ident($bytes:expr);
      ) => (
        $(#[$meta])*
        #[derive(Copy)]
        #[must_use]
        pub struct $name(pub [u8; $bytes]);
        newtype_clone!($name);
        newtype_traits!($name, $bytes);
        public_newtype_traits!($name);
        impl $name {
            newtype_from_slice!($name, $bytes);
        }
        impl ::std::fmt::Debug for $name {
            fn fmt(&self,
                   formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                write!(formatter, "{}({:?})", stringify!($name), &self[..])
            }
        }
        );
    ( $(#[$meta:meta])*
      nonce $name:ident($bytes:expr);
      ) => (
        $(#[$meta])*
        #[derive(Copy)]
        #[must_use]
        pub struct $name(pub [u8; $bytes]);
        newtype_clone!($name);
        newtype_traits!($name, $bytes);
        public_newtype_traits!($name);
        impl $name {
            newtype_from_slice!($name, $bytes);

            /// `increment_le()` treats the nonce as an unsigned little-endian number and
            /// returns an incremented version of it.
            ///
            /// WARNING: this method does not check for arithmetic overflow. It is the callers
            /// responsibility to ensure that any given nonce value is only used once.
            /// If the caller does not do that the cryptographic primitives in `rust_sodium`
            /// will not uphold any security guarantees (i.e. they will break)
            pub fn increment_le(&self) -> $name {
                let mut res = *self;
                res.increment_le_inplace();
                res
            }

            /// `increment_le_inplace()` treats the nonce as an unsigned little-endian number
            /// and increments it.
            ///
            /// WARNING: this method does not check for arithmetic overflow. It is the callers
            /// responsibility to ensure that any given nonce value is only used once.
            /// If the caller does not do that the cryptographic primitives in `rust_sodium`
            /// will not uphold any security guarantees.
            pub fn increment_le_inplace(&mut self) {
                use crate::utils::increment_le;
                let &mut $name(ref mut r) = self;
                increment_le(r);
            }

        }
        impl ::std::fmt::Debug for $name {
            fn fmt(&self,
                   formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                write!(formatter, "{}({:?})", stringify!($name), &self[..])
            }
        }
        );
}
