#![doc(html_no_source)]
//! This module exposes [GCJsonBridge], a struct that can be passed trough a C ABI boundary and wraps a serde_json::Value providing FFI callbacks to interact with it.
//! It is meant to be used over an FFI boundary to allow a plugin to interact with a JSON object.
//! Additionally, a C representation of this struct can be found in the [gc_abi_sys] crate under the file name "json.h".
//!
//! The GCJsonBridge takes a reference of a serde_json::Value, representing a JSON object, and setups multiple callbacks that can be
//! used to interact with the JSON object. It guarantees that the callbacks live as long as the JSON object.
//!
//! The following callback functions are available in [GCJsonBridge::gc_json_bridge_callbacks]:
//! - getBool: Retrieves a boolean value from the JSON object [json_object_get_bool](callbacks::json_object_get_bool)
//! - getInt: Retrieves an integer value from the JSON object [json_object_get_int](callbacks::json_object_get_int)
//! - getDouble: Retrieves a double value from the JSON object [json_object_get_double](callbacks::json_object_get_double)
//! - getString: Retrieves a string value from the JSON object [json_object_get_string](callbacks::json_object_get_string)
//! - getArrayIdx: Retrieves an element from an array at a given index [json_object_get_array_idx](callbacks::json_object_get_array_idx)
//! - getArrayLength: Retrieves the length of an array [json_object_get_array_length](callbacks::json_object_get_array_length)
//! - getObjectField: Retrieves a field from an object [json_object_get_object_field](callbacks::json_object_get_object_field)
//! - getObjectSerialized: Serializes the JSON object to a string [json_serialize](callbacks::json_serialize)
//! - freeString: Frees a string allocated by getString [free_string](callbacks::free_string)
//!
//! Every callback needs a pointer to a GCJsonObject as first argument and reference to a flag variable [gc_abi_sys::GCResultCallback] as the last argument.
//! The flag variable is used to indicate if the operation was successful or not, in case of an error, the return value of the callback is undefined.
//!
//! <div class="warning">
//! The FFI application should only use the callbacks on data passed in [sGCJsonCallbacks], as is guaranteed to be valid for teh same lifetime as the callbacks.
//! </div>
//!
//! <div class="warning">
//! It is very important that every call to getString is followed by a call to freeString, otherwise it will cause memory leaks!
//! </div>
//!
//! # Example
//! An example on how to properly parse the data in C:
//! ```c
//! #include "json.h"
//! static GCJsonCallbacks jsonParser;
//!
//! void processConfig(){
//!     GCResultCallback result = 0;
//!     GCJsonObject root = jsonParser->data;
//!     GCJsonObject subs_arr = jsonParser->getObjectField(root, "smv_subscribers", &result);
//!     if(result != GC_JSON_OK){
//!         pluginInterface->logSystemCallback(ctx, ERROR, "No smv_subscribers field found");
//!         return;
//!     }
//!
//!     GCJsonObject sub_0 = jsonParser->getArrayIdx(subs_arr, 0, &result);
//!     if(result != GC_JSON_OK){
//!         pluginInterface->logSystemCallback(ctx, ERROR, "No 0th subscriber found");
//!         return;
//!     }
//!
//!     GCJsonObject sub_0_name = jsonParser->getObjectField(sub_0, "smv_id", &result);
//!     if(result != GC_JSON_OK){
//!         pluginInterface->logSystemCallback(ctx, ERROR, "No smv_id field found");
//!         return;
//!     }
//!     const char* sub_0_smv_id = jsonParser->getString(sub_0_name, &result);
//!     if(result == GC_JSON_OK){
//!         pluginInterface->logSystemCallback(ctx, DEBUG, sub_0_smv_id);
//!         jsonParser->freeString(sub_0_smv_id);
//!         return;
//!     }
//!     pluginInterface->logSystemCallback(ctx, ERROR, "No sub_0_name field found");
//!
//! }
//!```
#![allow(rustdoc::private_intra_doc_links)]

mod callbacks;

use gc_abi_sys::GCJsonObject;
pub use gc_abi_sys::{GCJsonCallbacks, sGCJsonCallbacks};
use log::trace;
use std::os::raw::c_void;

/// GCJsonBridge is a struct that wraps a serde_json::Value and provides safe FFI callbacks to interact with it.
///
/// The data and callbacks necessary to be used in the ABI can be retrieved by calling [gc_json_bridge_callbacks](GCJsonBridge::gc_json_bridge_callbacks).
#[allow(dead_code)]
pub struct GCJsonBridge {
    json_value: Box<serde_json::Value>,
    callbacks: Box<sGCJsonCallbacks>,
}
impl GCJsonBridge {
    pub fn new(json_value: &serde_json::Value) -> Self {
        trace!("Creating GCJsonBridge");

        let heap_json = Box::new(json_value.clone());
        let heap_json_ptr = Box::into_raw(heap_json);

        let c_object_data = GCJsonObject {
            ptr: heap_json_ptr as *const c_void,
            type_: GCJsonBridge::get_serde_json_c_type(json_value),
        };

        let callbacks = Box::new(sGCJsonCallbacks {
            getBool: Some(callbacks::json_object_get_bool),
            getInt: Some(callbacks::json_object_get_int),
            getDouble: Some(callbacks::json_object_get_double),
            getString: Some(callbacks::json_object_get_string),
            getArrayIdx: Some(callbacks::json_object_get_array_idx),
            getArrayLength: Some(callbacks::json_object_get_array_length),
            getObjectField: Some(callbacks::json_object_get_object_field),
            getObjectSerialized: Some(callbacks::json_serialize),
            freeString: Some(callbacks::free_string),
            data: c_object_data,
        });

        GCJsonBridge {
            // Safety: this is valid as long as the GCJsonBridgeOwned is alive
            json_value: unsafe { Box::from_raw(heap_json_ptr) },
            callbacks,
        }
    }

    // The callbacks are guaranteed to live as long as the json value
    pub fn gc_json_bridge_callbacks(&self) -> &sGCJsonCallbacks {
        &self.callbacks
    }

    fn get_serde_json_c_type(json_data: &serde_json::Value) -> gc_abi_sys::GCJsonType {
        match json_data {
            serde_json::Value::Null => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_NULL,
            serde_json::Value::Bool(_) => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_BOOL,
            serde_json::Value::Number(num) => {
                if num.is_i64() {
                    gc_abi_sys::eGCJsonType_GC_JSON_TYPE_INT
                } else if num.is_f64() {
                    gc_abi_sys::eGCJsonType_GC_JSON_TYPE_DOUBLE
                } else {
                    panic!("json_object_get_object_field: obj is a number but not an integer or double, obj: {json_data:?}");
                }
            }
            serde_json::Value::String(_) => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_STRING,
            serde_json::Value::Array(_) => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_ARRAY,
            serde_json::Value::Object(_) => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_OBJECT,
        }
    }
}

unsafe impl Send for GCJsonBridge {}

/// Safety: gc_abi_sys::GCJsonObject should NEVER outlive json_obj
/// As GCJsonObject is autogenerated by bindgen, it is not possible to enforce this constraint using the borrow checker.
/// In the future, it should be considered to manually implement GCJsonObject to enforce this constraint.
/// As this is an internal function, it is safe to assume that the constraint is met.
unsafe fn convert_serde_json(json_obj: &serde_json::Value) -> gc_abi_sys::GCJsonObject {
    gc_abi_sys::GCJsonObject {
        ptr: json_obj as *const serde_json::Value as *const c_void,
        type_: match json_obj {
            serde_json::Value::Null => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_NULL,
            serde_json::Value::Bool(_) => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_BOOL,
            serde_json::Value::Number(num) => {
                if num.is_i64() {
                    gc_abi_sys::eGCJsonType_GC_JSON_TYPE_INT
                } else if num.is_f64() {
                    gc_abi_sys::eGCJsonType_GC_JSON_TYPE_DOUBLE
                } else {
                    panic!("json_object_get_object_field: obj is a number but not an integer or double, obj: {json_obj:?}");
                }
            }
            serde_json::Value::String(_) => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_STRING,
            serde_json::Value::Array(_) => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_ARRAY,
            serde_json::Value::Object(_) => gc_abi_sys::eGCJsonType_GC_JSON_TYPE_OBJECT,
        },
    }
}
