#include "ice_streamer.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "utilities_debug/cabot_assert.h"
#include "utilities_debug/trace.h"
#include "frost_mem.h"
#include "frost_list.h"
#include "frost_str.h"

TRACE_IMPLEMENT("ice/ice_streamer_plugin");



/*
 * Global variables
 */
static FROST_List *ice_streamer_plugin_list;

/*
 * Function prototypes
 */
static frost_bool ice_streamer_plugin_setup (ice_streamer_plugin_t *plugin, const ice_streamer_plugin_desc_t *desc);
static frost_bool ice_streamer_plugin_scan_directory (const char *dir);


/**
 * @internal 
 * Lookup the value of a plugin's config property, given the property key.
 * @param[in] plugin The plugin
 * @param[in] key The property key (config path)
 * @return A config value
 * @todo config value <-> property fixup
 */
ice_streamer_config_property_t *
ice_streamer_plugin_config_lookup (ice_streamer_plugin_t *plugin,
                           const char *key)
{
    char path[ICE_PLUGIN_SHORTNAME_MAX_LEN + 256];
    ice_streamer_config_property_t *prop;

    frost_return_val_if_fail (plugin, NULL);
    frost_return_val_if_fail (key, NULL);

    frost_snprintf (path, sizeof (path), "%s.%s",
                ice_streamer_plugin_shortname_get (plugin), key);
    prop = ice_streamer_config_lookup (path);

    return prop;
}

/**
 * @internal 
 * Register a config property for a plugin.
 * @param[in] plugin The plugin
 * @param[in] name The property name
 * @param[in] default_value The default value for the property
 * @param[in] cb A callback function to be executed when the property value
 * changes
 * @param[in] userdata Pointer to data to be passed to the callback
 * @todo config value <-> property fixup
 */
ice_streamer_config_property_t *
ice_streamer_plugin_config_property_register (ice_streamer_plugin_t *plugin,
                                      const char *name,
                                      const char *default_value,
                                      ice_streamer_object_handler_t cb,
                                      frost_pointer userdata)
{
    char fullpath[ICE_PLUGIN_SHORTNAME_MAX_LEN + 256];
    ice_streamer_config_property_t *prop;

    frost_return_val_if_fail (plugin, NULL);
    frost_return_val_if_fail (name, NULL);
    frost_return_val_if_fail (default_value, NULL);

    frost_snprintf (fullpath, sizeof (fullpath), "%s.%s",
                ice_streamer_plugin_shortname_get (plugin), name);

    prop = ice_streamer_config_property_register (fullpath, default_value, cb,
                                          userdata);

    return prop;
}

/**
 * @internal Get the type of this plugin
 * @param[in] plugin The plugin
 * @return The plugin type (#ice_streamer_plugin_type_t)
 */
ice_streamer_plugin_type_t
ice_streamer_plugin_type_get (const ice_streamer_plugin_t *plugin)
{
    frost_return_val_if_fail (plugin,(ice_streamer_plugin_type_t) 0);

    return plugin->type;
}

/**
 * @internal Get the plugin's name. This is just an accessor method.
 * @param[in] plugin The plugin
 * @return A string containing the plugin's name
 */
const char *
ice_streamer_plugin_name_get (const ice_streamer_plugin_t *plugin)
{
    frost_return_val_if_fail (plugin, NULL);

    return (const char *)plugin->name;
}

/**
 * @internal Get the plugin's short name. This is just an accessor method.
 * @param[in] plugin The plugin
 * @return A string containing the plugin's short name
 */
const char *
ice_streamer_plugin_shortname_get (const ice_streamer_plugin_t *plugin)
{
    frost_return_val_if_fail (plugin, NULL);

    return (const char *)plugin->shortname;
}

/**
 * @internal Get the plugin's version. This is just an accessor method.
 * @param[in] plugin The plugin
 * @return A string containing the plugin's version
 */
const char *
ice_streamer_plugin_version_get (const ice_streamer_plugin_t *plugin)
{
    frost_return_val_if_fail (plugin, NULL);

    return (const char *)plugin->version;
}

/**
 * @internal Get the plugin's description. This is just an accessor method.
 * @param[in] plugin The plugin
 * @return A string containing the plugin's description
 */
const char *
ice_streamer_plugin_description_get (const ice_streamer_plugin_t *plugin)
{
    frost_return_val_if_fail (plugin, NULL);

    return (const char *)plugin->description;
}

/*
 * Private functions
 */


static void
ice_streamer_plugin_add_builtin_plugins (void)
{
    for(int plugin = ICE_PLUGIN_MAGIC; plugin < ICE_PLUGIN_COUNT; plugin++)
    {
        ice_streamer_plugin_load((ice_plugin_id_t)plugin);
    }
    
}


/**
 * @internal Initialise the plugin system
 * @param[in] path Absolute path to the plugins directory.
 * @return Whether the initialisation was successful or not.
 */
frost_bool
ice_streamer_plugin_init ()
{

    ice_streamer_plugin_add_builtin_plugins ();
    return frost_true;
}

/**
 * @internal Shut down the plugin system. This function unrefs all the plugins
 * loaded.
 */
void
ice_streamer_plugin_shutdown ()
{
#ifdef HAVE_VALGRIND
    /* print out a leak summary at this point, because the final leak
     * summary won't include proper backtraces of leaks found in
     * plugins, since we close the so's here.
     *
     * note: the following call doesn't do anything if we're not run
     * in valgrind
     */
    VALGRIND_DO_LEAK_CHECK
        ;
#endif

    while (ice_streamer_plugin_list) {
        ice_streamer_plugin_t *p = (ice_streamer_plugin_t *)ice_streamer_plugin_list->data;

        /* if this plugin's refcount is > 1, then there's a bug
         * in one of the other subsystems
         */
        if (p->object.ref > 1) {
            TRACE_INFO(("%s's refcount is %i",
                      p->name, p->object.ref));
        }

        ice_streamer_object_unref (p);

        ice_streamer_plugin_list = frost_list_delete_link (ice_streamer_plugin_list,
                                               ice_streamer_plugin_list);
    }
}


ice_streamer_plugin_t *
ice_streamer_plugin_load (ice_plugin_id_t plugin_type)
{
    ice_streamer_plugin_t *plugin;
    ice_streamer_plugin_t *(*allocer) (void);
    frost_bool (*verifier) (ice_streamer_plugin_t *);
    frost_int expected_ver;
    const ice_streamer_plugin_desc_t *desc;
    
    switch(plugin_type)
    {
        case ICE_PLUGIN_MAGIC:
            desc = getMagicPluginDesc();
            break;
        case ICE_PLUGIN_ASF:
            desc = getAsfPluginDesc();
            break;
        case ICE_PLUGIN_CURL:
            desc = getCurlPluginDesc();
            break;
        case ICE_PLUGIN_MSTAR:
            desc = getMstarPluginDesc();
            break;
        case ICE_PLUGIN_DISKWRITE:
            desc = getDiskWritePluginDesc();
            break;        
        case ICE_PLUGIN_RINGBUF:
            desc = getRingBufPluginDesc();
            break;
        case ICE_PLUGIN_EXTERNAL:
            desc = getExternalDataPluginDesc();
            break;         
        default:
            return NULL;
    }

    switch (desc->type) {
    case ICE_PLUGIN_TYPE_OUTPUT:
        expected_ver = ICE_STREAMER_OUTPUT_API_VERSION;
        allocer = ice_streamer_output_plugin_new;
        verifier = ice_streamer_output_plugin_verify;
        break;
    case ICE_PLUGIN_TYPE_XFORM:
        expected_ver = ICE_STREAMER_XFORM_API_VERSION;
        allocer = (ice_streamer_plugin_t *(*)(void))ice_streamer_handler_plugin_new;
        verifier = (frost_bool (*) (ice_streamer_plugin_t *))ice_streamer_handler_plugin_verify;
        break;
    default:
        TRACE_ERROR(("Unknown plugin type!\n"));
        return NULL;
    }

    if (desc->api_version != expected_ver) {
        TRACE_ERROR(("Bad api version!\n"));
        return NULL;
    }

    plugin = allocer ();
    if (!plugin) {
        TRACE_ERROR(("Alloc failed!\n"));
        return NULL;
    }

    if (!ice_streamer_plugin_setup (plugin, desc)) {
        TRACE_ERROR(("Setup failed for plugin '%s'!", desc->name));
        ice_streamer_object_unref (plugin);
        return NULL;
    }

    if (!desc->setup_func (plugin)) {
        TRACE_ERROR(("Setup function failed for plugin '%s'!",
                        desc->name));
        ice_streamer_object_unref (plugin);
        return NULL;
    }

    if (!verifier (plugin)) {
        TRACE_ERROR(("Verify failed for plugin '%s'!", desc->name));
        ice_streamer_object_unref (plugin);
        return NULL;
    }

    TRACE_INFO(("Loading plugin '%s', plugin  : 0x%x\n", desc->name, plugin));
    
    ice_streamer_plugin_list = frost_list_append (ice_streamer_plugin_list, plugin);
    return plugin;
}

/**
 * @internal Apply a function to all plugins of specified type.
 * @param[in] type The type of plugin to look for.
 * @param[in] func function to apply.
 * @param[in] user_data Userspecified data passed to function.
 */
void
ice_streamer_plugin_foreach (ice_streamer_plugin_type_t type, ice_streamer_plugin_foreach_func_t func, frost_pointer user_data)
{
    FROST_List *node;

    for (node = ice_streamer_plugin_list; node; node = frost_list_next (node)) {
        ice_streamer_plugin_t *plugin = (ice_streamer_plugin_t *)node->data;

        if (plugin->type == type || type == ICE_PLUGIN_TYPE_ALL) {
            if (!func (plugin, user_data))
                break;
        }
    }
}

typedef struct {
    const char *name;
    ice_streamer_plugin_t *plugin;
} ice_streamer_plugin_find_foreach_data_t;

static frost_bool
ice_streamer_plugin_find_foreach (ice_streamer_plugin_t *plugin, frost_pointer udata)
{
    ice_streamer_plugin_find_foreach_data_t *data = (ice_streamer_plugin_find_foreach_data_t *)udata;

    if ((!frost_ascii_strcasecmp ((const char *)plugin->shortname, data->name)) && 
            !plugin->in_use) {
        ice_streamer_object_ref (plugin);
        data->plugin = plugin;
        return frost_false;
    }
    return frost_true;
}

/**
 * @internal Find a plugin that's been loaded, by a particular type and name
 * @param[in] type The type of plugin to look for
 * @param[in] name The name of the plugin to look for
 * @return The plugin instance, if found. NULL otherwise.
 */
ice_streamer_plugin_t *
ice_streamer_plugin_find (ice_streamer_plugin_type_t type, const char *name)
{
    ice_streamer_plugin_find_foreach_data_t data = {name, NULL};
    ice_streamer_plugin_foreach (type, ice_streamer_plugin_find_foreach, &data);
    return data.plugin;
}


static frost_bool
ice_streamer_plugin_setup (ice_streamer_plugin_t *plugin, const ice_streamer_plugin_desc_t *desc)
{
    plugin->type = desc->type;
    plugin->shortname = (frost_uint8 *)desc->shortname;
    plugin->name = (frost_uint8 *)desc->name;
    plugin->version = (frost_uint8 *)desc->version;
    plugin->description = (frost_uint8 *)desc->description;

    return frost_true;
}

void
ice_streamer_plugin_destroy (ice_streamer_plugin_t *plugin)
{
}

FROST_List*
ice_streamer_plugin_get_list()
{
    return ice_streamer_plugin_list;
}
