use blake2::{Blake2b512, Digest};
use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::ChaCha20;
use std::slice;

#[no_mangle]
pub extern "C" fn derive_key(
    seed: u64,
    tick: u64,
    salt_ptr: *const u8,
    salt_len: usize,
    out_ptr: *mut u8,
    out_len: usize,
) -> bool {
    let salt = unsafe { slice::from_raw_parts(salt_ptr, salt_len) };
    let out = unsafe { slice::from_raw_parts_mut(out_ptr, out_len) };

    let mut hasher = Blake2b512::new();
    hasher.update(seed.to_le_bytes());
    hasher.update(tick.to_le_bytes());
    hasher.update(salt);

    let result = hasher.finalize();
    if out_len > result.len() {
        return false;
    }
    out.copy_from_slice(&result[..out_len]);

    true
}

#[no_mangle]
pub extern "C" fn generate_keystream(
    key_ptr: *const u8,
    key_len: usize,
    salt_ptr: *const u8,
    salt_len: usize,
    out_ptr: *mut u8,
    out_len: usize,
) -> bool {
    if key_len < 32 {
        return false;
    }

    let key = unsafe { slice::from_raw_parts(key_ptr, key_len) };
    let salt = unsafe { slice::from_raw_parts(salt_ptr, salt_len) };
    let out = unsafe { slice::from_raw_parts_mut(out_ptr, out_len) };

    let mut nonce_hasher = Blake2b512::new();
    nonce_hasher.update(salt);
    let nonce_hash = nonce_hasher.finalize();

    let key_array: [u8; 32] = key[..32].try_into().unwrap();
    let nonce_array: [u8; 12] = nonce_hash[..12].try_into().unwrap();

    let mut cipher = ChaCha20::new(&key_array.into(), &nonce_array.into());
    cipher.apply_keystream(out);

    true
}

#[no_mangle]
pub extern "C" fn encrypt_xor(
    input_ptr: *const u8,
    len: usize,
    key_ptr: *const u8,
    out_ptr: *mut u8,
) {
    let input = unsafe { slice::from_raw_parts(input_ptr, len) };
    let key = unsafe { slice::from_raw_parts(key_ptr, len) };
    let out = unsafe { slice::from_raw_parts_mut(out_ptr, len) };

    for i in 0..len {
        out[i] = input[i] ^ key[i];
    }
}

#[no_mangle]
pub extern "C" fn fast_hash(
    data_ptr: *const u8,
    data_len: usize,
    out_ptr: *mut u8,
    out_len: usize,
) -> bool {
    let data = unsafe { slice::from_raw_parts(data_ptr, data_len) };
    let out = unsafe { slice::from_raw_parts_mut(out_ptr, out_len) };

    let mut hasher = Blake2b512::new();
    hasher.update(data);
    let result = hasher.finalize();

    if out_len > result.len() {
        return false;
    }
    out.copy_from_slice(&result[..out_len]);
    true
}
