// Copyright 2026 Jordan Castro <jordan@grupojvm.com>
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
//

// Doc comment convention:
// OWNED: the host/caller owns the value and must free it.
// TRANSFER: the value is transfered from host to library.
// BORROW: the value is borrowed by the library.

use etffi::{borrow_string, create_raw_string, free_raw_string, ptr_magic::PtrMagic};
use shared::{func::pxs_Func, var::pxs_Var};
use std::{
    ffi::{CString, c_char, c_void},
    ptr,
    sync::Arc,
};

#[cfg(feature = "js")]
use crate::js::JSScripting;
#[cfg(feature = "lua")]
use crate::lua::LuaScripting;
#[cfg(feature = "python")]
use crate::python::PythonScripting;

use crate::shared::{
    PXS_PTR_NAME, PixelScript,
    arena::pxs_PixelArena,
    func::{clear_function_lookup, lookup_add_function},
    module::pxs_Module,
    object::{ObjectFlags, clear_object_lookup, lookup_add_object, pxs_PixelObject},
    pxs_LoadFileFn, pxs_Opaque, pxs_ReadDirFn, pxs_Runtime, set_read_dir, set_read_file,
    var::{ObjectMethods, pxs_DeleterFn, pxs_VarList, pxs_VarT, pxs_VarType},
};

pub mod shared;

#[cfg(feature = "include-core")]
pub mod core;
#[cfg(feature = "js")]
pub mod js;
#[cfg(feature = "lua")]
pub mod lua;
#[cfg(feature = "python")]
pub mod python;

/// Assert that the module is initiated.
macro_rules! assert_initiated {
    () => {{
        unsafe {
            assert!(IS_INIT, "Pixel script library is not initialized.");
        }
    }};
}

/// Use a backend based on a runtime.
macro_rules! with_backend {
    ($runtime:expr, $backend_alias:ident => $body:block) => {
        match $runtime {
            #[cfg(feature = "python")]
            pxs_Runtime::pxs_Python => {
                type $backend_alias = PythonScripting;
                $body
            }
            #[cfg(feature = "lua")]
            pxs_Runtime::pxs_Lua => {
                type $backend_alias = LuaScripting;
                $body
            }
            #[cfg(feature = "js")]
            pxs_Runtime::pxs_JavaScript => {
                type $backend_alias = JSScripting;
                $body
            }
            _ => panic!("Runtime not enabled"),
        }
    };
}

/// Is initialized?
static mut IS_INIT: bool = false;
/// Is killed?
static mut IS_KILLED: bool = false;

/// Current pixelscript version.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_version() -> u32 {
    pxs_debug!("pxs_version");
    let major = 0;
    let minor = 6;
    let patch = 3;
    (major << 16) | (minor << 8) | patch
}

/// Initialize the PixelScript runtime.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_initialize() {
    pxs_debug!("pxs_initialize");
    unsafe {
        if IS_KILLED {
            panic!("Once finalized, PixelScript can not be initalized again.");
        }
        if !IS_INIT {
            with_feature!("lua", {
                LuaScripting::start();
            });

            with_feature!("python", {
                PythonScripting::start();
            });

            with_feature!("js", {
                JSScripting::start();
            });
        }
        IS_INIT = true;
    }
}

/// Finalize the PixelScript runtime.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_finalize() {
    pxs_debug!("pxs_finalize");
    assert_initiated!();

    unsafe {
        if IS_KILLED {
            panic!("Can not finalize the runtime twice.");
        }
        IS_KILLED = true;
    }

    // Drop function lookup
    clear_function_lookup();
    // Drop object lookup
    clear_object_lookup();

    with_feature!("lua", {
        LuaScripting::stop();
    });

    with_feature!("python", {
        PythonScripting::stop();
    });

    with_feature!("js", {
        JSScripting::stop();
    });
}

#[unsafe(no_mangle)]
/// Execute code in a runtime. Will return a pxs_VarT. Null means no error, otherwise error.
///
/// return:OWNED
pub extern "C" fn pxs_exec(
    runtime: pxs_Runtime,
    code: *const c_char,
    file_name: *const c_char,
) -> pxs_VarT {
    pxs_debug!("pxs_exec");
    assert_initiated!();

    if code.is_null() || file_name.is_null() {
        pxs_Var::new_exception("code or file_name are null").into_raw();
    }

    let rcode = borrow_string!(code);
    let rfile_name = borrow_string!(file_name);

    if rcode.is_empty() || rfile_name.is_empty() {
        return pxs_Var::new_exception("code or file_name are empty strings").into_raw();
    }

    with_backend!(runtime, Backend => {
        let res = Backend::execute(rcode, rfile_name);
        if res.is_err() {
            pxs_Var::new_exception(res.unwrap_err().to_string()).into_raw()
        } else {
            res.unwrap().into_raw()
        }
    })
}

/// Free the string created by the pixelscript library
///
/// string:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_freestr(string: *mut c_char) {
    pxs_debug!("pxs_freestr");
    assert_initiated!();
    if !string.is_null() {
        unsafe {
            // Let the string go out of scope to be dropped
            let _ = CString::from_raw(string);
        }
    }
}

/// Create a new pixelscript Module.
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newmod(name: *const c_char) -> *mut pxs_Module {
    pxs_debug!("pxs_newmod");
    assert_initiated!();
    if name.is_null() {
        return ptr::null_mut();
    }
    let name_str = borrow_string!(name);

    pxs_Module::new(name_str.to_owned()).into_raw()
}

/// Add a callback to a module.
///
/// Pass in the modules pointer and callback paramaters.
///
/// module_ptr:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_addfunc(module_ptr: *mut pxs_Module, name: *const c_char, func: pxs_Func) {
    pxs_debug!("pxs_addfunc");
    assert_initiated!();
    if module_ptr.is_null() {
        return;
    }

    if name.is_null() {
        return;
    }

    // Get actual data
    let module = unsafe { pxs_Module::from_borrow(module_ptr) };
    let name_str = borrow_string!(name);

    // Mangle the name
    let full_name = format!("_{}{}", module.name, name_str);

    // Check that module DOES not already contain this name
    if !module
        .callbacks
        .iter()
        .map(|cbk| cbk.full_name == full_name)
        .all(|v| v == false)
    {
        panic!("Function with name: {full_name} is already defined.");
        #[allow(unreachable_code)]
        return;
    }

    // Save the callback
    let idx = lookup_add_function(&full_name, func);

    // Now add callback
    module.add_callback(name_str, &full_name, idx);
}

/// Add a Varible to a module.
///
/// Pass in the module pointer and variable params.
///
/// Variable ownership is transfered.
///
/// module_ptr:BORROW
/// variable:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_addvar(
    module_ptr: *mut pxs_Module,
    name: *const c_char,
    variable: *mut pxs_Var,
) {
    pxs_debug!("pxs_addvar");
    assert_initiated!();
    if module_ptr.is_null() || name.is_null() || variable.is_null() {
        return;
    }

    let module = unsafe { pxs_Module::from_borrow(module_ptr) };
    let name_str = borrow_string!(name);

    // Check if name already exists
    if !module.variables.iter().all(|v| v.name != name_str) {
        panic!("Variable name: {name_str} is already defined.");
        #[allow(unreachable_code)]
        return;
    }

    // Now add variable
    module.add_variable(name_str, own_var!(variable));
}

/// Add a Module to a Module
///
/// This transfers ownership.
///
/// parent_ptr:BORROW
/// child_ptr:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_add_submod(parent_ptr: *mut pxs_Module, child_ptr: *mut pxs_Module) {
    pxs_debug!("pxs_add_submod");

    assert_initiated!();
    if parent_ptr.is_null() || child_ptr.is_null() {
        return;
    }

    let parent = unsafe { pxs_Module::from_borrow(parent_ptr) };
    // Own child
    let mut child = pxs_Module::from_raw(child_ptr);
    child.name = format!("{}.{}", parent.name, child.name);

    parent.add_module(Arc::new(child));

    // Child is now owned by parent
}

/// Add the module finally to the runtime.
///
/// After this you can forget about the ptr since PM handles it.
///
/// module_ptr:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_addmod(module_ptr: *mut pxs_Module) {
    pxs_debug!("pxs_addmod");

    assert_initiated!();
    if module_ptr.is_null() {
        return;
    }

    let module = Arc::new(pxs_Module::from_raw(module_ptr));

    // LUA
    with_feature!("lua", {
        LuaScripting::add_module(Arc::clone(&module));
    });
    with_feature!("python", {
        PythonScripting::add_module(Arc::clone(&module));
    });
    with_feature!("js", {
        JSScripting::add_module(Arc::clone(&module));
    });

    // Module gets dropped here, and that is good!
}

/// Optionally free a module if you changed your mind.
///
/// module_ptr:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_freemod(module_ptr: *mut pxs_Module) {
    pxs_debug!("pxs_freemod");
    assert_initiated!();

    if module_ptr.is_null() {
        return;
    }

    let _ = pxs_Module::from_raw(module_ptr);
}

/// Create a new object with a Type.
/// 
/// This is the same as `pxs_newobject` but it defines a `type` on the `pxs_PixelObject`.
/// 
/// This will not cause UB. Retrieve the host pointer using `pxs_gettype`. A `type_id` < 0 means no type.
/// 
/// ptr:OWNED
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newtype(
    ptr: pxs_Opaque,
    free_method: pxs_DeleterFn,
    type_name: *const c_char,
    type_id: i32
) -> *mut pxs_PixelObject {
    pxs_debug!("pxs_newtype");
    assert_initiated!();
    if ptr.is_null() || type_name.is_null() {
        return ptr::null_mut();
    }

    let type_name = borrow_string!(type_name);
    pxs_PixelObject::new_type(ptr, free_method, type_name, type_id).into_raw()
}

/// Create a new object.
///
/// This should only be used within a PixelScript function callback. I.e. a constructor.
///
/// This must be wrapped in a `pxs_newhost` before use within a callback. If setting to a variable, this is done automatically for you.
///
/// Can return nullptr.
///
/// ptr:OWNED
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newobject(
    ptr: pxs_Opaque,
    free_method: pxs_DeleterFn,
    type_name: *const c_char,
) -> *mut pxs_PixelObject {
    pxs_debug!("pxs_newobject");
    pxs_newtype(ptr, free_method, type_name, -1)
}

/// Add a callback to a object
fn add_callback_to_object(object: &mut pxs_PixelObject, name: &str, callback: pxs_Func, flags: u8) {
    // Add to function lookup
    let full_name = format!("_{}{}", object.type_name, name);
    let idx = lookup_add_function(full_name.as_str(), callback);

    object.add_callback(name, full_name.as_str(), idx, flags);
}

/// Add a callback to a object.
///
/// object_ptr:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_object_addfunc(
    object_ptr: *mut pxs_PixelObject,
    name: *const c_char,
    callback: pxs_Func,
) {
    pxs_debug!("pxs_object_addfunc");
    assert_initiated!();

    if object_ptr.is_null() || name.is_null() {
        return;
    }

    // Borrow ptr
    let object_borrow = unsafe { pxs_PixelObject::from_borrow(object_ptr) };
    let name_borrow = borrow_string!(name);

    add_callback_to_object(
        object_borrow,
        name_borrow,
        callback,
        ObjectFlags::UsesId as u8,
    );
}

/// Add a callback to a object and make it use the language pointer rather than _pxs_ptr idx.
///
/// object_ptr:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_object_add_reffunc(
    object_ptr: *mut pxs_PixelObject,
    name: *const c_char,
    callback: pxs_Func,
) {
    pxs_debug!("pxs_object_addfunc");
    assert_initiated!();

    if object_ptr.is_null() || name.is_null() {
        return;
    }

    // Borrow ptr
    let object_borrow = unsafe { pxs_PixelObject::from_borrow(object_ptr) };
    let name_borrow = borrow_string!(name);

    add_callback_to_object(
        object_borrow,
        name_borrow,
        callback,
        ObjectFlags::UsesRef as u8,
    );
}

/// Add a property to a object. Expects a name and a callback. The same as `pxs_object_addfunc` but that it saves
/// it differently for the backend to convert it into a property.
///
/// ptr:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_object_addprop(
    ptr: *mut pxs_PixelObject,
    name: *const c_char,
    callback: pxs_Func,
) {
    pxs_debug!("pxs_object_addprop");
    assert_initiated!();

    if ptr.is_null() || name.is_null() {
        return;
    }

    let object_borrow = unsafe { pxs_PixelObject::from_borrow(ptr) };
    let name_borrow = borrow_string!(name);

    let flags = ObjectFlags::UsesId as u8 | ObjectFlags::IsProp as u8;

    add_callback_to_object(object_borrow, name_borrow, callback, flags);
}

/// Add a object to a Module.
///
/// This essentially makes it so that when constructing this Module, this object is instanced.
/// This works by adding a public factory function with the type name. But the type name
/// is mangled (_module_typename).
///
/// In Lua:
/// ```lua
/// -- Let's say we have a object "Person"
/// local p = Person("Jordan", 23)
/// p:set_name("Jordan Castro")
/// local name = p:get_name()
///
/// -- Although you could also do
/// local p = Person("Jordan", 23)
/// p.set_name(p, "Jordan") -- You get the idea
/// ```
///
/// In Python:
/// ```python
/// p = Person("Jordan", 23)
/// # use '.' instead of ':'
/// # etc
/// ```
///
/// In JS the same as Python and Lua:
/// ```js
/// let p = Person("Jordan", 23);
/// // Same as Python
/// // etc
/// ```
///
/// module_ptr:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_addobject(
    module_ptr: *mut pxs_Module,
    name: *const c_char,
    object_constructor: pxs_Func,
) {
    pxs_debug!("pxs_addobject");
    // Save module to object
    pxs_addfunc(module_ptr, name, object_constructor);
}

/// Make a new Var string.
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newstring(str: *const c_char) -> pxs_VarT {
    pxs_debug!("pxs_newstring");
    let val = borrow_string!(str);
    // Clone string
    pxs_Var::new_string(val.to_string().clone()).into_raw()
}

/// Make a new Null var.
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newnull() -> pxs_VarT {
    pxs_debug!("pxs_newnull");
    pxs_Var::new_null().into_raw()
}

/// Make a new HostObject var.
///
/// Transfers ownership
///
/// pixel_object:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newhost(pixel_object: *mut pxs_PixelObject) -> pxs_VarT {
    pxs_debug!("pxs_newhost");
    assert_initiated!();

    if pixel_object.is_null() {
        return pxs_Var::null_param_ep("PixelObject").into_raw();
    }

    // Own the pixel_object
    let pixel_owned = pxs_PixelObject::from_raw(pixel_object);
    // Arc it
    let pixel_arc = Arc::new(pixel_owned);

    // Create it in the system
    let idx = lookup_add_object(Arc::clone(&pixel_arc));

    pxs_Var::new_host_object(idx).into_raw()
}

/// Create a new variable int. (i64)
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newint(val: i64) -> pxs_VarT {
    pxs_debug!("pxs_newint");
    pxs_Var::new_i64(val).into_raw()
}
/// Create a new variable uint. (u64)
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newuint(val: u64) -> pxs_VarT {
    pxs_debug!("pxs_newuint");
    pxs_Var::new_u64(val).into_raw()
}
/// Create a new variable bool.
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newbool(val: bool) -> pxs_VarT {
    pxs_debug!("pxs_newbool");
    pxs_Var::new_bool(val).into_raw()
}

/// Create a new variable float. (f64)
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newfloat(val: f64) -> pxs_VarT {
    pxs_debug!("pxs_newfloat");
    pxs_Var::new_f64(val).into_raw()
}

/// Call a function on a object, and use a Enum for runtime rather than a var.
///
/// var is self.
///
/// var:BORROW
/// args:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_object_callrt(
    runtime: pxs_Runtime,
    var: *mut pxs_Var,
    method: *const c_char,
    args: *mut pxs_Var,
) -> pxs_VarT {
    pxs_debug!("pxs_object_callrt");
    pxs_objectcall(
        pxs_Var::new_i64(runtime as i64).into_raw(),
        var,
        method,
        args,
    )
}

/// Object call.
///
/// All memory is borrowed except for args. But the var returned need to be freed on host side if not returned by a function.
///
/// You can get the runtime from the first Var in any callback.
///
/// Example
/// ```C
///     // Inside a Var* method
///     Var* obj = pxs_listget(args, 1);
///     Var name = pxs_object_call()
/// ```
///
/// runtime:BORROW
/// var:BORROW
/// args:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_objectcall(
    runtime: *mut pxs_Var,
    var: *mut pxs_Var,
    method: *const c_char,
    args: *mut pxs_Var,
) -> pxs_VarT {
    pxs_debug!("pxs_objectcall");
    assert_initiated!();

    if var.is_null() || method.is_null() || args.is_null() || runtime.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    // Borrow runtime, var, and method, and argv
    let runtime_borrow = unsafe { pxs_Var::from_borrow(runtime) };
    let var_borrow = unsafe { pxs_Var::from_borrow(var) };
    let method_borrow = borrow_string!(method);
    let args = own_var!(args);
    if !args.is_list() {
        return pxs_Var::incorrect_type_ep(pxs_VarType::pxs_List, args.tag).into_raw();
    }
    let list = args.get_list().unwrap();

    // Check that runtime is acually a int
    let runtime_id = runtime_borrow.get_i64();
    if runtime_id.is_err() {
        return pxs_Var::incorrect_types_ep(
            vec![pxs_VarType::pxs_Int64, pxs_VarType::pxs_UInt64],
            runtime_borrow.tag,
        )
        .into_raw();
    }
    let runtime_val = runtime_id.unwrap();

    let runtime = pxs_Runtime::from_i64(runtime_val);
    if runtime.is_none() {
        return pxs_Var::unkown_runtime_ep(runtime_val).into_raw();
    }
    let runtime = runtime.unwrap();

    // Ensure type
    if !var_borrow.is_object() {
        return pxs_Var::incorrect_type_ep(pxs_VarType::pxs_Object, var_borrow.tag).into_raw();
    }

    with_backend!(runtime, Backend => {
        let res = Backend::object_call(var_borrow, method_borrow, list);
        if res.is_err() {
            pxs_Var::new_exception(res.unwrap_err().to_string())
        } else {
            res.unwrap()
        }
    })
    .into_raw()
}

/// Get a int (i64) from a var.
///
/// var:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_getint(var: *mut pxs_Var) -> i64 {
    pxs_debug!("pxs_getint");
    if var.is_null() {
        return -1;
    }

    let b_var = unsafe { pxs_Var::from_borrow(var) };

    unsafe {
        match b_var.tag {
            pxs_VarType::pxs_Int64 => b_var.value.i64_val,
            pxs_VarType::pxs_UInt64 => b_var.value.u64_val as i64,
            pxs_VarType::pxs_Bool => b_var.value.bool_val.into(),
            pxs_VarType::pxs_Float64 => b_var.value.f64_val as i64,
            _ => -1,
        }
    }
}

/// Get a uint (u64)
///
/// var:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_getuint(var: *mut pxs_Var) -> u64 {
    pxs_debug!("pxs_getuint");
    if var.is_null() {
        return 0;
    }

    let b_var = unsafe { pxs_Var::from_borrow(var) };

    unsafe {
        match b_var.tag {
            pxs_VarType::pxs_Int64 => b_var.value.i64_val as u64,
            pxs_VarType::pxs_UInt64 => b_var.value.u64_val,
            pxs_VarType::pxs_Bool => b_var.value.bool_val.into(),
            pxs_VarType::pxs_Float64 => b_var.value.f64_val as u64,
            _ => 0,
        }
    }
}

/// Get a float (f64)
///
/// var:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_getfloat(var: *mut pxs_Var) -> f64 {
    pxs_debug!("pxs_getfloat");
    if var.is_null() {
        return -1.0;
    }

    let b_var = unsafe { pxs_Var::from_borrow(var) };

    unsafe {
        match b_var.tag {
            pxs_VarType::pxs_Int64 => b_var.value.i64_val as f64,
            pxs_VarType::pxs_UInt64 => b_var.value.u64_val as f64,
            pxs_VarType::pxs_Bool => b_var.value.bool_val.into(),
            pxs_VarType::pxs_Float64 => b_var.value.f64_val,
            _ => 0 as f64,
        }
    }
}

/// Get a Bool
///
/// CAN_CRASH
/// var:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_getbool(var: *mut pxs_Var) -> bool {
    pxs_debug!("pxs_getbool");
    if var.is_null() {
        return false;
    }

    unsafe { pxs_Var::from_borrow(var).get_bool().unwrap() }
}

/// Get a String
///
/// CAN_CRASH, CALLER
///
/// You have to free this memory by calling `pxs_free_str`
///
/// var:BORROW
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_getstring(var: *mut pxs_Var) -> *mut c_char {
    pxs_debug!("pxs_getstring");
    if var.is_null() {
        return ptr::null_mut();
    }

    unsafe {
        let string = pxs_Var::from_borrow(var).get_string().unwrap();
        create_raw_string!(string.clone())
    }
}

/// Check if a variable is of a type.
///
/// var:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_varis(var: *mut pxs_Var, var_type: pxs_VarType) -> bool {
    pxs_debug!("pxs_varis");
    if var.is_null() {
        return false;
    }

    let var_borrow = unsafe { pxs_Var::from_borrow(var) };

    var_borrow.tag == var_type
}

/// Set a function for reading a file.
///
/// This is used to load files via import, require, etc
#[unsafe(no_mangle)]
pub extern "C" fn pxs_set_filereader(func: pxs_LoadFileFn) {
    pxs_debug!("pxs_set_filereader");
    assert_initiated!();
    set_read_file(func);
}

/// Set a function for reading a directory.
///
/// This is used to read a dir.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_set_dirreader(func: pxs_ReadDirFn) {
    pxs_debug!("pxs_set_dirreader");
    assert_initiated!();
    set_read_dir(func);
}

/// Free a PixelScript var.
///
/// You should only free results from `pxs_object_call`
///
/// var:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_freevar(var: *mut pxs_Var) {
    pxs_debug!("pxs_freevar");
    assert_initiated!();

    if var.is_null() {
        return;
    }

    let _ = pxs_Var::from_raw(var);
}

/// Tells PixelScript that we are in a new thread.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_startthread() {
    pxs_debug!("pxs_startthread");
    assert_initiated!();
    with_feature!("lua", {
        LuaScripting::start_thread();
    });
    with_feature!("python", {
        PythonScripting::start_thread();
    });
    with_feature!("js", {
        JSScripting::start_thread();
    });
}

/// Tells PixelScript that we just stopped the most recent thread.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_stopthread() {
    pxs_debug!("pxs_stopthread");
    assert_initiated!();
    with_feature!("lua", {
        LuaScripting::stop_thread();
    });
    with_feature!("python", {
        PythonScripting::stop_thread();
    });
    with_feature!("js", {
        JSScripting::stop_thread();
    });
}

/// Clear the current threads state for all languages.
///
/// Optionally, if you want to run the garbage collector.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_clear() {
    pxs_debug!("pxs_clear");
    assert_initiated!();
    // Drop function lookup
    clear_function_lookup();
    // Drop object lookup
    clear_object_lookup();

    with_feature!("lua", {
        LuaScripting::clear();
    });
    with_feature!("python", {
        PythonScripting::clear();
    });
    with_feature!("js", {
        JSScripting::clear();
    })
}

/// Call a method within a specifed runtime.
///
/// Runtime is a `pxs_Var`.
///
/// Transfers ownership of args.
///
/// runtime:BORROW
/// args:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_call(
    runtime: *mut pxs_Var,
    method: *const c_char,
    args: *mut pxs_Var,
) -> *mut pxs_Var {
    pxs_debug!("pxs_call");
    assert_initiated!();

    if runtime.is_null() || method.is_null() || args.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    let runtime_var = unsafe { pxs_Var::from_borrow(runtime) };
    let runtime_id = pxs_getint(runtime_var);

    // Borrow runtime, var, and method, and argv
    let runtime_borrow = pxs_Runtime::from_i64(runtime_id);
    let method_borrow = borrow_string!(method);
    // Own args
    let args = own_var!(args);
    if !args.is_list() {
        return pxs_Var::incorrect_type_ep(pxs_VarType::pxs_List, args.tag).into_raw();
    }
    let list = args.get_list().unwrap();

    // Get runtime
    if let Some(rt) = runtime_borrow {
        with_backend!(rt, Backend => {
            let res = Backend::call_method(method_borrow, list);
            if res.is_err() {
                pxs_Var::new_exception(res.unwrap_err().to_string())
            } else {
                res.unwrap()
            }
        })
        .into_raw()
    } else {
        pxs_Var::unkown_runtime_ep(runtime_id).into_raw()
    }
}

/// Call a ToString method on this Var. If already a string, it won't call it.
///
/// Host must free this memory with `pxs_free_var`
///
/// runtime_var:BORROW
/// var:BORROW
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_tostring(runtime_var: *mut pxs_Var, var: *mut pxs_Var) -> *mut pxs_Var {
    pxs_debug!("pxs_tostring");
    assert_initiated!();

    if var.is_null() || runtime_var.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    // Borrow
    let b_var = unsafe { pxs_Var::from_borrow(var) };

    // If string or primative, no object calling needed
    match b_var.tag {
        pxs_VarType::pxs_Int64 => {
            let val = b_var.get_i64().unwrap();
            return pxs_Var::new_string(val.to_string()).into_raw();
        }
        pxs_VarType::pxs_UInt64 => {
            let val = b_var.get_u64().unwrap();
            return pxs_Var::new_string(val.to_string()).into_raw();
        }
        pxs_VarType::pxs_String | pxs_VarType::pxs_Exception => {
            return pxs_Var::new_string(b_var.get_string().unwrap().clone()).into_raw();
        }
        pxs_VarType::pxs_Bool => {
            return pxs_Var::new_string(b_var.get_bool().unwrap().to_string()).into_raw();
        }
        pxs_VarType::pxs_Float64 => {
            return pxs_Var::new_string(b_var.get_f64().unwrap().to_string()).into_raw();
        }
        pxs_VarType::pxs_Null => {
            return pxs_Var::new_string("null".to_string()).into_raw();
        }
        _ => {
            // Do nothing
        }
    }

    // Not a string, so let's convert
    let runtime = unsafe { pxs_Runtime::from_var_ptr(runtime_var) };
    if let Some(runtime) = runtime {
        let args = pxs_Var::new_list();
        let list = args.get_list().unwrap();
        list.add_item(b_var.shallow_copy());
        let res = match runtime {
            pxs_Runtime::pxs_Lua => {
                with_feature!("lua", { LuaScripting::call_method("tostring", list) }, {
                    return pxs_Var::feature_not_enabled_ep("lua").into_raw();
                })
            }
            pxs_Runtime::pxs_Python => {
                with_feature!("python", { PythonScripting::call_method("str", list) }, {
                    return pxs_Var::feature_not_enabled_ep("python").into_raw();
                })
            }
            pxs_Runtime::pxs_JavaScript => {
                with_feature!(
                    "js",
                    {
                        // Check variable is null.
                        if pxs_varis(var, pxs_VarType::pxs_Null) {
                            return pxs_Var::new_string("null".to_owned()).into_raw();
                        }

                        let mut empty_list = pxs_VarList::new();
                        JSScripting::object_call(b_var, "toString", &mut empty_list)
                    },
                    {
                        return pxs_Var::feature_not_enabled_ep("js").into_raw();
                    }
                )
            }
            pxs_Runtime::pxs_Wren => {
                todo!()
            }
        };

        if let Ok(res) = res {
            res.into_raw()
        } else {
            pxs_Var::new_exception(res.unwrap_err().to_string()).into_raw()
        }
    } else {
        pxs_Var::unkown_runtime_var_ep(runtime_var).into_raw()
    }
}

/// Create a new pxs_VarList.
///
/// This does not take any arguments. To add to a list, you must call `pxs_var_list_add(ptr, item)`
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newlist() -> *mut pxs_Var {
    pxs_debug!("pxs_newlist");
    assert_initiated!();

    pxs_Var::new_list().into_raw()
}

/// Add a item to a pxs_VarList.
///
/// Expects a pointer to pxs_VarList. And a pointer for the item to add (pxs_Var*)
///
/// This will take ownership of the added item. If you want to copy it instead first create a new `pxs_Var` with `pxs_var_newcopy(item)`
///
/// Will return the index added at.
///
/// list:BORROW
/// item:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_listadd(list: *mut pxs_Var, item: *mut pxs_Var) -> i32 {
    pxs_debug!("pxs_listadd");
    assert_initiated!();

    // Check null
    if list.is_null() || item.is_null() {
        return -1;
    }

    // Borrow list
    let borrow_list = unsafe { pxs_Var::from_borrow(list) };
    // Check that this is actually a list
    if !borrow_list.is_list() {
        return -1;
    }

    // and own item
    let owned_item = pxs_Var::from_raw(item);

    // Get varlist and add item
    let varlist = borrow_list.get_list().unwrap();
    varlist.add_item(owned_item);

    // Return index
    (varlist.vars.len() - 1) as i32
}

/// Get a item from a pxs_VarList.
///
/// Expcts a pointer to pxs_VarList. And a index of i32. Supports negative indexes just like in Python.
///
/// This will NOT return a cloned variable, you must NOT free it.
///
/// list:BORROW
/// return:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_listget(list: *mut pxs_Var, index: i32) -> *mut pxs_Var {
    pxs_debug!("pxs_listget");
    assert_initiated!();

    if list.is_null() {
        return pxs_Var::null_param_ep("list").into_raw();
    }

    // Get list
    let borrow_list = unsafe { pxs_Var::from_borrow(list) };
    if !borrow_list.is_list() {
        return pxs_Var::incorrect_type_ep(pxs_VarType::pxs_List, borrow_list.tag).into_raw();
    }

    // Derefernce list and get the item.
    let varlist = borrow_list.get_list().unwrap();
    if let Some(res) = varlist.get_item(index) {
        res as *const pxs_Var as *mut pxs_Var
    } else {
        return pxs_Var::item_not_found_ep().into_raw();
    }
}

/// Set a item at a specific index in a pxs_VarList.
///
/// Expects a pointer to pxs_VarList, a index of i32, and a pxs_Var. Supports negative indexes jsut like in Python.
///
/// Will take ownership of the pxs_Var.
///
/// This will return a boolean for success = true, or failure = false.
///
/// list:BORROW
/// item:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_listset(list: *mut pxs_Var, index: i32, item: *mut pxs_Var) -> bool {
    pxs_debug!("pxs_listset");
    assert_initiated!();

    if list.is_null() || item.is_null() {
        return false;
    }

    // Borrow list
    let borrow_list = unsafe { pxs_Var::from_borrow(list) };
    // Check if actually list
    if !borrow_list.is_list() {
        return false;
    }

    // Get list
    let list = borrow_list.get_list().unwrap();
    // Own item
    let owned_item = pxs_Var::from_raw(item);
    // Set in list
    list.set_item(owned_item, index)
}

/// Get length of a pxs_VarList.
///
/// Expects a pointer to a pxs_VarList
///
/// list:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_listlen(list: *mut pxs_Var) -> i32 {
    pxs_debug!("pxs_listlen");
    assert_initiated!();

    if list.is_null() {
        return -1;
    }

    let borrow_list = unsafe { pxs_Var::from_borrow(list) };
    if !borrow_list.is_list() {
        return -1;
    }

    let list = borrow_list.get_list().unwrap();

    list.vars.len() as i32
}

/// Call a `pxs_Var`s function.
///
/// Expects runtime var, var function, and args that is a List.
///
/// Transfers ownership of args.
///
/// runtime:BORROW
/// var_func:BORROW
/// args:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_varcall(
    runtime: *mut pxs_Var,
    var_func: *mut pxs_Var,
    args: *mut pxs_Var,
) -> *mut pxs_Var {
    pxs_debug!("pxs_varcall");
    assert_initiated!();

    if runtime.is_null() || var_func.is_null() || args.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    // Get the function pointer
    let borrow_func = borrow_var!(var_func);
    // Check if function
    if !borrow_func.is_function() {
        return pxs_Var::incorrect_type_ep(pxs_VarType::pxs_Function, borrow_func.tag).into_raw();
    }

    // own args
    let args = own_var!(args);
    // Check it's a list
    if !args.is_list() {
        return ptr::null_mut();
    }
    let list = args.get_list().unwrap();

    // Match the runtime
    let rt = unsafe { pxs_Runtime::from_var_ptr(runtime) };
    if let Some(runtime) = rt {
        with_backend!(runtime, Backend => {
            let res = Backend::var_call(borrow_func, list);
            if res.is_err() {
                pxs_Var::new_exception(res.unwrap_err().to_string())
            } else {
                res.unwrap()
            }
        })
        .into_raw()
    } else {
        pxs_Var::unkown_runtime_var_ep(runtime).into_raw()
    }
}

/// Copy the pxs_Var.
///
/// Memory is handled by caller
///
/// item:BORROW
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newcopy(item: *mut pxs_Var) -> *mut pxs_Var {
    pxs_debug!("pxs_newcopy");
    assert_initiated!();

    if item.is_null() {
        return pxs_Var::null_param_ep("item").into_raw();
    }

    // Borrow var
    let borrow_item = borrow_var!(item);

    // Clone the var
    let cloned_var = borrow_item.clone();
    cloned_var.into_raw()
}

/// Call a objects getter.
///
/// runtime:BORROW
/// obj:BORROW
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_objectget(runtime: pxs_VarT, obj: pxs_VarT, key: *const c_char) -> pxs_VarT {
    pxs_debug!("pxs_objectget");
    assert_initiated!();
    if runtime.is_null() || obj.is_null() || key.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    // Borrow var
    let borrow_obj = borrow_var!(obj);
    let borrow_rt = unsafe { pxs_Runtime::from_var_ptr(runtime).unwrap() };
    let borrow_key = borrow_string!(key);

    with_backend!(borrow_rt, Backend => {
        let res = Backend::get(borrow_obj, borrow_key);
        if res.is_err() {
            pxs_Var::new_exception(res.unwrap_err().to_string())
        } else {
            res.unwrap()
        }
    })
    .into_raw()
}

/// Call a objects setter.
///
/// value ownership is transfered.
///
/// runtime:BORROW
/// obj:BORROW
/// value:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_objectset(
    runtime: pxs_VarT,
    obj: pxs_VarT,
    key: *const c_char,
    value: pxs_VarT,
) -> bool {
    pxs_debug!("pxs_objectset");
    assert_initiated!();

    if runtime.is_null() || obj.is_null() || key.is_null() || value.is_null() {
        return false;
    }

    // Borrow
    let rt = unsafe { pxs_Runtime::from_var_ptr(runtime).unwrap() };
    let borrow_obj = borrow_var!(obj);
    let borrow_key = borrow_string!(key);
    // own
    let owned_value = own_var!(value);

    with_backend!(rt, Backend => {
        let res = Backend::set(borrow_obj, borrow_key, &owned_value);
        if res.is_err() {
            false
            // pxs_Var::new_exception(res.unwrap_err().to_string())
        } else {
            true
            // res.unwrap()
        }
    })
}

/// Evaluate code. This will return a `pxs_VarT`.
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_eval(script: *const c_char, rt: pxs_Runtime) -> pxs_VarT {
    pxs_debug!("pxs_eval");
    if script.is_null() {
        return pxs_Var::null_param_ep("script").into_raw();
    }

    let script = borrow_string!(script);

    with_backend!(rt, Backend => {
        let res = Backend::eval(script, "<eval>");
        if res.is_err() {
            pxs_Var::new_exception(res.unwrap_err().to_string())
        } else {
            res.unwrap()
        }
    })
    .into_raw()
}

/// Evaluate named code. This will return a `pxs_VarT`.
/// 
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_evalnamed(script: *const c_char, name: *const c_char, rt: pxs_Runtime) -> pxs_VarT {
    pxs_debug!("pxs_evalnamed");
    if script.is_null() || name.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    let script = borrow_string!(script);
    let name = borrow_string!(name);

    with_backend!(rt, Backend => {
        let res = Backend::eval(script, name);
        if res.is_err() {
            pxs_Var::new_exception(res.unwrap_err())
        } else {
            res.unwrap()
        }
    }).into_raw()
}

/// Add a factory variable. This variable will be instantiated once at module startup.
///
/// Args should not contain runtime. It gets added automatically.
///
/// Basically does:
/// ```python
/// var_name = callback(args)
/// ```
///
/// args:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newfactory(func: pxs_Func, args: *mut pxs_Var) -> pxs_VarT {
    pxs_debug!("pxs_newfactory");
    assert_initiated!();

    if args.is_null() {
        return pxs_Var::null_param_ep("args").into_raw();
    }
    // Check args is list
    let var = pxs_Var::from_raw(args);
    if !var.is_list() {
        return ptr::null_mut();
    }

    pxs_Var::new_factory(func, var).into_raw()
}

/// Get the `_pxs_ptr` of a `pxs_HostObject`. And type check it against `type_id`.
/// 
/// if `type_id` < 0, no type checking is done.
/// 
/// runtime: BORROW
/// var: BORROW
/// return: BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_gettype(runtime: pxs_VarT, var: pxs_VarT, type_id: i32) -> *mut c_void {
    pxs_debug!("pxs_gettype");
    assert_initiated!();
    if runtime.is_null() || var.is_null() {
        return ptr::null_mut();
    }

    let borrow_var = unsafe { pxs_Var::from_borrow(var) };

    if borrow_var.is_object() {
        // Check for ptr
        let key = create_raw_string!(PXS_PTR_NAME);
        let idx_var = pxs_objectget(runtime, var, key);
        unsafe {
            free_raw_string!(key);
        }
        if idx_var.is_null() {
            return ptr::null_mut();
        }

        let idx_own = pxs_Var::from_raw(idx_var);
        if type_id >= 0 {
            let t = idx_own.get_pxs_type();
            if t != type_id {
                return ptr::null_mut();
            }
        }
        let host_ptr = idx_own.get_host_ptr();

        if host_ptr.is_null() {
            pxs_debug!("Host pointer is null. The variable is: {:#?}", idx_own);
        }

        host_ptr
    } else if borrow_var.is_i64() || borrow_var.is_u64() || borrow_var.is_host_object() {
        if type_id >= 0 {
            let t = borrow_var.get_pxs_type();
            if t != type_id {
                return ptr::null_mut();
            }
        }
        borrow_var.get_host_ptr()
    } else if borrow_var.is_factory() {
        // Get the factory
        let factory = borrow_var.get_factory().unwrap();
        // Call the function
        let res = factory.call(unsafe { pxs_Runtime::from_var_ptr(runtime) }.unwrap());
        // Now we should have our object, so just call pxs_gettype on it again
        let res_raw = res.into_raw();
        let host = pxs_gettype(runtime, res_raw, type_id);
        // Free res_raw
        let _ = pxs_Var::from_raw(res_raw);
        // return host
        host
    } else {
        ptr::null_mut()
    }
}

/// Get the HostPointer universally supported for:
/// - Objects that have `_pxs_ptr` assigned.
/// - Integers (signed and unsigned)
/// - HostObjects
/// - Factories (this will call it on the fly.)
///
/// All other types will return NULL.
///
/// runtime:BORROW
/// var:BORROW
/// return:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_gethost(runtime: pxs_VarT, var: pxs_VarT) -> *mut c_void {
    pxs_debug!("pxs_gethost");
    pxs_gettype(runtime, var, -1)
}

/// Return a string rep of the `pxs_Var`.
///
/// String must be freed via `pxs_freestr`.
///
/// var:BORROW
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_debugvar(var: pxs_VarT) -> *mut c_char {
    pxs_debug!("pxs_debugvar");
    if var.is_null() {
        create_raw_string!("NULL")
    } else {
        let borrow_var = unsafe { pxs_Var::from_borrow(var) };
        let str = format!("{:#?}", borrow_var);
        create_raw_string!(str)
    }
}

/// Create a `pxs_Exception`.
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newexception(msg: *const c_char) -> pxs_VarT {
    pxs_debug!("pxs_newexception");
    if msg.is_null() {
        return pxs_Var::null_param_ep("msg").into_raw();
    }

    // Borrow msg
    let bmsg = borrow_string!(msg);
    pxs_Var::new_exception(bmsg.to_string().clone()).into_raw()
}

/// Get a variable reference from its name
///
/// rt:BORROW
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_var_fromname(rt: pxs_VarT, name: *const c_char) -> pxs_VarT {
    pxs_debug!("pxs_var_fromname");
    assert_initiated!();
    if name.is_null() {
        return pxs_Var::null_param_ep("name").into_raw();
    }

    let bname = borrow_string!(name);
    let runtime = unsafe { pxs_Runtime::from_var_ptr(rt) };
    if let Some(runtime) = runtime {
        with_backend!(runtime, Backend => {
            let res = Backend::get_from_name(bname);
            if res.is_err() {
                pxs_Var::new_exception(res.unwrap_err().to_string())
            } else {
                res.unwrap()
            }
        })
        .into_raw()
    } else {
        pxs_Var::unkown_runtime_var_ep(rt).into_raw()
    }
}

/// Remove a item from a list at a specific index.
///
/// Returns true for success, false for failed
///
/// list:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_listdel(list: pxs_VarT, index: i32) -> bool {
    pxs_debug!("pxs_listdel");
    if list.is_null() {
        return false;
    }

    let var = borrow_var!(list);
    if !var.is_list() {
        return false;
    }

    // Ok so it's a List and it's not null! Lets delete the item
    let var_list = var.get_list().unwrap();
    var_list.del_item(index)
}

/// Do a Shallow Copy. Which means it gets the same data without get the deleter for (pxs_Object or pxs_Function).
///
/// Memory is owned by caller.
///
/// var:BORROW
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_new_shallowcopy(var: pxs_VarT) -> pxs_VarT {
    pxs_debug!("pxs_new_shallowcopy");
    if var.is_null() {
        return pxs_Var::null_param_ep("var").into_raw();
    }

    // Apply a shallow copy.
    let bvar = borrow_var!(var);
    bvar.shallow_copy().into_raw()
}

/// Compile a code string into a code object for later execution.
///
/// Pass in a optional gloabl scope (or null for default). Scope ownership is transferred.
/// Returns a `pxs_Var` whichs memory is handled by the caller.
///
/// Resulting `pxs_Var` will contain (Associated Runtime, Code Object, Scope|default).
///
/// global_scope:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_compile(
    runtime: pxs_Runtime,
    code: *const c_char,
    global_scope: pxs_VarT,
) -> pxs_VarT {
    pxs_debug!("pxs_compile");
    assert_initiated!();

    if code.is_null() || global_scope.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    let rcode = borrow_string!(code);
    let scope = own_var!(global_scope);

    // Make sure scope is map or is null.
    if !scope.is_map() && !scope.is_null() {
        return pxs_Var::new_exception("Global scope must be a Map or Null".to_string()).into_raw();
    }

    let res = with_backend!(runtime, Backend => {
        let res = Backend::compile(rcode, scope);
        if res.is_err() {
            pxs_Var::new_exception(res.unwrap_err().to_string())
        } else {
            res.unwrap()
        }
    });

    if !res.is_list() {
        return res.into_raw();
    }

    // Add the runtime to the object.
    let list = res.get_list().unwrap();
    list.insert_item(0, runtime.into_var());

    res.into_raw()
}

/// Execute a compiled code object.
///
/// Variable ownership is transfered. If this is not desired behavior, pass in a shallow copy.
/// Returned variable must be freed by caller.
///
/// Runtime is not required because the object is embedded with it in pxs_compile.
/// Pass in a optional local scope that gets passed along with the global scope.
/// Note: Do not use the same scope as in `pxs_compile`.
///
/// Scope ownership is transferred.
///
/// object : TRANSFER
/// local  : TRANSFER
/// return : OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_execobject(object: pxs_VarT, local: pxs_VarT) -> pxs_VarT {
    pxs_debug!("pxs_execobject");
    assert_initiated!();

    if object.is_null() || local.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    let var = own_var!(object);

    if !var.is_list() {
        return pxs_Var::incorrect_type_ep(pxs_VarType::pxs_List, var.tag).into_raw();
    }

    // Get scope
    let scope = own_var!(local);
    // Check if scope is a map
    if !scope.is_map() && !scope.is_null() {
        return pxs_Var::incorrect_types_ep(
            vec![pxs_VarType::pxs_Map, pxs_VarType::pxs_Null],
            scope.tag,
        )
        .into_raw();
    }

    let list = var.get_list().unwrap();
    let rt = list.get_item(0);
    if let Some(rt) = rt {
        let runtime = pxs_Runtime::from_var(rt);
        if let Some(runtime) = runtime {
            // Now we can do stuff
            with_backend!(runtime, Backend => {
                let res = Backend::exec_object(var, scope);
                if res.is_err() {
                    pxs_Var::new_exception(res.unwrap_err().to_string())
                } else {
                    res.unwrap()
                }
            })
            .into_raw()
        } else {
            pxs_Var::new_exception(format!("Unkown runtime: {:#?}", rt)).into_raw()
        }
    } else {
        pxs_Var::new_exception("Code Object list is empty").into_raw()
    }
}

/// Create a new `pxs_Map`
///
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newmap() -> pxs_VarT {
    pxs_debug!("pxs_newmap");
    pxs_Var::new_map().into_raw()
}

/// Add a new key (`pxs_Var`) value (`pxs_Var`) pair in a map.
///
/// Keys can only be:
/// - `pxs_String`
/// - `pxs_Int64`
/// - `pxs_UInt64`
/// - `pxs_Float64`
/// - `pxs_Bool`
///
/// Key and value ownership are transfered.
///
/// map:BORROW
/// key:TRANSFER
/// value:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_map_addpair(map: pxs_VarT, key: pxs_VarT, value: pxs_VarT) {
    pxs_debug!("pxs_map_addpair");
    if map.is_null() || key.is_null() || value.is_null() {
        // TODO: error
        return;
    }

    // Check map is actually a map
    let map = borrow_var!(map);
    if !map.is_map() {
        // TODO: error
        return;
    }

    // Check key is valid pair
    let key = own_var!(key);
    if key.is_string() || key.is_i64() || key.is_bool() || key.is_u64() || key.is_f64() {
        let internal = map.get_map().unwrap();
        internal.add_item(key, own_var!(value));
    } else {
        // TODO: error
        return;
    }
}

/// Remove a value (`pxs_Var`) from a map based on it's key (`pxs_Var`).
///
/// map:BORROW
/// key:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_map_delitem(map: pxs_VarT, key: pxs_VarT) {
    pxs_debug!("pxs_map_delitem");
    if map.is_null() || key.is_null() {
        // TODO: error
        return;
    }

    let map = borrow_var!(map);
    if !map.is_map() {
        // TODO: error
        return;
    }

    let key = borrow_var!(key);
    let internal = map.get_map().unwrap();
    internal.del_item(key);
}

/// Get length of a `pxs_Map`.
///
/// -1 is invalid length.
///
/// map:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_maplen(map: pxs_VarT) -> i32 {
    pxs_debug!("pxs_maplen");
    if map.is_null() {
        return -1;
    }

    let bvar = borrow_var!(map);
    if bvar.is_map() {
        bvar.get_map().unwrap().len() as i32
    } else {
        -1
    }
}

/// Get the keys of a `pxs_Map`.
///
/// Returns a `pxs_List` or `pxs_Null` Which is owned by caller.
///
/// map:BORROW
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_mapkeys(map: pxs_VarT) -> pxs_VarT {
    pxs_debug!("pxs_mapkeys");
    if map.is_null() {
        return pxs_Var::null_param_ep("map").into_raw();
    }

    // Check is map
    let map = borrow_var!(map);
    if !map.is_map() {
        return pxs_Var::incorrect_type_ep(pxs_VarType::pxs_Map, map.tag).into_raw();
    }
    let internals = map.get_map().unwrap();

    let result = pxs_Var::new_list();
    let list = result.get_list().unwrap();

    // Get keys..
    let keys = internals.keys();
    for k in keys {
        // Deep copy the key over.
        list.add_item(k.clone());
    }

    result.into_raw()
}

/// Get a value in a map from a key.
///
/// Result is not owned by caller. Use `pxs_newcopy` to transfer ownership.
///
/// map:BORROW
/// key:BORROW
/// return:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_mapget(map: pxs_VarT, key: pxs_VarT) -> pxs_VarT {
    pxs_debug!("pxs_mapget");

    if map.is_null() || key.is_null() {
        return pxs_Var::null_params_ep().into_raw();
    }

    // check map
    let map = borrow_var!(map);
    if !map.is_map() {
        return pxs_Var::incorrect_type_ep(pxs_VarType::pxs_Map, map.tag).into_raw();
    }
    let internal = map.get_map().unwrap();
    let res = internal.get_item(borrow_var!(key));

    // Return a const pxs_Var.
    if let Some(res) = res {
        res as *const pxs_Var as *mut pxs_Var
    } else {
        pxs_Var::item_not_found_ep().into_raw()
    }
}

/// Insert a item into a list at a certain index, shifting all other items to the right.
///
/// Item ownership is transferred.
///
/// list:BORROW
/// item:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_listinsert(list: pxs_VarT, index: usize, item: pxs_VarT) {
    pxs_debug!("pxs_listinsert");

    if list.is_null() || item.is_null() {
        // TODO: log something?
        return;
    }

    // Check is list
    let list = borrow_var!(list);
    if !list.is_list() {
        // TODO: log something?
        return;
    }

    let item = own_var!(item);
    let internal = list.get_list().unwrap();
    internal.insert_item(index, item);
}

/// Create a new arena in memory.
/// This does not return anything, it simply creates a scope that will allocate pxs_Var memory.
/// when finished call, `pxs_freearena`
///
/// result:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_newarena() -> *mut pxs_PixelArena {
    pxs_debug!("pxs_newarena");
    assert_initiated!();

    let arena = pxs_PixelArena::new();
    arena.into_raw()
}

/// Free arena. Upon freeing all variables allocated since `pxs_newarena` will be freed.
///
/// arena:TRANSFER
#[unsafe(no_mangle)]
pub extern "C" fn pxs_freearena(arena: *mut pxs_PixelArena) {
    pxs_debug!("pxs_freearena");
    assert_initiated!();

    if arena.is_null() {
        return;
    }

    let _ = pxs_PixelArena::from_raw(arena);
}

/// Add a `pxs_VarT` to a `pxs_PixelArena`. Upon freeing the Arena, the variable is freed aswell.
///
/// arena:BORROW
/// var:TRANSFER
/// result:BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_arenaput(arena: *mut pxs_PixelArena, var: pxs_VarT) -> pxs_VarT {
    pxs_debug!("pxs_arenaput");
    assert_initiated!();

    if arena.is_null() || var.is_null() {
        return std::ptr::null_mut();
    }

    let barena = unsafe { pxs_PixelArena::from_borrow(arena) };
    barena.alloc(var);

    var
}

/// Debug state info.
///
/// result:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_debugstate(runtime: pxs_Runtime) -> *mut c_char {
    pxs_debug!("pxs_debugstate");
    assert_initiated!();
    with_backend!(runtime, Backend => {
        create_raw_string!(Backend::debug())
    })
}

/// Call GC for all backends.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_garbagecollect() {
    pxs_debug!("pxs_gc");
    assert_initiated!();

    with_feature!("lua", {
        LuaScripting::garbage_collect();
    });
    with_feature!("python", {
        PythonScripting::garbage_collect();
    });
    with_feature!("js", {
        JSScripting::garbage_collect();
    });
}

/// Get the host IDX from a `pxs_HostObject`.
/// 
/// if result is < 0 then that means it is not a object.
/// 
/// var: BORROW
#[unsafe(no_mangle)]
pub extern "C" fn pxs_getidx(var: pxs_VarT) -> i32 {
    pxs_debug!("pxs_getidx");
    assert_initiated!();

    if var.is_null() {
        return -1;
    }

    let bvar = borrow_var!(var);
    if !bvar.is_host_object() {
        return -1;
    }

    bvar.get_host_idx()
}

// ====================================== Core functions Start =======================================

/// Encode a `pxs_Var` into a JSON string. Will return a `pxs_Var` of type string.
/// Transfers ownership of args.
/// Basically calls the runtime.pxs_json.encode() function.
///
/// Note: This function is already enabled in each scripting language. This is a host language wrapper for calling it easily.
///
/// rt:BORROW
/// args:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_json_encode(rt: pxs_VarT, args: pxs_VarT) -> pxs_VarT {
    pxs_debug!("pxs_json_encode");
    assert_initiated!();
    with_feature!(
        "pxs_json",
        {
            unsafe {
                if !core::is_valid_pxs_function(rt, args) {
                    return pxs_Var::new_exception("Not a valid core call").into_raw();
                }
            }
            core::pxs_json::encode(rt, args)
        },
        { pxs_Var::feature_not_enabled_ep("pxs_json").into_raw() }
    )
}

/// Decode a `pxs_String` into a `pxs_Var`.
/// Make sure runtime is the first argument in args.
/// Transfers ownership of args.
///
/// Note: This function is already enabled in each scripting language. This is a host language wrapper for calling it easily.
///
/// rt:BORROW
/// args:TRANSFER
/// return:OWNED
#[unsafe(no_mangle)]
pub extern "C" fn pxs_json_decode(rt: pxs_VarT, args: pxs_VarT) -> pxs_VarT {
    pxs_debug!("pxs_json_decode");
    assert_initiated!();
    with_feature!(
        "pxs_json",
        {
            unsafe {
                if !core::is_valid_pxs_function(rt, args) {
                    return pxs_Var::new_exception("Not a valid core call").into_raw();
                }
            }
            core::pxs_json::decode(rt, args)
        },
        { pxs_Var::feature_not_enabled_ep("pxs_json").into_raw() }
    )
}

/// Add `pxs_mem` core module to scripting languages.
/// It can only be added once, if added gain it will throw an error.
#[unsafe(no_mangle)]
pub extern "C" fn pxs_meminit() {
    pxs_debug!("pxs_meminit");
    assert_initiated!();
    with_feature!(
        "pxs_mem",
        {
            core::pxs_mem::init();
        },
        {
            panic!("pxs_mem feature is not enabled");
        }
    );
}

// ====================================== Core functions End =========================================
