use etffi::ptr_magic::{PtrMagic, ThreadSafePointer};

// 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.
//
use super::var::pxs_Var;
use std::collections::HashMap
;

/// Function reference used in C.
///
/// args: *mut pxs_Var, A list of vars.
/// opaque: *mut c_void, opaque user data.
///
/// Func handles it's own memory, so no need to free the *mut Var returned or the argvs.
///
/// But if you use any Vars within the function, you will have to free them before the function returns.
#[allow(non_camel_case_types)]
pub type pxs_Func = unsafe extern "C" fn(args: *mut pxs_Var) -> *mut pxs_Var;

/// Basic rust structure to track Funcs and opaques together.
pub struct Function {
    pub name: String,
    pub func: pxs_Func,
}

unsafe impl Send for Function {}
unsafe impl Sync for Function {}

/// Lookup state structure
pub struct FunctionLookup {
    /// Function hash shared between all runtimes.
    ///
    /// Negative numbers are valid here.
    pub function_hash: HashMap<i32, Function>,
}

impl PtrMagic for FunctionLookup {}

impl FunctionLookup {
    pub fn get_function(&self, idx: i32) -> Option<&Function> {
        self.function_hash.get(&idx)
    }
    pub fn add_function(&mut self, name: &str, func: pxs_Func) -> i32 {
        // TODO: Allow for negative idxs.
        self.function_hash.insert(
            self.function_hash.len() as i32,
            Function {
                name: name.to_string(),
                func,
            },
        );

        return (self.function_hash.len() - 1) as i32;
    }
}

thread_local! {
    static FUNCTION_LOOKUP: ThreadSafePointer<FunctionLookup> = ThreadSafePointer::new_owned(new_function_lookup());
}

/// Create a new function lookup.
fn new_function_lookup() -> *mut FunctionLookup {
    FunctionLookup {
        function_hash: HashMap::new(),
    }.into_raw()
}

/// Get the function lookup global state. Shared between all runtimes.
fn get_function_lookup() -> *mut FunctionLookup {
    FUNCTION_LOOKUP.with(|mutex| {
        mutex.get_ptr()
    })
}

/// Add a function to the lookup
pub fn lookup_add_function(name: &str, func: pxs_Func) -> i32 {
    let lookup = get_function_lookup();
    unsafe {
        (*lookup).add_function(name, func)
    }
}

/// Clear function lookup hash
pub fn clear_function_lookup() {
    unsafe {
        (*get_function_lookup()).function_hash.clear();
    }
}

/// Call a function that is saved in the lookup by a idx.
///
/// This should only be used within languages and never from a end user.
pub unsafe fn call_function(fn_idx: i32, args: Vec<pxs_Var>) -> pxs_Var {
    let func = {
        let fl = get_function_lookup();
        let function = unsafe { (*fl).get_function(fn_idx) };
        if function.is_none() {
            return pxs_Var::new_null();
        }

        let function = function.unwrap();

        function.func
    };

    // Convert the pxs_Var vector into a list.
    // Do this because I don't want to mess with the older code.
    let args = pxs_Var::new_list_with(args);
    let args_ptr = args.into_raw();

    unsafe {
        let res = func(args_ptr);
        // Free args
        let _ = pxs_Var::from_raw(args_ptr);

        let result = if res.is_null() {
            pxs_Var::new_null()
        } else {
            pxs_Var::from_raw(res)
        };

        result
    }
}
