/**
 * @mainpage Gateway's C Plugin SDK
 *
 * This SDK provides a C ABI to interact with the core gateway. Every plugin must use this ABI to interact with the
 * core.
 *
 * To create a C plugin, the user is responsible for implementing the following exported functions:
 * - @ref gc_plugin_init
 * - @ref gc_plugin_shutdown
 * - @ref gc_plugin_get_info
 * - @ref gc_plugin_receive_datapoint
 *
 * The following is the simplest example of a subscriber plugin without any configuration.
 *
 * ```c
 * #include abi.h
 * #include <stdlib.h>
 * #include <stdio.h>
 *
 * typedef struct {
 *     GCPluginInterface interface;
 * } ExamplePlugin;
 *
 * GCInstanceHandle gc_plugin_init(GCPluginInterface interface) {
 *     ExamplePlugin* instance = malloc(sizeof(ExamplePlugin));
 *     if (instance == NULL) {
 *         gcSystemLog(interface, ERROR, "Failed to allocate memory for plugin instance");
 *         return NULL;
 *     }
 *     instance->interface = interface;
 *     return instance;
 * }
 *
 * void gc_plugin_shutdown(GCInstanceHandle instance) {
 *     free(instance);
 * }
 *
 * GCPluginInfo gc_plugin_get_info() {
 *     static struct sGCPluginInfo info = { "ExamplePlugin", "0.1.0", "0", 0 };
 *     return &info;
 * }
 *
 * bool gc_plugin_receive_datapoint(GCInstanceHandle instance, const GCDatapointValue dataValue) {
 *    ExamplePlugin* plugin = (ExamplePlugin*)instance;
 *    // Handle incoming datapoint.  Example: print the value
 *    switch (dataValue->type) {
 *        case GCOS_DATAPOINT_TYPE_INT32:
 *            gcSystemLog(plugin->interface, INFO, "Received datapoint value: %d", dataValue->value.i32);
 *            break;
 *        // Handle other types as needed...
 *        default:
 *            gcSystemLog(plugin->interface, WARNING, "Received unsupported datapoint type");
 *    }
 *
 *    plugin->interface->publishDatapointValueCallback(plugin->interface->ctx, dataValue);
 *    gcReleaseDatapointValue(plugin->interface, dataValue);
 *    return true;
 * }
 * ```
 * This plugin will log the value of an `INT32` datapoint to the system log.
 *
 * - The @ref gc_plugin_init function will be responsible for initializing the plugin instance. This function receives
 * the
 * @ref sGCPluginInterface which contains all the callbacks that the plugin can use to interact with the core. It should
 * return a handle to the plugin instance. This handle will be passed to all other plugin exported functions.
 *
 * - The @ref gc_plugin_shutdown function is called by the core when the plugin instance needs to be shutdown.  It is
 * crucial to release all resources allocated in the @ref gc_plugin_init function (check @ref gc_plugin_shutdown for
 * insights in the shutdown procedure).
 *
 * - The @ref gc_plugin_get_info function returns a @ref sGCPluginInfo struct which contains information about the
 * plugin.
 *
 * - The @ref gc_plugin_receive_datapoint function is called when a datapoint the plugin is subscribed to receives a new
 * value. The example code demonstrates handling an `INT32` datapoint and logging its value. Be sure to handle all
 * expected datapoint types.  **Critically**, this function *must* call the @ref
 * sGCPluginInterface::releaseDatapointValueCallback function from the provided interface to release the received
 * `dataValue` after processing it.
 *
 * @section Concurrency Concurrency
 * A plugin may have multiple instances running inside the same process and thus functions maybe called from multiple
 * threads simultaneously. Keep this in mind as it can affect the overall functionality (e.g., handling global, static
 * variables and race conditions). Head over to @ref gc_plugin_shutdown for more insights about the plugin shutdown
 * procedure which are important in a concurrent environment.
 *
 * @section Configuration Parsing plugin configuration
 *
 * Given a plugin instance with the following specific configuration:
 * ```c
 * {
 *  "host": "abc",
 *  "arr_example": [
 *      0,
 *      {
 *        "arr_el": 4001
 *      }
 *   ]
 * }
 * ```
 * @note The plugin plugin_specific_config is any config provided by the user in the configuration (Head over to [plugin
 * configuration](/docs/configuration/plugin_config/) for more information).
 *
 * Below is an example of how to parse the plugin own configuration using the provided JSON callbacks.
 * ```c
 *   GCPluginInterface* interface...; // GCPluginInterface yield by gc_plugin_init
 *   GCJsonCallbacks json_callbacks = interface->config.pluginJsonConfig;
 *
 *   GCResultCallback result = GC_JSON_ERROR;
 *   GCJsonObject root = json_callbacks->data;
 *
 *   // Retrieves the host field
 *   GCJsonObject host_value = json_callbacks->getObjectField(root, "host", &result);
 *   assert(result == GC_JSON_OK && "Failed to get host field");
 *
 *   const char* host = json_callbacks->getString(host_value, &result);
 *   assert(result == GC_JSON_OK && "Failed to get ethernet_interface value");
 *   assert(strcmp(host, "eth0") == 0 && "ethernet_interface value is not eth0");
 *   json_callbacks->freeString((void*)host); // Important to always free the string if not used
 *
 *
 *   // Retrieves arr_example field and size
 *   GCJsonObject arr_obj = json_callbacks->getObjectField(root, "arr_example", &result);
 *   assert(result == GC_JSON_OK && "Failed to get arr_example field");
 *   uint64_t arr_length = json_callbacks->getArrayLength(arr_obj, &result);
 *   assert(result == GC_JSON_OK && arr_length==2  && "Failed to get arr_example length");
 *
 *   // Retrieve arr_el
 *   GCJsonObject arr_el_parent_obj = json_callbacks->getArrayIdx(arr_obj, 1, &result);
 *   assert(result == GC_JSON_OK && "Failed to arr_el_parent_obj field");
 *   GCJsonObject arr_el_obj = json_callbacks->getObjectField(arr_el_parent_obj, "arr_el", &result);
 *   assert(result == GC_JSON_OK && "Failed to get arr_el field");
 *   int64_t arr_el = json_callbacks->getInt(arr_el_obj, &result);
 *   assert(result == GC_JSON_OK && arr_el==4001  && "Failed to get arr_el value");
 *
 * ```
 * Essentially any JSON object can be parsed and is important that all strings returned by the JSON callbacks are freed
 * using the `freeString` callback. All json callbacks (with the exception of the freeString) return a result as the
 * last argument, that should be checked to ensure the operation was successful.
 *
 * @section Examples Additional plugin examples
 * For an example of a plugin that creates a new thread and publishes values, check the
 * [datapoint_generator](https://bitbucket.org/gridcorelabs/nextgen-gateway/src/master/plugins/c/datapoint_generator/src/lib.c)
 * plugin.
 *
 * @section Testing
 * The SDK provides a testing library that can be used to test the plugin configuration parsing.
 * More information can be found in the @ref json_testing.h header file.
 *
 */

#ifndef ABI_H
#define ABI_H

#include "datapoint_value.h"
#include "json.h"
#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C"
{
#endif

/**
 * @brief The log levels available
 */
enum eGCLogLevel
{
    CRITICAL,
    ERROR,
    WARNING,
    INFO,
    DEBUG,
};
typedef enum eGCLogLevel GCLogLevel;

/**
 * @brief Plugin version information
 * The information that the core uses to register a new plugin. Every plugin must provide this information.
 */
struct sGCPluginInfo
{
    /// @brief The name of the plugin
    const char* name;

    /// @brief The version of the plugin
    const char* version;

    /// @brief The interface version of the plugin
    const char* pluginIfcVersion;

    /// @brief Reserved for future use
    uint64_t features;
};
typedef const struct sGCPluginInfo* GCPluginInfo;

/**
 * @brief Datapoint information
 * The information about a datapoint, each DatapointValue will belong to a specific Datapoint which can be verified by
 * it's ID which is unique for the entire Gateway/plugins.
 */
struct sGCDatapoint
{
    /// @brief The name of the datapoint
    const char* name;

    /// @brief The description of the datapoint
    const char* description;

    /// @brief The id of the datapoint
    uint64_t id;

    /// @brief The unit of the datapoint
    const char* unit;
};
typedef const struct sGCDatapoint GCDatapoint;

//////////////////// CORE CALLBACKS ///////////////////

typedef void* GCCoreCtx;

/**
 * @brief The handle to the plugin instance
 *
 * The handle that the core provides to the plugin, this is for stateful plugins so they can keep track of their own
 * state. It is meant to be casted to the plugin instance struct defined by it.
 */
typedef void* GCInstanceHandle;

typedef bool (*tPublishDatapointValue)(GCCoreCtx, GCDatapointValue);
typedef GCDatapointValue (*tGetLastDatapointValue)(GCCoreCtx, GCDatapointIdentifier);
typedef void (*tSystemLog)(GCCoreCtx, GCLogLevel, const char*);
typedef void (*tAuditLog)(GCCoreCtx, const char*);
typedef bool (*tReleaseDatapointValue)(GCDatapointValue);

/**
 * @brief Holds the configuration and related functions
 *
 * This struct holds the configuration of the plugin and the related functions that the plugin can use.
 */
struct sPluginConfig
{
    /**
     * Structure containing the configuration specific to this plugin and respective callbacks
     */
    const GCJsonCallbacks pluginJsonConfig;

    /**
     * An array with size @ref ownDatapointsCount that contains information about each datapoint where the plugin is
     * allowed to publish. This will be all the datapoints, where the current instance was set as a publisher in the
     * configuration.
     */
    const GCDatapoint* ownDatapoints;
    const uint64_t ownDatapointsCount;

    /**
     * An array with size @ref subscribedDatapointsCount that contains information about each datapoint where the plugin
     * is subscribed to. This will be all the datapoints, where the current instance was set as a subscriber in the
     * configuration.
     */
    const GCDatapoint* subscribedDatapoints;
    const uint64_t subscribedDatapointsCount;
};
typedef const struct sPluginConfig GCPluginConfig;

/**
 * @brief The plugin interface
 *
 * The interface that the core provides to the plugin, it contains all the callbacks that the plugin can use to interact
 * with the core.
 */
struct sGCPluginInterface
{
    /**
     * @brief Callback used to publish datapoint values
     *
     * This callback is used to publish a new datapoint value to the core.
     * @param ctx The core context
     * @param value The datapoint value to be published, owned by the plugin
     * @return true if the value was published successfully, false if an error occurred while publishing the value
     */
    tPublishDatapointValue publishDatapointValueCallback;

    /**
     * This callback shouldn't be used, instead use the higher level @ref gcSystemLog and @ref gcAuditLog functions
     */
    tSystemLog logSystemCallback;

    /**
     * This callback shouldn't be used, instead use the higher level @ref gcSystemLog and @ref gcAuditLog functions
     */
    tAuditLog logAuditCallback;

    /**
     * @brief Callback used to release a datapoint value
     *
     * This callback is used to release a datapoint value that was previously returned by the core.
     *
     * @param value The datapoint value to be released
     * @return true if the value was released successfully, false if an error occurred while releasing the value
     */
    tReleaseDatapointValue releaseDatapointValueCallback;

    /**
     * @brief Callback used to get the last value of a datapoint
     *
     * This callback is used to get the last value of a datapoint from the realtime database.
     *
     * @param ctx The core context
     * @param identifier The identifier of the datapoint to get the last value from
     * @return If the datapoint exists, a shared reference to the last value of the datapoint, otherwise null
     * @note The plugin **must** call @ref releaseDatapointValueCallback on the returned value if it is not null. Not
     * doing so will result in a memory leak.
     */
    tGetLastDatapointValue getLastDatapointValueCallback;

    /**
     * @brief Configuration and related functions
     *
     */
    const GCPluginConfig config;

    /**
     * @brief Core Context
     *
     * The context state that the core provides to the plugin, it is required to call most of the core callbacks.
     */
    const GCCoreCtx ctx;

    /**
     * @brief  The log level set for this plugin instance
     *
     * The log level set for this plugin instance, higher values mean more verbose logging.
     */
    const GCLogLevel logLevel;
};
typedef const struct sGCPluginInterface* GCPluginInterface;

//////////////////// EXPORT FUNCTIONS ///////////////////

/**
 * @brief Initializes the plugin
 *
 * This function is called by the core to initialize the plugin. This is the entrypoint of the plugin,
 * and it will be called once for every plugin instance.
 * It may also be called multiple times for the same plugin instance in the case of a restart for example,
 * but in this case it is guaranteed that the plugin instance will be shutdown before (when the core issues a shutdown
 * command). The core will only call other plugin exported functions after this function returns.
 *
 * @param interface The interface that the core provides to the plugin
 * @return The handle to the plugin instance, this maybe any pointer to memory and can be use to keep track of the
 * instance state as it will be forwarded to all other plugin exported functions.
 */
extern GCInstanceHandle
gc_plugin_init(GCPluginInterface interface);

/**
 * @brief Safely shuts down the plugin instance.
 *
 * This function is invoked by the core to terminate the plugin instance. The following points should be taken into
 * consideration regarding concurrency:
 *
 * - After this function completes, no other functions exported by the plugin will be called for this instance.
 * - During the execution of `gc_plugin_shutdown`, other plugin functions may still be running concurrently if their
 * calls were initiated before `gc_plugin_shutdown` was invoked. It is therefore the plugin's responsibility to handle
 * any potential race conditions while releasing resources.
 * - All resources allocated by the plugin should be released by the time this function returns.
 *
 * @param instance A pointer to the plugin instance being shut down.
 */
extern void
gc_plugin_shutdown(GCInstanceHandle instance);

/**
 * @brief Retrieves the plugin information
 *
 * This is the only exported function that does not require a plugin instance handle since every plugin instance will
 * have the same information.
 *
 * @return GCPluginInfo The information about the plugin
 */
extern GCPluginInfo
gc_plugin_get_info();

/**
 * @brief Receives a datapoint value
 *
 * This function is called by the core to deliver a new datapoint value to the plugin via the subscription system.
 * The plugin should process the value and return true if the value was successfully processed, false otherwise.
 *
 * It is **very important** that the plugin releases the datapoint passed as an argument after it is done with it. This
 * is done by calling the `releaseDatapointValue` callback. Failing to do so will result in a memory leak.
 *
 * @param instance The handle to the plugin instance
 * @param dataValue A shared reference to a datapoint value
 * @return true if the value was successfully processed, false otherwise
 */
extern bool
gc_plugin_receive_datapoint(GCInstanceHandle instance, const GCDatapointValue dataValue);

/// <div rustbindgen hide></div>
/**
 * @brief Logs a message to the system log
 *
 * This function is a wrapper around the `logSystemCallback` callback provided by the core. It is used to log messages
 * to the system log. It works similarly to the printf function. It has a maximum buffer size of 1000 bytes, any message
 * exceeding this size will be truncated.
 *
 * @param interface The interface that the core provides to the plugin
 * @param level The log level of the message from the GCLogLevel enum
 * @param message The message to be logged
 */
void
gcSystemLog(GCPluginInterface interface, GCLogLevel level, const char* message, ...);

/// <div rustbindgen hide></div>
/**
 * @brief Logs a message to the audit log
 *
 * This function is a wrapper around the `logAuditCallback` callback provided by the core. It is used to log messages to
 * the audit log. It works similarly to the printf function. It has a maximum buffer size of 1000 bytes, any message
 * exceeding this size will be truncated.
 *
 * @param interface The interface that the core provides to the plugin
 * @param message The message to be logged
 */
void
gcAuditLog(GCPluginInterface interface, const char* message, ...);

/// <div rustbindgen hide></div>
/**
 * @brief Releases a datapoint value
 * This function is a wrapper around the `releaseDatapointValueCallback` callback provided by the core. It is used to
 * release a datapoint value that was previously returned by the core.
 *
 * @param interface The interface that the core provides to the plugin
 * @param dataValue The datapoint value to be released
 */

void
gcReleaseDatapointValue(GCPluginInterface interface, GCDatapointValue dataValue);

/// <div rustbindgen hide></div>
/**
 * @brief Publishes a datapoint value
 * This function is a wrapper around the `publishDatapointValueCallback` callback provided by the core. It is used to
 * publish a new datapoint value to the core.
 *
 * @param interface The interface that the core provides to the plugin
 * @param dataValue The datapoint value to be published
 * @return true if the value was published successfully, false if an error occurred while publishing the value
 */
bool
gcPublishDatapointValue(GCPluginInterface interface, GCDatapointValue dataValue);

#ifdef __cplusplus
}
#endif

#endif // ABI_H
