//! This module defines the callbacks that are used by the GCJsonBridge.
//!
//! All the functions take as guarantee that the GCJsonObject will live as long as their return values.
use crate::convert_serde_json;
use log::trace;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

#[doc(alias = "jsonObjectGetBool")]
/// Define the function to get a boolean value from a JSON object
pub unsafe extern "C" fn json_object_get_bool(obj: gc_abi_sys::GCJsonObject, result: *mut gc_abi_sys::GCResultCallback) -> bool {
    unsafe {
        trace!("call to json_object_get_bool");
        let json_value = &*(obj.ptr as *const serde_json::Value);
        if obj.ptr.is_null() {
            *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            trace!("json_object_get_bool: obj is null");
            return false;
        }

        match json_value {
            serde_json::Value::Bool(value) => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_OK;
                *value
            }
            _ => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                trace!("json_object_get_bool: obj is not a boolean, obj: {json_value:?}");
                false
            }
        }
    }
}

#[doc(alias = "jsonObjectGetInt")]
/// Define the function to get an integer value from a JSON object
pub unsafe extern "C" fn json_object_get_int(obj: gc_abi_sys::GCJsonObject, result: *mut gc_abi_sys::GCResultCallback) -> i64 {
    unsafe {
        trace!("call to json_object_get_int");
        let json_value = &*(obj.ptr as *const serde_json::Value);
        if obj.ptr.is_null() {
            *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            trace!("json_object_get_int: obj is null");
            return 0;
        }

        match json_value {
            serde_json::Value::Number(value) => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_OK;
                value.as_i64().map_or_else(
                    || {
                        *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                        trace!("json_object_get_int: obj is not an integer, obj: {json_value:?}");
                        0
                    },
                    |v| v,
                )
            }
            _ => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                trace!("json_object_get_int: obj is not an integer, obj: {json_value:?}");
                0
            }
        }
    }
}

#[doc(alias = "jsonObjectGetDouble")]
/// Define the function to get a double value from a JSON object
pub unsafe extern "C" fn json_object_get_double(obj: gc_abi_sys::GCJsonObject, result: *mut gc_abi_sys::GCResultCallback) -> f64 {
    unsafe {
        trace!("call to json_object_get_double");
        let json_value = &*(obj.ptr as *const serde_json::Value);
        if obj.ptr.is_null() {
            *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            trace!("json_object_get_double: obj is null");
            return 0.0;
        }

        match json_value {
            serde_json::Value::Number(value) => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_OK;
                value.as_f64().map_or_else(
                    || {
                        *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                        trace!("json_object_get_double: obj is not a double, obj: {json_value:?}");
                        0.0
                    },
                    |v| v,
                )
            }
            _ => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                trace!("json_object_get_double: obj is not a double, obj: {json_value:?}");
                0.0
            }
        }
    }
}

#[doc(alias = "jsonObjectGetString")]
/// Define the function to get a string value from a JSON object.
/// The string should be freed using [free_string].
/// <div class="warning">
/// This functions hands over the ownership of the string to the caller.
/// It is important that the string is freed using the same allocator that allocated it.
/// </div>
pub unsafe extern "C" fn json_object_get_string(obj: gc_abi_sys::GCJsonObject, result: *mut gc_abi_sys::GCResultCallback) -> *mut c_char {
    unsafe {
        trace!("call to json_object_get_string");
        let json_value = &*(obj.ptr as *const serde_json::Value);
        if obj.ptr.is_null() {
            *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            trace!("json_object_get_string: obj is null");
            return std::ptr::null_mut();
        }

        match json_value {
            serde_json::Value::String(value) => {
                let s = CString::new(value.to_string());
                match s {
                    Ok(s) => {
                        *result = gc_abi_sys::eResultCallback_GC_JSON_OK;
                        s.into_raw() as *mut c_char
                    }
                    Err(e) => {
                        *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                        trace!("json_object_get_string: error converting string, error: {e}");
                        std::ptr::null_mut()
                    }
                }
            }
            _ => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                trace!("json_object_get_string: obj is not a string, obj: {json_value:?}");
                std::ptr::null_mut()
            }
        }
    }
}

#[doc(alias = "jsonObjectGetArrayIdx")]
/// Define the function to get an element from an array by index in a JSON object
pub unsafe extern "C" fn json_object_get_array_idx(
    obj: gc_abi_sys::GCJsonObject,
    idx: i64,
    result: *mut gc_abi_sys::GCResultCallback,
) -> gc_abi_sys::GCJsonObject {
    unsafe {
        trace!("call to json_object_get_array_idx with idx: {idx}");
        let json_value = &*(obj.ptr as *const serde_json::Value);
        if obj.ptr.is_null() {
            *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            trace!("json_object_get_array_idx: obj is null");
            return obj;
        }

        match json_value {
            serde_json::Value::Array(value) => {
                if idx < 0 || idx >= value.len() as i64 {
                    *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                    trace!(
                        "json_object_get_array_idx: idx out of bounds, idx: {}, array length: {}",
                        idx,
                        value.len()
                    );
                    return obj;
                }

                *result = gc_abi_sys::eResultCallback_GC_JSON_OK;
                convert_serde_json(&value[idx as usize])
            }
            _ => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                trace!("json_object_get_array_idx: obj is not an array, obj: {json_value:?}");
                obj
            }
        }
    }
}

#[doc(alias = "jsonSerialize")]
/// Define the function to serialize a JSON object to a string
/// The string should be freed using [free_string].
/// <div class="warning">
/// This functions hands over the ownership of the string to the caller.
/// It is important that the string is freed using the same allocator that allocated it.
/// </div>
pub unsafe extern "C" fn json_serialize(obj: gc_abi_sys::GCJsonObject, result: *mut gc_abi_sys::GCResultCallback) -> *mut c_char {
    unsafe {
        trace!("call to json_serialize");
        let json_value = &*(obj.ptr as *const serde_json::Value);
        if obj.ptr.is_null() {
            *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            trace!("json_serialize: obj is null");
            return std::ptr::null_mut();
        }

        match serde_json::to_string(json_value) {
            Ok(value) => {
                let s = CString::new(value.to_string());
                match s {
                    Ok(s) => {
                        *result = gc_abi_sys::eResultCallback_GC_JSON_OK;
                        s.into_raw() as *mut c_char
                    }
                    Err(e) => {
                        *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                        trace!("json_object_get_string: error converting string, error: {e}");
                        std::ptr::null_mut()
                    }
                }
            }
            Err(e) => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                trace!("json_serialize: error serializing json, error: {e}");
                std::ptr::null_mut()
            }
        }
    }
}

#[doc(alias = "jsonObjectGetArrayLength")]
/// Define the function to get the length of an array in a JSON object
pub unsafe extern "C" fn json_object_get_array_length(obj: gc_abi_sys::GCJsonObject, result: *mut gc_abi_sys::GCResultCallback) -> i64 {
    unsafe {
        trace!("call to json_object_get_array_length");
        let json_value = &*(obj.ptr as *const serde_json::Value);
        if obj.ptr.is_null() {
            *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            trace!("json_object_get_array_length: obj is null");
            return 0;
        }

        match json_value {
            serde_json::Value::Array(value) => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_OK;
                value.len() as i64
            }
            _ => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                trace!("json_object_get_array_length: obj is not an array, obj: {json_value:?}");
                0
            }
        }
    }
}

#[doc(alias = "jsonObjectGetObjectField")]
/// Define the function to get a field from a JSON object
pub unsafe extern "C" fn json_object_get_object_field(
    obj: gc_abi_sys::GCJsonObject,
    field_name: *const c_char,
    result: *mut gc_abi_sys::GCResultCallback,
) -> gc_abi_sys::GCJsonObject {
    unsafe {
        trace!("call to json_object_get_object_field");
        let json_value = &*(obj.ptr as *const serde_json::Value);
        if obj.ptr.is_null() {
            *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            return obj;
        }

        match json_value {
            serde_json::Value::Object(value) => {
                let field_name = CStr::from_ptr(field_name).to_string_lossy();
                trace!("json_object_get_object_field: field_name: {field_name}");
                match value.get(field_name.as_ref()) {
                    Some(field_value) => {
                        *result = gc_abi_sys::eResultCallback_GC_JSON_OK;
                        convert_serde_json(field_value)
                    }
                    None => {
                        *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                        trace!("json_object_get_object_field: field_name not found, field_name: {field_name}");
                        obj
                    }
                }
            }
            _ => {
                *result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                trace!("json_object_get_object_field: obj is not an object, obj: {json_value:?}");
                obj
            }
        }
    }
}

#[doc(alias = "freeString")]
/// Define the function to free a string allocated by [json_object_get_string]
pub unsafe extern "C" fn free_string(s: gc_abi_sys::GCString) {
    unsafe {
        trace!("call to free_string");
        if s.is_null() {
            return;
        }
        drop(CString::from_raw(s as *mut c_char));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serial_test::serial;

    fn convert_data_test() -> String {
        r#"
            {
                "bool": true,
                "int": 10,
                "-int": -10,
                "double": 11.5,
                "double2": -1.2,
                "string": "hello",
                "array": [1, 2, 3],
                "object": {
                    "field": 10
                }
            }
        "#
        .to_string()
    }

    #[test]
    fn test_json_object_get_bool() {
        let json_value: serde_json::Value = serde_json::from_str(convert_data_test().as_str()).unwrap();

        for (k, v) in json_value.as_object().unwrap() {
            let mut result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            let c_obj = unsafe { convert_serde_json(v) };
            let ret = unsafe { json_object_get_bool(c_obj, &mut result) };
            match k.as_str() {
                "bool" => {
                    assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_bool");
                    assert!(ret, "Error executing json_object_get_bool");
                }
                _ => {
                    assert_eq!(
                        result,
                        gc_abi_sys::eResultCallback_GC_JSON_ERROR,
                        "Json Key {k} is not a boolean and should yield an error"
                    );
                }
            }
        }
    }

    #[test]
    fn test_json_object_get_int() {
        let json_value: serde_json::Value = serde_json::from_str(convert_data_test().as_str()).unwrap();

        for (k, v) in json_value.as_object().unwrap() {
            let mut result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            let c_obj = unsafe { convert_serde_json(v) };
            let ret = unsafe { json_object_get_int(c_obj, &mut result) };
            match k.as_str() {
                "int" => {
                    assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_int");
                    assert_eq!(ret, 10, "Error executing json_object_get_int");
                }
                "-int" => {
                    assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_int");
                    assert_eq!(ret, -10, "Error executing json_object_get_int");
                }
                _ => {
                    assert_eq!(
                        result,
                        gc_abi_sys::eResultCallback_GC_JSON_ERROR,
                        "Json Key {k} is not a integer and should yield an error"
                    );
                }
            }
        }
    }

    #[test]
    fn test_json_object_get_double() {
        let json_value: serde_json::Value = serde_json::from_str(convert_data_test().as_str()).unwrap();

        for (k, v) in json_value.as_object().unwrap() {
            let mut result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            let c_obj = unsafe { convert_serde_json(v) };
            let ret = unsafe { json_object_get_double(c_obj, &mut result) };
            match k.as_str() {
                "double" => {
                    assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_double");
                    assert_eq!(ret, 11.5, "Error executing json_object_get_double");
                }
                "double2" => {
                    assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_double");
                    assert_eq!(ret, -1.2, "Error executing json_object_get_double");
                }
                "int" => {
                    assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_double");
                    assert_eq!(ret, 10.0, "Error executing json_object_get_double");
                }
                "-int" => {
                    assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_double");
                    assert_eq!(ret, -10.0, "Error executing json_object_get_double");
                }
                _ => {
                    assert_eq!(
                        result,
                        gc_abi_sys::eResultCallback_GC_JSON_ERROR,
                        "Json Key {k} is not a double and should yield an error"
                    );
                }
            }
        }
    }

    #[test]
    #[serial]
    fn test_json_object_get_string() {
        let info = allocation_counter::measure(|| {
            let json_value: serde_json::Value = serde_json::from_str(convert_data_test().as_str()).unwrap();

            for (k, v) in json_value.as_object().unwrap() {
                let mut result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                let c_obj = unsafe { convert_serde_json(v) };
                let ret = unsafe { json_object_get_string(c_obj, &mut result) };
                match k.as_str() {
                    "string" => {
                        assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_string");
                        let s = unsafe { CString::from_raw(ret as *mut c_char) };
                        assert_eq!(s.to_str().unwrap(), "hello", "Error executing json_object_get_string");
                        unsafe { free_string(s.into_raw() as *const c_char) };
                    }
                    _ => {
                        assert_eq!(
                            result,
                            gc_abi_sys::eResultCallback_GC_JSON_ERROR,
                            "Json Key {k} is not a string and should yield an error"
                        );
                    }
                }
            }
        });
        assert_eq!(info.count_current, 0);
    }

    #[test]
    fn test_json_object_get_array_length() {
        let json_value: serde_json::Value = serde_json::from_str(convert_data_test().as_str()).unwrap();

        for (k, v) in json_value.as_object().unwrap() {
            let mut result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            let c_obj = unsafe { convert_serde_json(v) };
            let ret = unsafe { json_object_get_array_length(c_obj, &mut result) };
            match k.as_str() {
                "array" => {
                    assert_eq!(
                        result,
                        gc_abi_sys::eResultCallback_GC_JSON_OK,
                        "Error executing json_object_get_array_length"
                    );
                    assert_eq!(ret, 3, "Error executing json_object_get_array_length");
                }
                _ => {
                    assert_eq!(
                        result,
                        gc_abi_sys::eResultCallback_GC_JSON_ERROR,
                        "Json Key {k} is not an array and should yield an error"
                    );
                }
            }
        }
    }
    #[test]
    fn test_json_object_get_array_idx() {
        let json_value: serde_json::Value = serde_json::from_str(convert_data_test().as_str()).unwrap();

        for (k, v) in json_value.as_object().unwrap() {
            let mut result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
            let c_obj = unsafe { convert_serde_json(v) };
            let ret = unsafe { json_object_get_array_idx(c_obj, 0, &mut result) };
            match k.as_str() {
                "array" => {
                    assert_eq!(
                        result,
                        gc_abi_sys::eResultCallback_GC_JSON_OK,
                        "Error executing json_object_get_array_idx"
                    );
                    let val_idx_1 = unsafe { json_object_get_int(ret, &mut result) };
                    assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_int");
                    assert_eq!(val_idx_1, 1, "Error executing json_object_get_array_idx");
                }
                _ => {
                    assert_eq!(
                        result,
                        gc_abi_sys::eResultCallback_GC_JSON_ERROR,
                        "Json Key {k} is not an array and should yield an error"
                    );
                }
            }
        }
    }
    #[test]
    fn test_json_serialize() {
        let original_json_value: serde_json::Value = serde_json::from_str(convert_data_test().as_str()).unwrap();

        let mut result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
        let c_obj = unsafe { convert_serde_json(&original_json_value) };
        let c_raw_result = unsafe { json_serialize(c_obj, &mut result) };
        let c_string_result = unsafe { CString::from_raw(c_raw_result) };
        let converted_string_result = c_string_result.to_str().unwrap();
        let result_json_value: serde_json::Value = serde_json::from_str(converted_string_result).unwrap();

        assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_serialize");
        assert_eq!(
            result_json_value, original_json_value,
            "The serialized json is not the same as the original json"
        );
    }

    #[test]
    #[serial]
    fn test_json_object_get_object_field() {
        let info = allocation_counter::measure(|| {
            let json_value: serde_json::Value = serde_json::from_str(convert_data_test().as_str()).unwrap();

            for (k, v) in json_value.as_object().unwrap() {
                let mut result = gc_abi_sys::eResultCallback_GC_JSON_ERROR;
                let c_obj = unsafe { convert_serde_json(v) };
                let field_str = CString::new("field").unwrap().into_raw();
                let ret = unsafe { json_object_get_object_field(c_obj, field_str as *const c_char, &mut result) };
                drop(unsafe { CString::from_raw(field_str as *mut c_char) });
                match k.as_str() {
                    "object" => {
                        assert_eq!(
                            result,
                            gc_abi_sys::eResultCallback_GC_JSON_OK,
                            "Error executing json_object_get_object_field"
                        );
                        let val = unsafe { json_object_get_int(ret, &mut result) };
                        assert_eq!(result, gc_abi_sys::eResultCallback_GC_JSON_OK, "Error executing json_object_get_string");
                        assert_eq!(val, 10, "Error executing json_object_get_object_field");
                    }
                    _ => {
                        assert_eq!(
                            result,
                            gc_abi_sys::eResultCallback_GC_JSON_ERROR,
                            "Json Key {k} is not an object and should yield an error"
                        );
                    }
                }
            }
        });
        assert_eq!(info.count_current, 0);
    }
}
