// Copyright 2018 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
// https://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.

use crate::errors::AppError;
use crate::ffi::errors::Result;
use bincode::{deserialize, serialize};
use ffi_utils::{catch_unwind_cb, FfiResult, ReprC, SafePtr, FFI_RESULT_OK};
use safe_core::crypto::shared_secretbox;
use safe_core::ffi::arrays::{SymNonce, SymSecretKey, XorNameArray};
use safe_core::ffi::{md_kind_clone_from_repr_c, MDataInfo};
use safe_core::MDataInfo as NativeMDataInfo;
use safe_nd::{MDataAddress, XorName};
use std::os::raw::c_void;
use std::slice;

/// Create encrypted mdata info with explicit data name and a
/// provided private key.
#[no_mangle]
pub unsafe extern "C" fn mdata_info_new_private(
    md_seq: bool,
    name: *const XorNameArray,
    type_tag: u64,
    secret_key: *const SymSecretKey,
    nonce: *const SymNonce,
    user_data: *mut c_void,
    o_cb: extern "C" fn(
        user_data: *mut c_void,
        result: *const FfiResult,
        mdata_info: *const MDataInfo,
    ),
) {
    catch_unwind_cb(user_data, o_cb, || -> Result<_> {
        let name = XorName(*name);
        let sk = shared_secretbox::Key::from_raw(&*secret_key);

        let md_kind = md_kind_clone_from_repr_c(md_seq);
        let address = MDataAddress::from_kind(md_kind, name, type_tag);

        let info = NativeMDataInfo::new_private(address, (sk, *nonce));
        let info = info.into_repr_c();

        o_cb(user_data, FFI_RESULT_OK, &info);
        Ok(())
    })
}

/// Create random, non-encrypted mdata info.
#[no_mangle]
pub unsafe extern "C" fn mdata_info_random_public(
    md_seq: bool,
    type_tag: u64,
    user_data: *mut c_void,
    o_cb: extern "C" fn(
        user_data: *mut c_void,
        result: *const FfiResult,
        mdata_info: *const MDataInfo,
    ),
) {
    catch_unwind_cb(user_data, o_cb, || -> Result<_> {
        let md_kind = md_kind_clone_from_repr_c(md_seq);
        let info = NativeMDataInfo::random_public(md_kind, type_tag)?;
        let info = info.into_repr_c();

        o_cb(user_data, FFI_RESULT_OK, &info);
        Ok(())
    })
}

/// Create random, encrypted mdata info.
#[no_mangle]
pub unsafe extern "C" fn mdata_info_random_private(
    md_seq: bool,
    type_tag: u64,
    user_data: *mut c_void,
    o_cb: extern "C" fn(
        user_data: *mut c_void,
        result: *const FfiResult,
        mdata_info: *const MDataInfo,
    ),
) {
    catch_unwind_cb(user_data, o_cb, || -> Result<_> {
        let md_kind = md_kind_clone_from_repr_c(md_seq);
        let info = NativeMDataInfo::random_private(md_kind, type_tag)?;
        let info = info.into_repr_c();

        o_cb(user_data, FFI_RESULT_OK, &info);
        Ok(())
    })
}

/// Encrypt mdata entry key using the corresponding mdata info.
#[no_mangle]
pub unsafe extern "C" fn mdata_info_encrypt_entry_key(
    info: *const MDataInfo,
    input: *const u8,
    input_len: usize,
    user_data: *mut c_void,
    o_cb: extern "C" fn(
        user_data: *mut c_void,
        result: *const FfiResult,
        enc_entry_key: *const u8,
        enc_entry_key_len: usize,
    ),
) {
    catch_unwind_cb(user_data, o_cb, || -> Result<_> {
        let info = NativeMDataInfo::clone_from_repr_c(info)?;
        let input = slice::from_raw_parts(input, input_len);
        let encoded = info.enc_entry_key(input).map_err(AppError::from)?;

        o_cb(
            user_data,
            FFI_RESULT_OK,
            encoded.as_safe_ptr(),
            encoded.len(),
        );
        Ok(())
    })
}

/// Encrypt mdata entry value using the corresponding mdata info.
#[no_mangle]
pub unsafe extern "C" fn mdata_info_encrypt_entry_value(
    info: *const MDataInfo,
    input: *const u8,
    input_len: usize,
    user_data: *mut c_void,
    o_cb: extern "C" fn(
        user_data: *mut c_void,
        result: *const FfiResult,
        enc_entry_value: *const u8,
        enc_entry_value_len: usize,
    ),
) {
    catch_unwind_cb(user_data, o_cb, || -> Result<_> {
        let info = NativeMDataInfo::clone_from_repr_c(info)?;
        let input = slice::from_raw_parts(input, input_len);
        let encoded = info.enc_entry_value(input).map_err(AppError::from)?;

        o_cb(
            user_data,
            FFI_RESULT_OK,
            encoded.as_safe_ptr(),
            encoded.len(),
        );
        Ok(())
    })
}

/// Decrypt mdata entry value or a key using the corresponding mdata info.
#[no_mangle]
pub unsafe extern "C" fn mdata_info_decrypt(
    info: *const MDataInfo,
    input: *const u8,
    input_len: usize,
    user_data: *mut c_void,
    o_cb: extern "C" fn(
        user_data: *mut c_void,
        result: *const FfiResult,
        mdata_info_decrypt: *const u8,
        mdata_info_decrypt_len: usize,
    ),
) {
    catch_unwind_cb(user_data, o_cb, || -> Result<_> {
        let info = NativeMDataInfo::clone_from_repr_c(info)?;
        let encoded = slice::from_raw_parts(input, input_len);
        let decoded = info.decrypt(encoded).map_err(AppError::from)?;

        o_cb(
            user_data,
            FFI_RESULT_OK,
            decoded.as_safe_ptr(),
            decoded.len(),
        );
        Ok(())
    })
}

/// Serialise `MDataInfo`.
#[no_mangle]
pub unsafe extern "C" fn mdata_info_serialise(
    info: *const MDataInfo,
    user_data: *mut c_void,
    o_cb: extern "C" fn(
        user_data: *mut c_void,
        result: *const FfiResult,
        encoded: *const u8,
        encoded_len: usize,
    ),
) {
    catch_unwind_cb(user_data, o_cb, || -> Result<_> {
        let info = NativeMDataInfo::clone_from_repr_c(info)?;
        let encoded = serialize(&info).map_err(AppError::from)?;

        o_cb(
            user_data,
            FFI_RESULT_OK,
            encoded.as_safe_ptr(),
            encoded.len(),
        );
        Ok(())
    })
}

/// Deserialise `MDataInfo`.
#[no_mangle]
pub unsafe extern "C" fn mdata_info_deserialise(
    encoded_ptr: *const u8,
    encoded_len: usize,
    user_data: *mut c_void,
    o_cb: extern "C" fn(
        user_data: *mut c_void,
        result: *const FfiResult,
        mdata_info: *const MDataInfo,
    ),
) {
    catch_unwind_cb(user_data, o_cb, || -> Result<_> {
        let encoded = slice::from_raw_parts(encoded_ptr, encoded_len);
        let info: NativeMDataInfo = deserialize(encoded)?;
        let info = info.into_repr_c();

        o_cb(user_data, FFI_RESULT_OK, &info);
        Ok(())
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use ffi_utils::test_utils::{call_1, call_vec_u8};
    use rand;
    use safe_core::crypto::shared_secretbox;
    use safe_core::{utils, MDataInfo};
    use safe_nd::{MDataKind as NativeMDataKind, XOR_NAME_LEN};
    use unwrap::unwrap;

    // Test creating non-encrypted mdata info.
    #[test]
    fn create_public() {
        let seq = true;
        let native_kind = NativeMDataKind::Seq;
        let type_tag: u64 = rand::random();

        let info: MDataInfo = unsafe {
            unwrap!(call_1(|ud, cb| mdata_info_random_public(
                seq, type_tag, ud, cb
            )))
        };

        assert_eq!(info.kind(), native_kind);
        assert_eq!(info.type_tag(), type_tag);
        assert!(info.enc_info.is_none());
    }

    // Test creating encrypted mdata info.
    #[test]
    fn create_private() {
        let seq = true;
        let native_kind = NativeMDataKind::Unseq;
        let type_tag: u64 = rand::random();

        let rand_info: MDataInfo = unsafe {
            unwrap!(call_1(|ud, cb| mdata_info_random_private(
                seq, type_tag, ud, cb
            )))
        };

        let key = shared_secretbox::gen_key();
        let nonce = utils::generate_nonce();
        let new_info: MDataInfo = unsafe {
            unwrap!(call_1(|ud, cb| mdata_info_new_private(
                false,
                &[2; XOR_NAME_LEN],
                type_tag,
                &*key,
                &nonce,
                ud,
                cb
            )))
        };

        assert_eq!(rand_info.type_tag(), type_tag);
        assert!(rand_info.enc_info.is_some());

        assert_eq!(new_info.kind(), native_kind);
        assert_eq!(new_info.type_tag(), type_tag);
        match new_info.enc_info {
            Some((ref got_key, ref got_nonce)) => {
                assert_eq!(*got_key, key);
                assert_eq!(*got_nonce, nonce);
            }
            None => panic!("Unexpected result: no enc_info in private MDataInfo"),
        }
    }

    // Test serialising and deserialising mdata_info.
    #[test]
    fn serialise_deserialise() {
        let info1 = unwrap!(MDataInfo::random_private(NativeMDataKind::Unseq, 1000));
        let info1_ffi = info1.clone().into_repr_c();

        let encoded = unsafe {
            unwrap!(call_vec_u8(|ud, cb| mdata_info_serialise(
                &info1_ffi, ud, cb
            ),))
        };

        let info2 = unsafe {
            let res =
                call_1(|ud, cb| mdata_info_deserialise(encoded.as_ptr(), encoded.len(), ud, cb));

            unwrap!(res)
        };

        assert_eq!(info1, info2);
    }
}
