extern crate sodiumoxide;

use errors::common::CommonError;

use self::sodiumoxide::crypto::secretbox;
use std::convert::AsMut;

pub struct XSalsa20 {}

impl XSalsa20 {
    pub fn new() -> XSalsa20 {
        XSalsa20 {}
    }

    pub fn create_key(&self) -> Vec<u8> {
        secretbox::gen_key()[..].to_vec()
    }

    pub fn gen_nonce(&self) -> Vec<u8> {
        secretbox::gen_nonce()[..].to_vec()
    }

    pub fn encrypt(&self, key: &[u8], nonce: &[u8], doc: &[u8]) -> Vec<u8> {
        secretbox::seal(
            doc,
            &secretbox::Nonce(XSalsa20::_clone_into_array(nonce)),
            &secretbox::Key(XSalsa20::_clone_into_array(key))
        )
    }

    pub fn decrypt(&self, key: &[u8], nonce: &[u8], doc: &[u8]) -> Result<Vec<u8>, CommonError> {
        secretbox::open(
            doc,
            &secretbox::Nonce(XSalsa20::_clone_into_array(nonce)),
            &secretbox::Key(XSalsa20::_clone_into_array(key))
        )
            .map_err(|_| CommonError::InvalidStructure("Unable to decrypt data".to_string()))
    }

    fn _clone_into_array<A, T>(slice: &[T]) -> A
        where A: Sized + Default + AsMut<[T]>, T: Clone
    {
        let mut a = Default::default();
        <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
        a
    }
}


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

    use self::sodiumoxide::randombytes;

    #[test]
    fn encrypt_decrypt_works() {
        let xsalsa20 = XSalsa20::new();

        let nonce = xsalsa20.gen_nonce();
        let key = xsalsa20.create_key();
        let data = randombytes::randombytes(16);

        let encrypted_data = xsalsa20.encrypt(&key, &nonce, &data);
        let decrypt_result = xsalsa20.decrypt(&key, &nonce, &encrypted_data);

        assert!(decrypt_result.is_ok());
        assert_eq!(data, decrypt_result.unwrap());
    }

}