extern crate libc;

use api::{ErrorCode, IndyHandle};
use commands::{Command, CommandExecutor};
use commands::crypto::CryptoCommand;
use domain::crypto::key::KeyInfo;
use errors::prelude::*;
use utils::ctypes;

use serde_json;
use self::libc::c_char;


/// Creates keys pair and stores in the wallet.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: Wallet handle (created by open_wallet).
/// key_json: Key information as json. Example:
/// {
///     "seed": string, (optional) Seed that allows deterministic key creation (if not set random one will be created).
///                                Can be UTF-8, base64 or hex string.
///     "crypto_type": string, // Optional (if not set then ed25519 curve is used); Currently only 'ed25519' value is supported for this field.
/// }
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// Error Code
/// cb:
/// - command_handle_: command handle to map callback to caller context.
/// - err: Error code.
/// - verkey: Ver key of generated key pair, also used as key identifier
///
/// #Errors
/// Common*
/// Wallet*
/// Crypto*
#[no_mangle]
pub  extern fn indy_create_key(command_handle: IndyHandle,
                               wallet_handle: IndyHandle,
                               key_json: *const c_char,
                               cb: Option<extern fn(command_handle_: IndyHandle,
                                                    err: ErrorCode,
                                                    verkey: *const c_char)>) -> ErrorCode {
    trace!("indy_create_key: >>> wallet_handle: {:?}, key_json: {:?}", wallet_handle, key_json);

    check_useful_json!(key_json, ErrorCode::CommonInvalidParam3, KeyInfo);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam4);

    trace!("indy_create_key: entities >>> wallet_handle: {:?}, key_json: {:?}", wallet_handle, secret!(&key_json));

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::CreateKey(
            wallet_handle,
            key_json,
            Box::new(move |result| {
                let (err, verkey) = result_to_err_code_1!(result, String::new());
                trace!("indy_create_key: verkey: {:?}", verkey);
                let verkey = ctypes::string_to_cstring(verkey);
                cb(command_handle, err, verkey.as_ptr())
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_create_key: <<< res: {:?}", res);

    res
}

/// Saves/replaces the meta information for the giving key in the wallet.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: Wallet handle (created by open_wallet).
/// verkey - the key (verkey, key id) to store metadata.
/// metadata - the meta information that will be store with the key.
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// Error Code
/// cb:
/// - command_handle_: command handle to map callback to caller context.
/// - err: Error code.
///
/// #Errors
/// Common*
/// Wallet*
/// Crypto*
#[no_mangle]
pub  extern fn indy_set_key_metadata(command_handle: IndyHandle,
                                     wallet_handle: IndyHandle,
                                     verkey: *const c_char,
                                     metadata: *const c_char,
                                     cb: Option<extern fn(command_handle_: IndyHandle,
                                                          err: ErrorCode)>) -> ErrorCode {
    trace!("indy_set_key_metadata: >>> wallet_handle: {:?}, verkey: {:?}, metadata: {:?}", wallet_handle, verkey, metadata);

    check_useful_c_str!(verkey, ErrorCode::CommonInvalidParam3);
    check_useful_c_str_empty_accepted!(metadata, ErrorCode::CommonInvalidParam4);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam5);

    trace!("indy_set_key_metadata: entities >>> wallet_handle: {:?}, verkey: {:?}, metadata: {:?}", wallet_handle, verkey, metadata);

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::SetKeyMetadata(
            wallet_handle,
            verkey,
            metadata,
            Box::new(move |result| {
                let err = result_to_err_code!(result);
                trace!("indy_set_key_metadata: ");
                cb(command_handle, err)
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_set_key_metadata: <<< res: {:?}", res);

    res
}

/// Retrieves the meta information for the giving key in the wallet.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: Wallet handle (created by open_wallet).
/// verkey - The key (verkey, key id) to retrieve metadata.
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// Error Code
/// cb:
/// - command_handle_: Command handle to map callback to caller context.
/// - err: Error code.
/// - metadata - The meta information stored with the key; Can be null if no metadata was saved for this key.
///
/// #Errors
/// Common*
/// Wallet*
/// Crypto*
#[no_mangle]
pub  extern fn indy_get_key_metadata(command_handle: IndyHandle,
                                     wallet_handle: IndyHandle,
                                     verkey: *const c_char,
                                     cb: Option<extern fn(command_handle_: IndyHandle,
                                                          err: ErrorCode,
                                                          metadata: *const c_char)>) -> ErrorCode {
    trace!("indy_get_key_metadata: >>> wallet_handle: {:?}, verkey: {:?}", wallet_handle, verkey);

    check_useful_c_str!(verkey, ErrorCode::CommonInvalidParam3);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam4);

    trace!("indy_get_key_metadata: entities >>> wallet_handle: {:?}, verkey: {:?}", wallet_handle, verkey);

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::GetKeyMetadata(
            wallet_handle,
            verkey,
            Box::new(move |result| {
                let (err, metadata) = result_to_err_code_1!(result, String::new());
                trace!("indy_get_key_metadata: metadata: {:?}", metadata);
                let metadata = ctypes::string_to_cstring(metadata);
                cb(command_handle, err, metadata.as_ptr())
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_get_key_metadata: <<< res: {:?}", res);

    res
}

/// Signs a message with a key.
///
/// Note to use DID keys with this function you can call indy_key_for_did to get key id (verkey)
/// for specific DID.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// wallet_handle: wallet handler (created by open_wallet).
/// signer_vk: id (verkey) of message signer. The key must be created by calling indy_create_key or indy_create_and_store_my_did
/// message_raw: a pointer to first byte of message to be signed
/// message_len: a message length
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// a signature string
///
/// #Errors
/// Common*
/// Wallet*
/// Crypto*
#[no_mangle]
pub  extern fn indy_crypto_sign(command_handle: IndyHandle,
                                wallet_handle: IndyHandle,
                                signer_vk: *const c_char,
                                message_raw: *const u8,
                                message_len: u32,
                                cb: Option<extern fn(command_handle_: IndyHandle,
                                                     err: ErrorCode,
                                                     signature_raw: *const u8,
                                                     signature_len: u32)>) -> ErrorCode {
    trace!("indy_crypto_sign: >>> wallet_handle: {:?}, signer_vk: {:?}, message_raw: {:?}, message_len: {:?}",
           wallet_handle, signer_vk, message_raw, message_len);

    check_useful_c_str!(signer_vk, ErrorCode::CommonInvalidParam3);
    check_useful_c_byte_array!(message_raw, message_len, ErrorCode::CommonInvalidParam4, ErrorCode::CommonInvalidParam5);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam6);

    trace!("indy_crypto_sign: entities >>> wallet_handle: {:?}, signer_vk: {:?}, message_raw: {:?}, message_len: {:?}",
           wallet_handle, signer_vk, message_raw, message_len);

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::CryptoSign(
            wallet_handle,
            signer_vk,
            message_raw,
            Box::new(move |result| {
                let (err, signature) = result_to_err_code_1!(result, Vec::new());
                trace!("indy_crypto_sign: signature: {:?}", signature);
                let (signature_raw, signature_len) = ctypes::vec_to_pointer(&signature);
                cb(command_handle, err, signature_raw, signature_len)
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_crypto_sign: <<< res: {:?}", res);

    res
}

/// Verify a signature with a verkey.
///
/// Note to use DID keys with this function you can call indy_key_for_did to get key id (verkey)
/// for specific DID.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// signer_vk: verkey of the message signer
/// message_raw: a pointer to first byte of message that has been signed
/// message_len: a message length
/// signature_raw: a pointer to first byte of signature to be verified
/// signature_len: a signature length
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// valid: true - if signature is valid, false - otherwise
///
/// #Errors
/// Common*
/// Wallet*
/// Ledger*
/// Crypto*
#[no_mangle]
pub  extern fn indy_crypto_verify(command_handle: IndyHandle,
                                  signer_vk: *const c_char,
                                  message_raw: *const u8,
                                  message_len: u32,
                                  signature_raw: *const u8,
                                  signature_len: u32,
                                  cb: Option<extern fn(command_handle_: IndyHandle,
                                                       err: ErrorCode,
                                                       valid: bool)>) -> ErrorCode {
    trace!("indy_crypto_verify: >>> signer_vk: {:?}, message_raw: {:?}, message_len: {:?}, signature_raw: {:?}, signature_len: {:?}",
           signer_vk, message_raw, message_len, signature_raw, signature_len);

    check_useful_c_str!(signer_vk, ErrorCode::CommonInvalidParam2);
    check_useful_c_byte_array!(message_raw, message_len, ErrorCode::CommonInvalidParam3, ErrorCode::CommonInvalidParam4);
    check_useful_c_byte_array!(signature_raw, signature_len, ErrorCode::CommonInvalidParam5, ErrorCode::CommonInvalidParam6);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam7);

    trace!("indy_crypto_verify: entities >>> signer_vk: {:?}, message_raw: {:?}, message_len: {:?}, signature_raw: {:?}, signature_len: {:?}",
           signer_vk, message_raw, message_len, signature_raw, signature_len);

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::CryptoVerify(
            signer_vk,
            message_raw,
            signature_raw,
            Box::new(move |result| {
                let (err, valid) = result_to_err_code_1!(result, false);
                trace!("indy_crypto_verify: valid: {:?}", valid);
                cb(command_handle, err, valid)
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_crypto_verify: <<< res: {:?}", res);

    res
}

/// Encrypt a message by authenticated-encryption scheme.
///
/// Sender can encrypt a confidential message specifically for Recipient, using Sender's public key.
/// Using Recipient's public key, Sender can compute a shared secret key.
/// Using Sender's public key and his secret key, Recipient can compute the exact same shared secret key.
/// That shared secret key can be used to verify that the encrypted message was not tampered with,
/// before eventually decrypting it.
///
/// Note to use DID keys with this function you can call indy_key_for_did to get key id (verkey)
/// for specific DID.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// wallet_handle: wallet handle (created by open_wallet).
/// sender_vk: id (verkey) of message sender. The key must be created by calling indy_create_key or indy_create_and_store_my_did
/// recipient_vk: id (verkey) of message recipient
/// message_raw: a pointer to first byte of message that to be encrypted
/// message_len: a message length
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// an encrypted message as a pointer to array of bytes.
///
/// #Errors
/// Common*
/// Wallet*
/// Ledger*
/// Crypto*
#[no_mangle]
pub  extern fn indy_crypto_auth_crypt(command_handle: IndyHandle,
                                      wallet_handle: IndyHandle,
                                      sender_vk: *const c_char,
                                      recipient_vk: *const c_char,
                                      msg_data: *const u8,
                                      msg_len: u32,
                                      cb: Option<extern fn(command_handle_: IndyHandle,
                                                           err: ErrorCode,
                                                           encrypted_msg: *const u8,
                                                           encrypted_len: u32)>) -> ErrorCode {
    trace!("indy_crypto_auth_crypt: >>> wallet_handle: {:?}, sender_vk: {:?}, recipient_vk: {:?}, msg_data: {:?}, msg_len: {:?}",
           wallet_handle, sender_vk, recipient_vk, msg_data, msg_len);

    check_useful_c_str!(sender_vk, ErrorCode::CommonInvalidParam3);
    check_useful_c_str!(recipient_vk, ErrorCode::CommonInvalidParam4);
    check_useful_c_byte_array!(msg_data, msg_len, ErrorCode::CommonInvalidParam5, ErrorCode::CommonInvalidParam6);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam7);

    trace!("indy_crypto_auth_crypt: entities >>> wallet_handle: {:?}, sender_vk: {:?}, recipient_vk: {:?}, msg_data: {:?}, msg_len: {:?}",
           wallet_handle, sender_vk, recipient_vk, msg_data, msg_len);

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::AuthenticatedEncrypt(
            wallet_handle,
            sender_vk,
            recipient_vk,
            msg_data,
            Box::new(move |result| {
                let (err, encrypted_msg) = result_to_err_code_1!(result, Vec::new());
                trace!("indy_crypto_auth_crypt: encrypted_msg: {:?}", encrypted_msg);
                let (encrypted_msg_raw, encrypted_msg_len) = ctypes::vec_to_pointer(&encrypted_msg);
                cb(command_handle, err, encrypted_msg_raw, encrypted_msg_len)
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_crypto_auth_crypt: <<< res: {:?}", res);

    res
}

/// Decrypt a message by authenticated-encryption scheme.
///
/// Sender can encrypt a confidential message specifically for Recipient, using Sender's public key.
/// Using Recipient's public key, Sender can compute a shared secret key.
/// Using Sender's public key and his secret key, Recipient can compute the exact same shared secret key.
/// That shared secret key can be used to verify that the encrypted message was not tampered with,
/// before eventually decrypting it.
///
/// Note to use DID keys with this function you can call indy_key_for_did to get key id (verkey)
/// for specific DID.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// wallet_handle: wallet handler (created by open_wallet).
/// recipient_vk: id (verkey) of message recipient. The key must be created by calling indy_create_key or indy_create_and_store_my_did
/// encrypted_msg_raw: a pointer to first byte of message that to be decrypted
/// encrypted_msg_len: a message length
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// sender verkey and decrypted message as a pointer to array of bytes
///
/// #Errors
/// Common*
/// Wallet*
/// Crypto*
#[no_mangle]
pub  extern fn indy_crypto_auth_decrypt(command_handle: IndyHandle,
                                        wallet_handle: IndyHandle,
                                        recipient_vk: *const c_char,
                                        encrypted_msg: *const u8,
                                        encrypted_len: u32,
                                        cb: Option<extern fn(command_handle_: IndyHandle,
                                                             err: ErrorCode,
                                                             sender_vk: *const c_char,
                                                             msg_data: *const u8,
                                                             msg_len: u32)>) -> ErrorCode {
    trace!("indy_crypto_auth_decrypt: >>> wallet_handle: {:?}, recipient_vk: {:?}, encrypted_msg: {:?}, encrypted_len: {:?}",
           wallet_handle, recipient_vk, encrypted_msg, encrypted_len);

    check_useful_c_str!(recipient_vk, ErrorCode::CommonInvalidParam3);
    check_useful_c_byte_array!(encrypted_msg, encrypted_len, ErrorCode::CommonInvalidParam4, ErrorCode::CommonInvalidParam5);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam6);

    trace!("indy_crypto_auth_decrypt: entities >>> wallet_handle: {:?}, recipient_vk: {:?}, encrypted_msg: {:?}, encrypted_len: {:?}",
           wallet_handle, recipient_vk, encrypted_msg, encrypted_len);

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::AuthenticatedDecrypt(
            wallet_handle,
            recipient_vk,
            encrypted_msg,
            Box::new(move |result| {
                let (err, sender_vk, msg) = result_to_err_code_2!(result, String::new(), Vec::new());
                trace!("indy_crypto_auth_decrypt: sender_vk: {:?}, msg: {:?}", sender_vk, msg);
                let (msg_data, msg_len) = ctypes::vec_to_pointer(&msg);
                let sender_vk = ctypes::string_to_cstring(sender_vk);
                cb(command_handle, err, sender_vk.as_ptr(), msg_data, msg_len)
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_crypto_auth_decrypt: <<< res: {:?}", res);

    res
}

/// Encrypts a message by anonymous-encryption scheme.
///
/// Sealed boxes are designed to anonymously send messages to a Recipient given its public key.
/// Only the Recipient can decrypt these messages, using its private key.
/// While the Recipient can verify the integrity of the message, it cannot verify the identity of the Sender.
///
/// Note to use DID keys with this function you can call indy_key_for_did to get key id (verkey)
/// for specific DID.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// recipient_vk: verkey of message recipient
/// message_raw: a pointer to first byte of message that to be encrypted
/// message_len: a message length
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// an encrypted message as a pointer to array of bytes
///
/// #Errors
/// Common*
/// Wallet*
/// Ledger*
/// Crypto*
#[no_mangle]
pub  extern fn indy_crypto_anon_crypt(command_handle: IndyHandle,
                                      recipient_vk: *const c_char,
                                      msg_data: *const u8,
                                      msg_len: u32,
                                      cb: Option<extern fn(command_handle_: IndyHandle,
                                                           err: ErrorCode,
                                                           encrypted_msg: *const u8,
                                                           encrypted_len: u32)>) -> ErrorCode {
    trace!("indy_crypto_anon_crypt: >>> recipient_vk: {:?}, msg_data: {:?}, msg_len: {:?}", recipient_vk, msg_data, msg_len);

    check_useful_c_str!(recipient_vk, ErrorCode::CommonInvalidParam2);
    check_useful_c_byte_array!(msg_data, msg_len, ErrorCode::CommonInvalidParam3, ErrorCode::CommonInvalidParam4);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam5);

    trace!("indy_crypto_anon_crypt: entities >>> recipient_vk: {:?}, msg_data: {:?}, msg_len: {:?}", recipient_vk, msg_data, msg_len);

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::AnonymousEncrypt(
            recipient_vk,
            msg_data,
            Box::new(move |result| {
                let (err, encrypted_msg) = result_to_err_code_1!(result, Vec::new());
                trace!("indy_crypto_anon_crypt: encrypted_msg: {:?}", encrypted_msg);
                let (encrypted_msg_raw, encrypted_msg_len) = ctypes::vec_to_pointer(&encrypted_msg);
                cb(command_handle, err, encrypted_msg_raw, encrypted_msg_len)
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_crypto_anon_crypt: <<< res: {:?}", res);

    res
}

/// Decrypts a message by anonymous-encryption scheme.
///
/// Sealed boxes are designed to anonymously send messages to a Recipient given its public key.
/// Only the Recipient can decrypt these messages, using its private key.
/// While the Recipient can verify the integrity of the message, it cannot verify the identity of the Sender.
///
/// Note to use DID keys with this function you can call indy_key_for_did to get key id (verkey)
/// for specific DID.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// wallet_handle: wallet handler (created by open_wallet).
/// recipient_vk: id (verkey) of my key. The key must be created by calling indy_create_key or indy_create_and_store_my_did
/// encrypted_msg_raw: a pointer to first byte of message that to be decrypted
/// encrypted_msg_len: a message length
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// decrypted message as a pointer to an array of bytes
///
/// #Errors
/// Common*
/// Wallet*
/// Crypto*
#[no_mangle]
pub  extern fn indy_crypto_anon_decrypt(command_handle: IndyHandle,
                                        wallet_handle: IndyHandle,
                                        recipient_vk: *const c_char,
                                        encrypted_msg: *const u8,
                                        encrypted_len: u32,
                                        cb: Option<extern fn(command_handle_: IndyHandle,
                                                             err: ErrorCode,
                                                             msg_data: *const u8,
                                                             msg_len: u32)>) -> ErrorCode {
    trace!("indy_crypto_anon_decrypt: >>> wallet_handle: {:?}, recipient_vk: {:?}, encrypted_msg: {:?}, encrypted_len: {:?}",
           wallet_handle, recipient_vk, encrypted_msg, encrypted_len);

    check_useful_c_str!(recipient_vk, ErrorCode::CommonInvalidParam3);
    check_useful_c_byte_array!(encrypted_msg, encrypted_len, ErrorCode::CommonInvalidParam4, ErrorCode::CommonInvalidParam5);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam6);

    trace!("indy_crypto_anon_decrypt: entities >>> wallet_handle: {:?}, recipient_vk: {:?}, encrypted_msg: {:?}, encrypted_len: {:?}",
           wallet_handle, recipient_vk, encrypted_msg, encrypted_len);

    let result = CommandExecutor::instance()
        .send(Command::Crypto(CryptoCommand::AnonymousDecrypt(
            wallet_handle,
            recipient_vk,
            encrypted_msg,
            Box::new(move |result| {
                let (err, msg) = result_to_err_code_1!(result, Vec::new());
                trace!("indy_crypto_anon_decrypt: msg: {:?}", msg);
                let (msg_data, msg_len) = ctypes::vec_to_pointer(&msg);
                cb(command_handle, err, msg_data, msg_len)
            })
        )));

    let res = result_to_err_code!(result);

    trace!("indy_crypto_anon_decrypt: <<< res: {:?}", res);

    res
}