use web3::contract::tokens::{Detokenize, Tokenize};

extern "C" {
    fn __k3_data_sc_call(sc_address_ptr: u32, fn_data_ptr: u32) -> u32;
    fn __k3_data_sc_query(sc_address_ptr: u32, fn_data_ptr: u32) -> u32;
}

pub fn call(address: &str, abi: &str, fn_name: &str, params: impl Tokenize) -> String {
    let abi = web3::ethabi::Contract::load(abi.as_bytes()).expect("Invalid ABI");
    let fn_data = abi
        .function(fn_name)
        .expect("Undeclared function name")
        .encode_input(&params.into_tokens())
        .unwrap();

    let mut address_buf = (address.len() as u32).to_le_bytes().to_vec();
    address_buf.extend_from_slice(address.as_bytes());
    let mut fn_data_buf = (fn_data.len() as u32).to_le_bytes().to_vec();
    fn_data_buf.extend_from_slice(&fn_data);

    unsafe {
        let tx_hash_ptr =
            __k3_data_sc_call(address_buf.as_ptr() as u32, fn_data_buf.as_ptr() as u32) as *mut u8;
        let len = u32::from_le_bytes(
            std::slice::from_raw_parts(tx_hash_ptr, 4)
                .try_into()
                .unwrap(),
        );
        let mut tx_hash = vec![0u8; len as usize];
        tx_hash.copy_from_slice(std::slice::from_raw_parts(tx_hash_ptr.add(4), len as usize));
        String::from_utf8(tx_hash).unwrap()
    }
}

pub fn query<R: Detokenize>(address: &str, abi: &str, fn_name: &str, params: impl Tokenize) -> R {
    let abi = web3::ethabi::Contract::load(abi.as_bytes()).expect("Invalid ABI");
    let function = abi.function(fn_name).expect("Undeclared function name");
    let fn_data = function.encode_input(&params.into_tokens()).unwrap();

    let mut address_buf = (address.len() as u32).to_le_bytes().to_vec();
    address_buf.extend_from_slice(address.as_bytes());
    let mut fn_data_buf = (fn_data.len() as u32).to_le_bytes().to_vec();
    fn_data_buf.extend_from_slice(&fn_data);

    let return_data = unsafe {
        let return_data_ptr =
            __k3_data_sc_query(address_buf.as_ptr() as u32, fn_data_buf.as_ptr() as u32) as *mut u8;
        let len = u32::from_le_bytes(
            std::slice::from_raw_parts(return_data_ptr, 4)
                .try_into()
                .unwrap(),
        );
        let mut return_data = vec![0u8; len as usize];
        return_data.copy_from_slice(std::slice::from_raw_parts(
            return_data_ptr.add(4),
            len as usize,
        ));
        return_data
    };
    let output = function.decode_output(&return_data).unwrap();
    R::from_tokens(output).unwrap()
}
