//! Compile, validate, instantiate, serialize, and destroy modules.

use crate::{
    error::{update_last_error, CApiError},
    export::wasmer_import_export_kind,
    import::wasmer_import_t,
    instance::wasmer_instance_t,
    wasmer_byte_array, wasmer_result_t,
};
use libc::c_int;
use std::{collections::HashMap, slice};
use wasmer_runtime::{compile, default_compiler, Global, ImportObject, Memory, Module, Table};
use wasmer_runtime_core::{cache::Artifact, export::Export, import::Namespace, load_cache_with};

#[repr(C)]
pub struct wasmer_module_t;

#[repr(C)]
pub struct wasmer_serialized_module_t;

/// Creates a new Module from the given wasm bytes.
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub unsafe extern "C" fn wasmer_compile(
    module: *mut *mut wasmer_module_t,
    wasm_bytes: *mut u8,
    wasm_bytes_len: u32,
) -> wasmer_result_t {
    let bytes: &[u8] = slice::from_raw_parts_mut(wasm_bytes, wasm_bytes_len as usize);
    let result = compile(bytes);
    let new_module = match result {
        Ok(instance) => instance,
        Err(error) => {
            update_last_error(error);
            return wasmer_result_t::WASMER_ERROR;
        }
    };
    *module = Box::into_raw(Box::new(new_module)) as *mut wasmer_module_t;
    wasmer_result_t::WASMER_OK
}

/// Returns true for valid wasm bytes and false for invalid bytes
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub unsafe extern "C" fn wasmer_validate(wasm_bytes: *const u8, wasm_bytes_len: u32) -> bool {
    if wasm_bytes.is_null() {
        return false;
    }
    let bytes: &[u8] = slice::from_raw_parts(wasm_bytes, wasm_bytes_len as usize);

    wasmer_runtime_core::validate(bytes)
}

/// Creates a new Instance from the given module and imports.
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub unsafe extern "C" fn wasmer_module_instantiate(
    module: *const wasmer_module_t,
    instance: *mut *mut wasmer_instance_t,
    imports: *mut wasmer_import_t,
    imports_len: c_int,
) -> wasmer_result_t {
    let imports: &[wasmer_import_t] = slice::from_raw_parts(imports, imports_len as usize);
    let mut import_object = ImportObject::new();
    let mut namespaces = HashMap::new();
    for import in imports {
        let module_name = slice::from_raw_parts(
            import.module_name.bytes,
            import.module_name.bytes_len as usize,
        );
        let module_name = if let Ok(s) = std::str::from_utf8(module_name) {
            s
        } else {
            update_last_error(CApiError {
                msg: "error converting module name to string".to_string(),
            });
            return wasmer_result_t::WASMER_ERROR;
        };
        let import_name = slice::from_raw_parts(
            import.import_name.bytes,
            import.import_name.bytes_len as usize,
        );
        let import_name = if let Ok(s) = std::str::from_utf8(import_name) {
            s
        } else {
            update_last_error(CApiError {
                msg: "error converting import_name to string".to_string(),
            });
            return wasmer_result_t::WASMER_ERROR;
        };

        let namespace = namespaces.entry(module_name).or_insert_with(Namespace::new);

        let export = match import.tag {
            wasmer_import_export_kind::WASM_MEMORY => {
                let mem = import.value.memory as *mut Memory;
                Export::Memory((&*mem).clone())
            }
            wasmer_import_export_kind::WASM_FUNCTION => {
                let func_export = import.value.func as *mut Export;
                (&*func_export).clone()
            }
            wasmer_import_export_kind::WASM_GLOBAL => {
                let global = import.value.global as *mut Global;
                Export::Global((&*global).clone())
            }
            wasmer_import_export_kind::WASM_TABLE => {
                let table = import.value.table as *mut Table;
                Export::Table((&*table).clone())
            }
        };
        namespace.insert(import_name, export);
    }
    for (module_name, namespace) in namespaces.into_iter() {
        import_object.register(module_name, namespace);
    }

    let module = &*(module as *const Module);
    let new_instance = match module.instantiate(&import_object) {
        Ok(instance) => instance,
        Err(error) => {
            update_last_error(error);
            return wasmer_result_t::WASMER_ERROR;
        }
    };

    *instance = Box::into_raw(Box::new(new_instance)) as *mut wasmer_instance_t;
    wasmer_result_t::WASMER_OK
}

/// Serialize the given Module.
///
/// The caller owns the object and should call `wasmer_serialized_module_destroy` to free it.
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub unsafe extern "C" fn wasmer_module_serialize(
    serialized_module: *mut *mut wasmer_serialized_module_t,
    module: *const wasmer_module_t,
) -> wasmer_result_t {
    let module = &*(module as *const Module);

    match module.cache() {
        Ok(artifact) => match artifact.serialize() {
            Ok(serialized_artifact) => {
                *serialized_module = Box::into_raw(Box::new(serialized_artifact)) as _;

                wasmer_result_t::WASMER_OK
            }
            Err(_) => {
                update_last_error(CApiError {
                    msg: "Failed to serialize the module artifact".to_string(),
                });
                wasmer_result_t::WASMER_ERROR
            }
        },
        Err(_) => {
            update_last_error(CApiError {
                msg: "Failed to serialize the module".to_string(),
            });
            wasmer_result_t::WASMER_ERROR
        }
    }
}

/// Get bytes of the serialized module.
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub unsafe extern "C" fn wasmer_serialized_module_bytes(
    serialized_module: *const wasmer_serialized_module_t,
) -> wasmer_byte_array {
    let serialized_module = &*(serialized_module as *const &[u8]);

    wasmer_byte_array {
        bytes: serialized_module.as_ptr(),
        bytes_len: serialized_module.len() as u32,
    }
}

/// Transform a sequence of bytes into a serialized module.
///
/// The caller owns the object and should call `wasmer_serialized_module_destroy` to free it.
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub unsafe extern "C" fn wasmer_serialized_module_from_bytes(
    serialized_module: *mut *mut wasmer_serialized_module_t,
    serialized_module_bytes: *const u8,
    serialized_module_bytes_length: u32,
) -> wasmer_result_t {
    if serialized_module.is_null() {
        update_last_error(CApiError {
            msg: "`serialized_module_bytes` pointer is null".to_string(),
        });
        return wasmer_result_t::WASMER_ERROR;
    }

    let serialized_module_bytes: &[u8] = slice::from_raw_parts(
        serialized_module_bytes,
        serialized_module_bytes_length as usize,
    );

    *serialized_module = Box::into_raw(Box::new(serialized_module_bytes)) as _;
    wasmer_result_t::WASMER_OK
}

/// Deserialize the given serialized module.
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub unsafe extern "C" fn wasmer_module_deserialize(
    module: *mut *mut wasmer_module_t,
    serialized_module: *const wasmer_serialized_module_t,
) -> wasmer_result_t {
    if serialized_module.is_null() {
        update_last_error(CApiError {
            msg: "`serialized_module` pointer is null".to_string(),
        });
        return wasmer_result_t::WASMER_ERROR;
    }

    let serialized_module: &[u8] = &*(serialized_module as *const &[u8]);

    match Artifact::deserialize(serialized_module) {
        Ok(artifact) => match load_cache_with(artifact, &default_compiler()) {
            Ok(deserialized_module) => {
                *module = Box::into_raw(Box::new(deserialized_module)) as _;
                wasmer_result_t::WASMER_OK
            }
            Err(_) => {
                update_last_error(CApiError {
                    msg: "Failed to compile the serialized module".to_string(),
                });
                wasmer_result_t::WASMER_ERROR
            }
        },
        Err(_) => {
            update_last_error(CApiError {
                msg: "Failed to deserialize the module".to_string(),
            });
            wasmer_result_t::WASMER_ERROR
        }
    }
}

/// Frees memory for the given serialized Module.
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub extern "C" fn wasmer_serialized_module_destroy(
    serialized_module: *mut wasmer_serialized_module_t,
) {
    if !serialized_module.is_null() {
        unsafe { Box::from_raw(serialized_module as *mut &[u8]) };
    }
}

/// Frees memory for the given Module
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub extern "C" fn wasmer_module_destroy(module: *mut wasmer_module_t) {
    if !module.is_null() {
        unsafe { Box::from_raw(module as *mut Module) };
    }
}
