
#include "ice_streamer.h"

#include <string.h>
#include <unistd.h>

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

static ICE_IceStreamerStatusCallback g_status_callback_function = 0 ;
static void* g_status_user_data = 0 ;

static ICE_IceStreamerNeedDataCallback g_need_data_callback_function = NULL;
static void* g_need_data_user_data = NULL;

static ICE_IceStreamerWriteDataCallback g_write_data_callback_function = NULL;
static void* g_write_data_user_data = NULL;

TRACE_IMPLEMENT("ice/ice_streamer_handler");

void
ice_streamer_handler_browse_add_entry_property_str (ice_streamer_handler_t *xform,
                                          const char *key,
                                          const char *value)
{
    ice_val_t *val = ice_val_new_string (value);
    ice_streamer_handler_browse_add_entry_property (xform, key, val);
    ice_val_unref (val);
}


void
ice_streamer_handler_browse_add_entry_property_int (ice_streamer_handler_t *xform,
                                          const char *key,
                                          frost_int value)
{
    ice_val_t *val = ice_val_new_int (value);
    ice_streamer_handler_browse_add_entry_property (xform, key, val);
    ice_val_unref (val);
}

frost_bool
ice_streamer_medialib_decode_url (char *url)
{
    int i = 0, j = 0;

    frost_return_val_if_fail (url, frost_true);

    while (url[i]) {
        unsigned char chr = url[i++];

        if (chr == '+') {
            url[j++] = ' ';
        } else if (chr == '%') {
            char ts[3];
            char *t;

            ts[0] = url[i++];
            if (!ts[0])
                return frost_false;
            ts[1] = url[i++];
            if (!ts[1])
                return frost_false;
            ts[2] = '\0';

            url[j++] = strtoul (ts, &t, 16);
            if (t != &ts[2])
                return frost_false;
        } else {
            url[j++] = chr;
        }
    }

    url[j] = '\0';

    return frost_true;
}


#define GOODCHAR(a) ((((a) >= 'a') && ((a) <= 'z')) || \
                     (((a) >= 'A') && ((a) <= 'Z')) || \
                     (((a) >= '0') && ((a) <= '9')) || \
                     ((a) == ':') || \
                     ((a) == '/') || \
                     ((a) == '-') || \
                     ((a) == '.') || \
                     ((a) == '_'))

/* we don't share code here with medialib because we want to use g_malloc :( */
char *
ice_streamer_medialib_url_encode (const char *path)
{
    char hex[17] = "0123456789abcdef";
    char *res;
    int i = 0, j = 0;

    res = (char*)frost_malloc (strlen (path) * 3 + 1);
    if (!res)
        return NULL;

    while (path[i]) {
        unsigned char chr = path[i++];
        if (GOODCHAR (chr)) {
            res[j++] = chr;
        } else if (chr == ' ') {
            res[j++] = '+';
        } else {
            res[j++] = '%';
            res[j++] = hex[((chr & 0xf0) >> 4)];
            res[j++] = hex[(chr & 0x0f)];
        }
    }

    res[j] = '\0';

    return res;
}

void
ice_streamer_handler_browse_add_symlink_args (ice_streamer_handler_t *xform, const char *basename,
                                    const char *url, frost_int nargs, char **args)
{
    frost_basic_string *s;
    char *eurl;
    char bname[32];
    frost_int i;

    if (!basename) {
        frost_snprintf (bname, sizeof (bname), "%d", xform->browse_index++);
        basename = bname;
    }

    ice_streamer_handler_browse_add_entry (xform, basename, 0);
    eurl = ice_streamer_medialib_url_encode (url);
    s = frost_string_new (eurl);

    for (i = 0; i < nargs; i++) {
        frost_string_append_c (s, i == 0 ? '?' : '&');
        frost_string_append (s, args[i]);
    }

    ice_streamer_handler_browse_add_entry_property_str (xform, "realpath", s->str);

    frost_free (eurl);
    frost_string_free (s, frost_true);
}

void
ice_streamer_handler_browse_add_symlink (ice_streamer_handler_t *xform, const char *basename,
                               const char *url)
{
    ice_streamer_handler_browse_add_symlink_args (xform, basename, url, 0, NULL);
}

void
ice_streamer_handler_browse_add_entry_property (ice_streamer_handler_t *xform, const char *key,
                                      ice_val_t *val)
{
    frost_return_if_fail (xform);
    frost_return_if_fail (xform->browse_dict);
    frost_return_if_fail (key);
    frost_return_if_fail (val);

    ice_val_dict_set (xform->browse_dict, key, val);
}

void
ice_streamer_handler_browse_add_entry (ice_streamer_handler_t *xform, const char *filename,
                             frost_uint32 flags)
{
    ice_val_t *val;
    const char *url;
    char *efile, *eurl, *t;
    frost_int l, isdir;

    frost_return_if_fail (filename);

    t = strchr (filename, '/');
    frost_return_if_fail (!t); /* filenames can't contain '/', can they? */

    url = (const char *)ice_streamer_handler_get_url (xform);
    frost_return_if_fail (url);

    xform->browse_dict = ice_val_new_dict ();

    eurl = (char*)url;
    efile = (char*)filename;

    /* can't use g_build_filename as we need to preserve
       slashes stuff like file:/// */
    l = strlen (url);
    if (l && url[l - 1] == '/') {
        t = frost_strdup_printf ("%s%s", eurl, efile);
    } else {
        t = frost_strdup_printf ("%s/%s", eurl, efile);
    }

    isdir = !!(flags & ICE_BROWSE_FLAG_DIR);
    ice_streamer_handler_browse_add_entry_property_str (xform, "path", t);
    ice_streamer_handler_browse_add_entry_property_int (xform, "isdir", isdir);

    val = xform->browse_dict;
    xform->browse_list = frost_list_prepend (xform->browse_list, val);

    frost_free (t);
    frost_free (efile);
    frost_free (eurl);
}

static frost_int
ice_streamer_browse_list_sortfunc (frost_constpointer a, frost_constpointer b)
{
    int r1, r2;
    ice_val_t *val1, *val2, *tmp1, *tmp2;
    const char *s1, *s2;

    val1 = (ice_val_t *) a;
    val2 = (ice_val_t *) b;

    frost_return_val_if_fail (ice_val_get_type (val1) == ICE_STREAMER_V_TYPE_DICT, 0);
    frost_return_val_if_fail (ice_val_get_type (val2) == ICE_STREAMER_V_TYPE_DICT, 0);

    r1 = ice_val_dict_get (val1, "intsort", &tmp1);
    r2 = ice_val_dict_get (val2, "intsort", &tmp2);

    if (r1 && r2) {
        frost_int i1, i2;

        if (!ice_val_get_int (tmp1, &i1))
            return 0;
        if (!ice_val_get_int (tmp2, &i2))
            return 0;
        return i1 > i2;
    }

    if (!ice_val_dict_get (val1, "path", &tmp1))
        return 0;
    if (!ice_val_dict_get (val2, "path", &tmp2))
        return 0;

    if (!ice_val_get_string (tmp1, &s1))
        return 0;
    if (!ice_val_get_string (tmp2, &s2))
        return 0;

    return strcmp (s1, s2);
}

FROST_List *
ice_streamer_handler_browse_method (ice_streamer_handler_t *xform, const char *url,
                          ice_error_code_t *error)
{
    FROST_List *list = NULL;

    if (ice_streamer_handler_plugin_can_browse (xform->plugin)) {
        if (!ice_streamer_handler_plugin_browse (xform->plugin, xform, url, error)) {
            return NULL;
        }
        list = xform->browse_list;
        xform->browse_list = NULL;
        list = frost_list_sort (list, ice_streamer_browse_list_sortfunc);
    } else {
        ice_streamer_error_set (error, ICE_ERROR_GENERIC, "Couldn't handle that URL");
    }

    return list;
}

FROST_List *
ice_streamer_handler_browse (const char *url, ice_error_code_t *error)
{
    FROST_List *list = NULL;
    char *durl;
    ice_streamer_handler_t *xform = NULL;
    ice_streamer_handler_t *xform2 = NULL;

    xform = ice_streamer_handler_new (NULL, NULL, 0, NULL);

    durl = frost_strdup (url);
    ice_streamer_medialib_decode_url (durl);
    TRACE_INFO(("url = %s", durl));

    ice_streamer_handler_outdata_type_add (xform,
                                 ICE_STREAM_TYPE_MIMETYPE,
                                 "application/x-url",
                                 ICE_STREAM_TYPE_URL,
                                 durl,
                                 ICE_STREAM_TYPE_END);

    xform2 = ice_streamer_handler_find (xform, 0, NULL);
    if (xform2) {
        TRACE_INFO(("found xform %s", ice_streamer_handler_shortname (xform2)));
    } else {
        ice_streamer_error_set (error, ICE_ERROR_GENERIC, "Couldn't handle that URL");
        ice_streamer_object_unref (xform);
        frost_free (durl);
        return NULL;
    }

    list = ice_streamer_handler_browse_method (xform2, durl, error);

    ice_streamer_object_unref (xform);
    ice_streamer_object_unref (xform2);

    frost_free (durl);

    return list;
}

FROST_List *
ice_streamer_handler_client_browse (ice_streamer_handler_object_t *obj, const char *url,
                          ice_error_code_t *error)
{
    return ice_streamer_handler_browse (url, error);
}

static void
ice_streamer_handler_object_destroy (ice_streamer_object_t *obj)
{
//    ice_streamer_handler_unregister_ipc_commands ();
}

ice_streamer_handler_object_t *
ice_streamer_handler_object_init (void)
{
    ice_streamer_handler_object_t *obj;

    obj = ice_streamer_object_new (ice_streamer_handler_object_t, ice_streamer_handler_object_destroy);

//    ice_streamer_handler_register_ipc_commands (ice_streamer_object (obj));

    effect_callbacks_init ();

    return obj;
}

void
ice_streamer_handler_destroy (ice_streamer_object_t *object)
{
    ice_streamer_handler_t *xform = (ice_streamer_handler_t *)object;

    TRACE_INFO(("Freeing xform '%s'\n", ice_streamer_handler_shortname (xform)));

    /* The 'destroy' method is not mandatory */
    if (xform->plugin && xform->inited) {
        if (ice_streamer_handler_plugin_can_destroy (xform->plugin)) {
            ice_streamer_handler_plugin_destroy (xform->plugin, xform);
        }
    }

    frost_hash_table_destroy (xform->metadata);

    frost_hash_table_destroy (xform->privdata);
    frost_queue_free (xform->hotspots);

    if(xform->buffer && xform->buffer_is_static == frost_false)
    {
//        frost_free (xform->buffer);
    }

    ice_streamer_object_unref (xform->out_type);
    ice_streamer_object_unref (xform->plugin);

    if (xform->prev) {
        ice_streamer_object_unref (xform->prev);
    }

}

ice_streamer_handler_t *
ice_streamer_handler_new (ice_streamer_handler_plugin_t *plugin, ice_streamer_handler_t *prev,
                frost_int32 entry, FROST_List *goal_hints)
{
    ice_streamer_handler_t *xform;

    xform = ice_streamer_object_new (ice_streamer_handler_t, ice_streamer_handler_destroy);

    ice_streamer_object_ref (plugin);
    xform->plugin = plugin;
    xform->entry = entry;
    xform->goal_hints = goal_hints;
    xform->lr.bufend = &xform->lr.buf[0];

    if (prev) {
        ice_streamer_object_ref (prev);
        xform->prev = prev;
    }

    xform->metadata = frost_hash_table_new_full (frost_str_hash, frost_str_equal,
                                             frost_free,
                                             (FROSTDestroyNotify) ice_val_unref);

    xform->privdata = frost_hash_table_new_full (frost_str_hash, frost_str_equal,
                                             frost_free,
                                             (FROSTDestroyNotify) ice_val_unref);
    xform->hotspots = frost_queue_new ();

    if (plugin && entry) {
        if (!ice_streamer_handler_plugin_init (xform->plugin, xform)) {
            ice_streamer_object_unref (xform);
            return NULL;
        }
        xform->inited = frost_true;
        frost_return_val_if_fail (xform->out_type, NULL);
    }

//    xform->buffer = (char*)frost_malloc (READ_CHUNK);
    xform->buffersize = READ_CHUNK;
    xform->buffer_is_static = frost_false ;

    return xform;
}

frost_int32
ice_streamer_handler_entry_get (ice_streamer_handler_t *xform)
{
    return xform->entry;
}

frost_pointer
ice_streamer_handler_private_data_get (ice_streamer_handler_t *xform)
{
    return xform->priv;
}

void
ice_streamer_handler_private_data_set (ice_streamer_handler_t *xform, frost_pointer data)
{
    xform->priv = data;
}

void
ice_streamer_handler_outdata_type_add (ice_streamer_handler_t *xform, ...)
{
    va_list ap;
    va_start (ap, xform);
    xform->out_type = ice_streamer_stream_type_parse (ap);
    va_end (ap);
}

void
ice_streamer_handler_outdata_type_set (ice_streamer_handler_t *xform, ice_streamer_stream_type_t *type)
{
    ice_streamer_object_ref (type);
    xform->out_type = type;
}

void
ice_streamer_handler_outdata_type_copy (ice_streamer_handler_t *xform)
{
    ice_streamer_object_ref (xform->prev->out_type);
    xform->out_type = xform->prev->out_type;
}

const char *
ice_streamer_handler_indata_find_str (ice_streamer_handler_t *xform, ice_streamer_stream_type_key_t key)
{
    const char *r;
    r = ice_streamer_stream_type_get_str (xform->prev->out_type, key);
    if (r) {
        return r;
    } else if (xform->prev) {
        return ice_streamer_handler_indata_find_str (xform->prev, key);
    }
    return NULL;
}

const char *
ice_streamer_handler_indata_get_str (ice_streamer_handler_t *xform, ice_streamer_stream_type_key_t key)
{
    return ice_streamer_stream_type_get_str (xform->prev->out_type, key);
}

frost_int
ice_streamer_handler_indata_get_int (ice_streamer_handler_t *xform, ice_streamer_stream_type_key_t key)
{
    return ice_streamer_stream_type_get_int (xform->prev->out_type, key);
}

ice_streamer_stream_type_t *
ice_streamer_handler_outtype_get (ice_streamer_handler_t *xform)
{
    return xform->out_type;
}

ice_streamer_stream_type_t *
ice_streamer_handler_intype_get (ice_streamer_handler_t *xform)
{
    return ice_streamer_handler_outtype_get (xform->prev);
}



const char *
ice_streamer_handler_outtype_get_str (ice_streamer_handler_t *xform, ice_streamer_stream_type_key_t key)
{
    return ice_streamer_stream_type_get_str (xform->out_type, key);
}

frost_int
ice_streamer_handler_outtype_get_int (ice_streamer_handler_t *xform, ice_streamer_stream_type_key_t key)
{
    return ice_streamer_stream_type_get_int (xform->out_type, key);
}


void
ice_streamer_handler_metadata_set_int (ice_streamer_handler_t *xform, const char *key, int val)
{
    TRACE_INFO(("Setting '%s' to %d\n", key, val));
    frost_hash_table_insert (xform->metadata, frost_strdup (key),
                         ice_val_new_int (val));
    xform->metadata_changed = frost_true;
}

void
ice_streamer_handler_metadata_set_str (ice_streamer_handler_t *xform, const char *key,
                             const char *val)
{
    const char *old;

    if (!frost_utf8_validate (val, -1, NULL)) {
        TRACE_ERROR(("xform '%s' tried to set property '%s' to a NON UTF-8 string!", ice_streamer_handler_shortname (xform), key));
        return;
    }

    if (ice_streamer_handler_metadata_get_str (xform, key, &old)) {
        if (strcmp (old, val) == 0) {
            return;
        }
    }

    frost_hash_table_insert (xform->metadata, frost_strdup (key),
                         ice_val_new_string (val));

    xform->metadata_changed = frost_true;
}

static const ice_val_t *
ice_streamer_handler_metadata_get_val (ice_streamer_handler_t *xform, const char *key)
{
    ice_val_t *val = NULL;

    for (; xform; xform = xform->prev) {
        val = (ice_val_t *)frost_hash_table_lookup (xform->metadata, key);
        if (val) {
            break;
        }
    }

    return val;
}

frost_bool
ice_streamer_handler_metadata_has_val (ice_streamer_handler_t *xform, const char *key)
{
    return (!!ice_streamer_handler_metadata_get_val (xform, key)) ? frost_true : frost_false ;
}

frost_bool
ice_streamer_handler_metadata_get_int (ice_streamer_handler_t *xform, const char *key,
                             frost_int32 *val)
{
    const ice_val_t *obj;
    frost_bool ret = frost_false;

    obj = ice_streamer_handler_metadata_get_val (xform, key);
    if (obj && ice_val_get_type (obj) == ICE_STREAMER_V_TYPE_INT32) {
        ice_val_get_int (obj, val);
        ret = frost_true;
    }

    return ret;
}

frost_bool
ice_streamer_handler_metadata_get_str (ice_streamer_handler_t *xform, const char *key,
                             const char **val)
{
    const ice_val_t *obj;
    frost_bool ret = frost_false;

    obj = ice_streamer_handler_metadata_get_val (xform, key);
    if (obj && ice_val_get_type (obj) == ICE_STREAMER_V_TYPE_STRING) {
        ice_val_get_string (obj, val);
        ret = frost_true;
    }

    return ret;
}

typedef struct {
//    ice_streamer_medialib_session_t *session;
    frost_int32 entry;
    frost_uint32 source;
} metadata_festate_t;

static void
add_metadatum (frost_pointer _key, frost_pointer _value, frost_pointer user_data)
{
    ice_val_t *value = (ice_val_t *) _value;
    char *key = (char *) _key;
    metadata_festate_t *st = (metadata_festate_t *) user_data;

    if (ice_val_get_type (value) == ICE_STREAMER_V_TYPE_STRING) {
        const char *s;
        ice_val_get_string (value, &s);
//        ice_streamer_medialib_entry_property_set_str_source (st->session,
//                                                     st->entry,
//                                                     key,
//                                                     s,
//                                                     st->source);
    } else if (ice_val_get_type (value) == ICE_STREAMER_V_TYPE_INT32) {
        frost_int i;
        ice_val_get_int (value, &i);
//        ice_streamer_medialib_entry_property_set_int_source (st->session,
//                                                     st->entry,
//                                                     key,
//                                                     i,
//                                                     st->source);
    } else {
        TRACE_INFO(("Unknown type?!?\n"));
    }
}

static void
ice_streamer_handler_metadata_collect_one (ice_streamer_handler_t *xform, metadata_festate_t *info)
{
    char src[ICE_STREAMER_PLUGIN_SHORTNAME_MAX_LEN + 8];

    TRACE_INFO(("Collecting metadata from %s", ice_streamer_handler_shortname (xform)));

    frost_snprintf (src, sizeof (src), "plugin/%s",
                ice_streamer_handler_shortname (xform));

//    info->source = ice_streamer_medialib_source_to_id (info->session, src);
    frost_hash_table_foreach (xform->metadata, add_metadatum, info);

    xform->metadata_changed = frost_false;
}

static void
ice_streamer_handler_metadata_collect_r (ice_streamer_handler_t *xform, metadata_festate_t *info,
                               frost_basic_string *namestr)
{
    if (xform->prev) {
        ice_streamer_handler_metadata_collect_r (xform->prev, info, namestr);
    }

    if (xform->plugin) {
        if (namestr->len) {
            frost_string_append_c (namestr, ':');
        }
        frost_string_append (namestr, ice_streamer_handler_shortname (xform));
    }

    if (xform->metadata_changed) {
        ice_streamer_handler_metadata_collect_one (xform, info);
    }

    xform->metadata_collected = frost_true;
}

static void
ice_streamer_handler_metadata_collect (ice_streamer_handler_t *start, frost_basic_string *namestr, frost_bool rehashing)
{
    metadata_festate_t info;
    frost_int times_played;
    frost_int last_started;
    frost_uint32 now = FROST_Time();

    info.entry = start->entry;
//    info.session = ice_streamer_medialib_begin_write ();

//    times_played = ice_streamer_medialib_entry_property_get_int (info.session,
//                                                         info.entry,
//                                                         ICE_MEDIALIB_ENTRY_PROPERTY_TIMESPLAYED);

    /* times_played == -1 if we haven't played this entry yet. so after initial
     * metadata collection the mlib would have timesplayed = -1 if we didn't do
     * the following */
    if (times_played < 0) {
        times_played = 0;
    }

//    last_started = ice_streamer_medialib_entry_property_get_int (info.session,
//                                                         info.entry,
//                                                         ICE_MEDIALIB_ENTRY_PROPERTY_LASTSTARTED);
//
//    ice_streamer_medialib_entry_cleanup (info.session, info.entry);
//
//    ice_streamer_handler_metadata_collect_r (start, &info, namestr);
//
//    ice_streamer_medialib_entry_property_set_str (info.session, info.entry,
//                                          ICE_MEDIALIB_ENTRY_PROPERTY_CHAIN,
//                                          namestr->str);
//
//    ice_streamer_medialib_entry_property_set_int (info.session, info.entry,
//                                          ICE_MEDIALIB_ENTRY_PROPERTY_TIMESPLAYED,
//                                          times_played + (rehashing ? 0 : 1));

    if (!rehashing || (rehashing && last_started)) {

//        ice_streamer_medialib_entry_property_set_int (info.session, info.entry,
//                                              ICE_MEDIALIB_ENTRY_PROPERTY_LASTSTARTED,
//                                              (rehashing ? last_started : (frost_int)(now/1000)));
    }

//    ice_streamer_medialib_entry_status_set (info.session, info.entry,
//                                    ICE_MEDIALIB_ENTRY_STATUS_OK);
//
//    ice_streamer_medialib_end (info.session);
//    ice_streamer_medialib_entry_send_update (info.entry);
}

static void
ice_streamer_handler_metadata_update (ice_streamer_handler_t *xform)
{
    metadata_festate_t info;

    info.entry = xform->entry;
//    info.session = ice_streamer_medialib_begin_write ();

    ice_streamer_handler_metadata_collect_one (xform, &info);

//    ice_streamer_medialib_end (info.session);
//    ice_streamer_medialib_entry_send_update (info.entry);
}

static void
ice_streamer_handler_auxdata_set_val (ice_streamer_handler_t *xform, char *key, ice_val_t *val)
{
    ice_streamer_handler_hotspot_t *hs;

    hs = frost_new0 (ice_streamer_handler_hotspot_t, 1);
    hs->pos = xform->buffered;
    hs->key = key;
    hs->obj = val;

    frost_queue_push_tail (xform->hotspots, hs);
}

void
ice_streamer_handler_auxdata_barrier (ice_streamer_handler_t *xform)
{
    ice_val_t *val = ice_val_new_none ();
    ice_streamer_handler_auxdata_set_val (xform, NULL, val);
}

void
ice_streamer_handler_auxdata_set_int (ice_streamer_handler_t *xform, const char *key, int intval)
{
    ice_val_t *val = ice_val_new_int (intval);
    ice_streamer_handler_auxdata_set_val (xform, frost_strdup (key), val);
}

void
ice_streamer_handler_auxdata_set_str (ice_streamer_handler_t *xform, const char *key,
                            const char *strval)
{
    ice_val_t *val;
    const char *old;

    if (ice_streamer_handler_auxdata_get_str (xform, key, &old)) {
        if (strcmp (old, strval) == 0) {
            return;
        }
    }

    val = ice_val_new_string (strval);
    ice_streamer_handler_auxdata_set_val (xform, frost_strdup (key), val);
}

void
ice_streamer_handler_auxdata_set_bin (ice_streamer_handler_t *xform, const char *key,
                            frost_pointer data, frost_long len)
{
    ice_val_t *val;

    val = ice_val_new_bin ((const unsigned char *)data, len);
    ice_streamer_handler_auxdata_set_val (xform, frost_strdup (key), val);
}

static const ice_val_t *
ice_streamer_handler_auxdata_get_val (ice_streamer_handler_t *xform, const char *key)
{
    frost_uint i;
    ice_streamer_handler_hotspot_t *hs;
    ice_val_t *val = NULL;

    /* privdata is always got from the previous xform */
    xform = xform->prev;

    /* check if we have unhandled current (pos 0) hotspots for this key */
    for (i=0;
            (hs =(ice_streamer_handler_hotspot_t *) frost_queue_peek_nth (xform->hotspots, i)) != NULL;
            i++) {
        if (hs->pos != 0) {
            break;
        } else if (hs->key && !strcmp (key, hs->key)) {
            val = hs->obj;
        }
    }

    if (!val) {
        val = (ice_val_t *)frost_hash_table_lookup (xform->privdata, key);
    }

    return val;
}

frost_bool
ice_streamer_handler_auxdata_has_val (ice_streamer_handler_t *xform, const char *key)
{
    return (!!ice_streamer_handler_auxdata_get_val (xform, key)) ? frost_true : frost_false ;
}

frost_bool
ice_streamer_handler_auxdata_get_int (ice_streamer_handler_t *xform, const char *key, frost_int32 *val)
{
    const ice_val_t *obj;

    obj = ice_streamer_handler_auxdata_get_val (xform, key);
    if (obj && ice_val_get_type (obj) == ICE_STREAMER_V_TYPE_INT32) {
        ice_val_get_int (obj, val);
        return frost_true;
    }

    return frost_false;
}

frost_bool
ice_streamer_handler_auxdata_get_str (ice_streamer_handler_t *xform, const char *key,
                            const char **val)
{
    const ice_val_t *obj;

    obj = ice_streamer_handler_auxdata_get_val (xform, key);
    if (obj && ice_val_get_type (obj) == ICE_STREAMER_V_TYPE_STRING) {
        ice_val_get_string (obj, val);
        return frost_true;
    }

    return frost_false;
}

frost_bool
ice_streamer_handler_auxdata_get_bin (ice_streamer_handler_t *xform, const char *key,
                            const frost_uint8 **data, frost_ulong *datalen)
{
    const ice_val_t *obj;

    obj = ice_streamer_handler_auxdata_get_val (xform, key);
    if (obj && ice_val_get_type (obj) == ICE_STREAMER_V_TYPE_BIN) {
        ice_val_get_bin (obj, data, (unsigned int *)datalen);
        return frost_true;
    }

    return frost_false;
}

const char *
ice_streamer_handler_shortname (ice_streamer_handler_t *xform)
{
    return (xform->plugin)
           ? ice_streamer_plugin_shortname_get ((ice_streamer_plugin_t *) xform->plugin)
           : "unknown";
}

static frost_int
ice_streamer_handler_this_peek (ice_streamer_handler_t *xform, frost_pointer buf, frost_int siz,
                      ice_error_code_t *err)
{
    while (xform->buffered < siz) {
        frost_int res;

        if (xform->buffered + READ_CHUNK > xform->buffersize) {
            xform->buffersize *= 2;
//            xform->buffer = (char*)frost_realloc (xform->buffer, xform->buffersize);
        }

        res = ice_streamer_handler_plugin_read (xform->plugin, xform,
                                      &xform->buffer[xform->buffered],
                                      READ_CHUNK, err);

        if (res < -1) {
            TRACE_INFO(("Read method of %s returned bad value (%d) - BUG IN PLUGIN",
                      ice_streamer_handler_shortname (xform), res));
            res = -1;
        }

        if (res == 0) {
            xform->eos = frost_true;
            break;
        } else if (res == -1) {
            xform->error = frost_true;
            return -1;
        } else {
            xform->buffered += res;
        }
    }

    /* might have eosed */
    siz = MIN (siz, xform->buffered);
    memcpy (buf, xform->buffer, siz);
    return siz;
}

static void
ice_streamer_handler_hotspot_callback (frost_pointer data, frost_pointer user_data)
{
    ice_streamer_handler_hotspot_t *hs = (ice_streamer_handler_hotspot_t *)data;
    frost_int *read = (frost_int *)user_data;

    hs->pos -= *read;
}

static frost_int
ice_streamer_handler_hotspots_update (ice_streamer_handler_t *xform)
{
    ice_streamer_handler_hotspot_t *hs;
    frost_int ret = -1;

    hs = (ice_streamer_handler_hotspot_t *)frost_queue_peek_head (xform->hotspots);
    while (hs != NULL && hs->pos == 0) {
        frost_queue_pop_head (xform->hotspots);
        if (hs->key) {
            frost_hash_table_insert (xform->privdata, hs->key, hs->obj);
        }
        hs = (ice_streamer_handler_hotspot_t *)frost_queue_peek_head (xform->hotspots);
    }

    if (hs != NULL) {
        ret = hs->pos;
    }

    return ret;
}


void
ice_streamer_status_callback_set(ICE_IceStreamerStatusCallback callback_function, void *user_data)
{
    g_status_user_data = user_data ,
            g_status_callback_function = callback_function ;
}

void
ice_streamer_need_data_callback_set(ICE_IceStreamerNeedDataCallback need_data_callback, void *user_data)
{
    g_need_data_callback_function = need_data_callback;
    g_need_data_user_data = user_data;
}

void
ice_streamer_write_data_callback_set(ICE_IceStreamerWriteDataCallback write_data_callback, void *user_data)
{
    g_write_data_callback_function = write_data_callback;
    g_write_data_user_data = user_data;
}

void
ice_streamer_handler_need_data(frost_uint8 *buffer, frost_uint buffer_len)
{
    TRACE_INFO(("In %s(): Need data callback is called\n", __FUNCTION__));
    if (g_need_data_callback_function)
    {
        g_need_data_callback_function(buffer, buffer_len, g_need_data_user_data);
    }
}

frost_bool
ice_streamer_handler_write_data(frost_uint8 *buffer, frost_uint length, frost_bool end_of_data)
{
    frost_bool result = frost_false;
    if (g_write_data_callback_function)
    {
        result = g_write_data_callback_function(buffer, length, end_of_data, g_write_data_user_data);
    }
    return result;
}

frost_int
ice_streamer_handler_this_read (ice_streamer_handler_t *xform, frost_pointer buf, frost_int siz,
                      ice_error_code_t *err)
{
    frost_int read = 0;
    frost_int nexths;

    TRACE_INFO(("In %s(): ENTRY\n", __FUNCTION__));

    if (xform->error) {
        ice_streamer_error_set (err, ICE_ERROR_GENERIC, "Read on errored xform");
        return -1;
    }

    /* update hotspots */
    nexths = ice_streamer_handler_hotspots_update (xform);
    if (nexths >= 0) {
        siz = MIN (siz, nexths);
    }

    if (xform->buffered) {
        read = MIN (siz, xform->buffered);
        memcpy (buf, xform->buffer, read);
        xform->buffered -= read;

        /* buffer edited, update hotspot positions */
        frost_queue_foreach (xform->hotspots, &ice_streamer_handler_hotspot_callback, &read);

        if (xform->buffered) {
            /* unless we are _peek:ing often
               this should be fine */
            memmove (xform->buffer, &xform->buffer[read], xform->buffered);
        }
    }

    if (xform->eos) {
        return read;
    }

    while (read < siz) {
        frost_int res;
        frost_pointer bb = (char*)buf + read ;
        res = ice_streamer_handler_plugin_read (xform->plugin, xform, (bb), siz - read, err);
        if (xform->metadata_collected && xform->metadata_changed)
            ice_streamer_handler_metadata_update (xform);

        if (res < -1) {
            TRACE_INFO(("Read method of %s returned bad value (%d) - BUG IN PLUGIN", ice_streamer_handler_shortname (xform), res));
            res = -1;
        }

        if (res == 0) {
            xform->eos = frost_true;
//            if(g_status_callback_function)
//            {
//                g_status_callback_function(g_status_user_data,ice_streamer_completed);
//            }
            break;
        } else if (res == -1) {
            /*if(g_status_callback_function)
            {
                g_status_callback_function(g_status_user_data,ice_streamer_error);
            }*/
            xform->error = frost_true;
            return -1;
        } else {
            if (read == 0)
                ice_streamer_handler_hotspots_update (xform);

            if (!frost_queue_is_empty (xform->hotspots)) {
                if (xform->buffered + res > xform->buffersize) {
                    xform->buffersize = MAX (xform->buffersize * 2,
                                             xform->buffersize + res);
//                    if(xform->buffer_is_static == frost_false)
//                    {
//                        xform->buffer = (char*)frost_realloc (xform->buffer,
//                                                   xform->buffersize);
//                    }
//                    else
//                    {
//                        xform->buffer = (char*)frost_malloc (xform->buffersize);
//                        xform->buffer_is_static = frost_false ;
//                    }
                }
                frost_pointer bb = xform->buffer + xform->buffered;
                frost_pointer cc = (char*)buf + read;
                frost_memmove (bb, cc , res);
                xform->buffered += res;
                break;
            }
            read += res;
        }
    }

    return read;
}

frost_int64
ice_streamer_handler_this_seek (ice_streamer_handler_t *xform, frost_int64 offset,
                      ice_seek_mode_t whence, ice_error_code_t *err)
{
    frost_int64 res;

    if (xform->error) {
        ice_streamer_error_set (err, ICE_ERROR_GENERIC, "Seek on errored xform");
        return -1;
    }

    if (!ice_streamer_handler_plugin_can_seek (xform->plugin)) {
        TRACE_INFO(("Seek not implemented in '%s'", ice_streamer_handler_shortname (xform)));
        ice_streamer_error_set (err, ICE_ERROR_GENERIC, "Seek not implemented");
        return -1;
    }

    if (xform->buffered && whence == ICE_SEEK_CUR) {
        offset -= xform->buffered;
    }

    res = ice_streamer_handler_plugin_seek (xform->plugin, xform, offset, whence, err);
    if (res != -1) {
        ice_streamer_handler_hotspot_t *hs;

        xform->eos = frost_false;
        xform->buffered = 0;

        /* flush the hotspot queue on seek */
        while ((hs = (ice_streamer_handler_hotspot_t *)frost_queue_pop_head (xform->hotspots)) != NULL) {
            frost_free (hs->key);
            ice_val_unref (hs->obj);
            frost_free (hs);
        }
    }

    return res;
}

frost_int
ice_streamer_handler_peek (ice_streamer_handler_t *xform, frost_pointer buf, frost_int siz,
                 ice_error_code_t *err)
{
    frost_return_val_if_fail (xform->prev, -1);
    return ice_streamer_handler_this_peek (xform->prev, buf, siz, err);
}

char *
ice_streamer_handler_read_line (ice_streamer_handler_t *xform, char *line, ice_error_code_t *err)
{
    char *p;

    frost_return_val_if_fail (xform, NULL);
    frost_return_val_if_fail (line, NULL);

    p = strchr (xform->lr.buf, '\n');

    if (!p) {
        frost_int l, r;

        l = (ICE_STREAMER_XFORM_MAX_LINE_SIZE - 1) - (xform->lr.bufend - xform->lr.buf);
        if (l) {
            r = ice_streamer_handler_read (xform, xform->lr.bufend, l, err);
            if (r < 0) {
                return NULL;
            }
            xform->lr.bufend += r;
        }
        if (xform->lr.bufend <= xform->lr.buf)
            return NULL;

        *(xform->lr.bufend) = '\0';
        p = strchr (xform->lr.buf, '\n');
        if (!p) {
            p = xform->lr.bufend;
        }
    }

    if (p > xform->lr.buf && *(p-1) == '\r') {
        *(p-1) = '\0';
    } else {
        *p = '\0';
    }

    strcpy (line, xform->lr.buf);
    memmove (xform->lr.buf, p + 1, xform->lr.bufend - p);
    xform->lr.bufend -= (p - xform->lr.buf) + 1;
    *xform->lr.bufend = '\0';

    return line;
}

frost_int
ice_streamer_handler_read (ice_streamer_handler_t *xform, frost_pointer buf, frost_int siz, ice_error_code_t *err)
{
    frost_return_val_if_fail (xform->prev, -1);
    return ice_streamer_handler_this_read (xform->prev, buf, siz, err);
}

frost_int64
ice_streamer_handler_seek (ice_streamer_handler_t *xform, frost_int64 offset,
                 ice_seek_mode_t whence, ice_error_code_t *err)
{
    frost_return_val_if_fail (xform->prev, -1);
    return ice_streamer_handler_this_seek (xform->prev, offset, whence, err);
}

const char *
ice_streamer_handler_get_url (ice_streamer_handler_t *xform)
{
    const char *url = NULL;
    ice_streamer_handler_t *x;
    x = xform;

    while (!url && x) {
        url = ice_streamer_handler_indata_get_str (x, ICE_STREAM_TYPE_URL);
        x = x->prev;
    }

    return url;
}


typedef struct match_state_St {
    ice_streamer_handler_plugin_t *match;
    ice_streamer_stream_type_t *out_type;
    frost_int priority;
} match_state_t;

static frost_bool
ice_streamer_handler_match (ice_streamer_plugin_t *plugin, frost_pointer user_data)
{
    ice_streamer_handler_plugin_t *xform_plugin = (ice_streamer_handler_plugin_t *) plugin;
    match_state_t *state = (match_state_t *) user_data;
    frost_int priority = 0;

    CABOT_ASSERT (plugin->type == ICE_PLUGIN_TYPE_XFORM);

    TRACE_INFO(("Trying plugin '%s' \n", ice_streamer_plugin_shortname_get (plugin)));
    if (!ice_streamer_handler_plugin_supports (xform_plugin, state->out_type, &priority)) {
        return frost_true;
    }

    TRACE_INFO(("Plugin '%s' matched (priority %d) \n",
              ice_streamer_plugin_shortname_get (plugin), priority));

    if (priority > state->priority) {
        if (state->match) {
            ice_streamer_plugin_t *previous_plugin = (ice_streamer_plugin_t *) state->match;
            TRACE_INFO(("Using plugin '%s' (priority %d) instead of '%s' (priority %d) \n",
                      ice_streamer_plugin_shortname_get (plugin), priority,
                      ice_streamer_plugin_shortname_get (previous_plugin),
                      state->priority));
        }

        state->match = xform_plugin;
        state->priority = priority;
    }

    return frost_true;
}

ice_streamer_handler_t *
ice_streamer_handler_find (ice_streamer_handler_t *prev, frost_int32 entry,
                 FROST_List *goal_hints)
{
    match_state_t state;
    ice_streamer_handler_t *xform = NULL;

    state.out_type = prev->out_type;
    state.match = NULL;
    state.priority = -1;

    ice_streamer_plugin_foreach (ICE_PLUGIN_TYPE_XFORM, ice_streamer_handler_match, &state);

    if (state.match) {
        xform = ice_streamer_handler_new (state.match, prev, entry, goal_hints);
    } else {
        TRACE_INFO(("Found no matching plugin...\n"));
    }

    return xform;
}

frost_bool
ice_streamer_handler_iseos (ice_streamer_handler_t *xform)
{
    frost_bool ret = frost_true;

    if (xform->prev) {
        ret = xform->prev->eos;
    }

    return ret;
}

const ice_streamer_stream_type_t *
ice_streamer_handler_get_out_stream_type (ice_streamer_handler_t *xform)
{
    return xform->out_type;
}

const FROST_List *
ice_streamer_handler_goal_hints_get (ice_streamer_handler_t *xform)
{
    return xform->goal_hints;
}


static frost_bool
has_goalformat (ice_streamer_handler_t *xform, FROST_List *goal_formats)
{
    const ice_streamer_stream_type_t *current;
    frost_bool ret = frost_false;
    FROST_List *n;

    current = ice_streamer_handler_get_out_stream_type (xform);

    for (n = goal_formats; n; n = frost_list_next (n)) {
        ice_streamer_stream_type_t *goal_type = (ice_streamer_stream_type_t *)n->data;
        TRACE_INFO(("In %s(): Goal name: %s\n", __FUNCTION__, goal_type->name));
        if (ice_streamer_stream_type_match (goal_type, current)) {
            ret = frost_true;
            break;
        }
    }

    if (!ret) {
        TRACE_INFO(("Not in one of %d goal-types\n", frost_list_length (goal_formats)));
    }

    return ret;
}

const char *
ice_streamer_sample_name_get (ice_streamer_sample_format_t fmt)
{
    switch (fmt) {
    case ICE_SAMPLE_FORMAT_UNKNOWN:
        return "Unknown";
    case ICE_SAMPLE_FORMAT_S8:
        return "S8";
    case ICE_SAMPLE_FORMAT_U8:
        return "U8";
    case ICE_SAMPLE_FORMAT_S16:
        return "S16";
    case ICE_SAMPLE_FORMAT_U16:
        return "U16";
    case ICE_SAMPLE_FORMAT_S32:
        return "S32";
    case ICE_SAMPLE_FORMAT_U32:
        return "U32";
    case ICE_SAMPLE_FORMAT_FLOAT:
        return "FLOAT";
    case ICE_SAMPLE_FORMAT_DOUBLE:
        return "DOUBLE";
    }
    return "UNKNOWN";
}
static void
outdata_type_metadata_collect (ice_streamer_handler_t *xform)
{
    frost_int val;
    const char *mime;
    ice_streamer_stream_type_t *type;

    type = xform->out_type;
    mime = ice_streamer_stream_type_get_str (type, ICE_STREAM_TYPE_MIMETYPE);
    if (strcmp (mime, "audio/pcm") != 0) {
        return;
    }

    val = ice_streamer_stream_type_get_int (type, ICE_STREAM_TYPE_FMT_FORMAT);
    if (val != -1) {
        const char *name = ice_streamer_sample_name_get ((ice_streamer_sample_format_t) val);
        ice_streamer_handler_metadata_set_str (xform,
                                     ICE_MEDIALIB_ENTRY_PROPERTY_SAMPLE_FMT,
                                     name);
    }

    val = ice_streamer_stream_type_get_int (type, ICE_STREAM_TYPE_FMT_SAMPLERATE);
    if (val != -1) {
        ice_streamer_handler_metadata_set_int (xform,
                                     ICE_MEDIALIB_ENTRY_PROPERTY_SAMPLERATE,
                                     val);
    }

    val = ice_streamer_stream_type_get_int (type, ICE_STREAM_TYPE_FMT_CHANNELS);
    if (val != -1) {
        ice_streamer_handler_metadata_set_int (xform,
                                     ICE_MEDIALIB_ENTRY_PROPERTY_CHANNELS,
                                     val);
    }
}

static ice_streamer_handler_t *
chain_setup (frost_int32 entry, const char *url, FROST_List *goal_formats)
{
    ice_streamer_handler_t *xform, *last;
    char *durl, *args;

    if (!entry) {
        entry = 1; /* FIXME: this is soooo ugly, don't do this */
    }

    xform = ice_streamer_handler_new (NULL, NULL, 0, goal_formats);

    durl = frost_strdup (url);

    args = strchr (durl, '?');
    if (args) {
        char **params;
        frost_int i;
        *args = 0;
        args++;
        ice_streamer_medialib_decode_url (args);

        params = frost_strsplit (args, "&", 0);

        for (i = 0; params && params[i]; i++) {
            char *v;
            v = strchr (params[i], '=');
            if (v) {
                *v = 0;
                v++;
                ice_streamer_handler_metadata_set_str (xform, params[i], v);
            } else {
                ice_streamer_handler_metadata_set_int (xform, params[i], 1);
            }
        }
        frost_strfreev (params);
    }
    ice_streamer_medialib_decode_url (durl);

    ice_streamer_handler_outdata_type_add (xform,
                                           ICE_STREAM_TYPE_MIMETYPE, "application/x-url",
                                           ICE_STREAM_TYPE_URL, durl,
                                           ICE_STREAM_TYPE_END);

    frost_free (durl);

    last = xform;

    do {
        xform = ice_streamer_handler_find (last, entry, goal_formats);
        if (!xform) {
            TRACE_ERROR(("Couldn't set up chain for '%s' (%d)",
                            url, entry));
            ice_streamer_object_unref (last);

            return NULL;
        }
        ice_streamer_object_unref (last);
        last = xform;
    } while (!has_goalformat (xform, goal_formats));

    outdata_type_metadata_collect (last);

    return last;
}

static void
chain_finalize (ice_streamer_handler_t *xform, frost_int32 entry,
                const char *url, frost_bool rehashing)
{
    frost_basic_string *namestr;

    namestr = frost_string_new ("");
    ice_streamer_handler_metadata_collect (xform, namestr, rehashing);
    TRACE_INFO(("Successfully setup chain for '%s' (%d) containing %s",
                   url, entry, namestr->str));

    frost_string_free (namestr, frost_true);
}

static char *
get_url_for_entry (frost_int32 entry)
{
//    ice_streamer_medialib_session_t *session;
    char *url = NULL;

//    session = ice_streamer_medialib_begin ();
//    url = ice_streamer_medialib_entry_property_get_str (session, entry,
//                                                ICE_MEDIALIB_ENTRY_PROPERTY_URL);
//    ice_streamer_medialib_end (session);

    if (!url) {
        TRACE_ERROR(("Couldn't get url for entry (%d)", entry));
    }

    return url;
}

ice_streamer_handler_t *
ice_streamer_handler_chain_setup_url (frost_int32 entry, const char *url,
                            FROST_List *goal_formats, frost_bool rehash)
{
    ice_streamer_handler_t *last;
    ice_streamer_plugin_t *plugin;
    ice_streamer_handler_plugin_t *xform_plugin;
    frost_bool add_segment = frost_false;
    frost_int priority;

    last = chain_setup (entry, url, goal_formats);
    if (!last) {
        return NULL;
    }

    /* first check that segment plugin is available in the system */
    plugin = ice_streamer_plugin_find (ICE_PLUGIN_TYPE_XFORM, "segment");
    xform_plugin = (ice_streamer_handler_plugin_t *) plugin;

    /* if segment plugin input is the same as current output, include it
     * for collecting additional duration metadata on audio entries */
    if (xform_plugin) {
        add_segment = ice_streamer_handler_plugin_supports (xform_plugin,
                                                  last->out_type,
                                                  &priority);
        ice_streamer_object_unref (plugin);
    }

    /* add segment plugin to the chain if it can be added */
    if (add_segment) {
        last = ice_streamer_handler_new_effect (last, entry, goal_formats, "segment");
        if (!last) {
            return NULL;
        }
    }

    /* if not rehashing, also initialize all the effect plugins */
    if (!rehash) {
        last = add_effects (last, entry, goal_formats);
        if (!last) {
            return NULL;
        }
    }

    chain_finalize (last, entry, url, rehash);
    return last;
}

ice_streamer_handler_t *
ice_streamer_handler_chain_setup (frost_int32 entry, FROST_List *goal_formats,
                        frost_bool rehash)
{
    char *url;
    ice_streamer_handler_t *xform;

    if (!(url = get_url_for_entry (entry))) {
        return NULL;
    }

    xform = ice_streamer_handler_chain_setup_url (entry, url, goal_formats, rehash);
    frost_free (url);

    return xform;
}

ice_streamer_config_property_t *
ice_streamer_handler_config_lookup (ice_streamer_handler_t *xform, const char *path)
{
    frost_return_val_if_fail (xform->plugin, NULL);

    return ice_streamer_plugin_config_lookup ((ice_streamer_plugin_t *) xform->plugin, path);
}
/**
 * Config file version
 */
#define ICE_CONFIG_VERSION 2
static ice_streamer_config_t *global_config;

/**
 * @}
 * @addtogroup Config
 * @{
 */

/**
 * Config functions
 */

/**
 * Lookup config key and return its associated value as a string.
 * This is a convenient function to make it easier to get a configuration value
 * rather than having to call #ice_streamer_config_property_get_string separately.
 *
 * @param conf Global config
 * @param key Configuration property to lookup
 * @param err if error occurs this will be filled in
 *
 * @return A string with the value. If the value is an int it will return NULL
 */
const char *
ice_streamer_config_property_lookup_get_string (ice_streamer_config_t *conf, const char *key,
                                        ice_error_code_t *err)
{
    ice_streamer_config_property_t *prop;

    prop = (ice_streamer_config_property_t *)ice_streamer_config_lookup (key);
    if (!prop) {
        ice_streamer_error_set (err, ICE_ERROR_NOENT,
                        "Trying to get non-existent property");
        return NULL;
    }

    return ice_streamer_config_property_get_string (prop);
}

static int
compare_key (frost_constpointer a, frost_constpointer b, frost_pointer user_data)
{
    return strcmp ((char *) a, (char *) b);
}

static FROST_Tree *
create_tree (void)
{
    return frost_tree_new_full (compare_key, NULL, frost_free,
                            (FROSTDestroyNotify) __int_ice_streamer_object_unref);
}

static void
clear_config (ice_streamer_config_t *config)
{
    frost_tree_destroy (config->properties);
    config->properties = create_tree ();

    config->version = ICE_CONFIG_VERSION;

    frost_free (config->value_name);
    config->value_name = NULL;
}

static void
ice_streamer_config_destroy (ice_streamer_object_t *object)
{
    ice_streamer_config_t *config = (ice_streamer_config_t *)object;

    FROST_MutexDestroy(config->mutex);

    frost_tree_destroy (config->properties);

//    ice_streamer_config_unregister_ipc_commands ();
}

void
ice_streamer_config_init (void)
{
    ice_streamer_config_t *config;
    int ret, fd = -1;
    frost_bool parserr = frost_false, eof = frost_false;

    config = ice_streamer_object_new (ice_streamer_config_t, ice_streamer_config_destroy);
    config->mutex = FROST_MutexCreate();

    config->properties = create_tree ();

    config->version = 0;
    global_config = config;

//    ice_streamer_config_register_ipc_commands (ICEST_OBJECT (config));

}

/**
 * @internal Shut down the config layer - free memory from the global
 * configuration.
 */
void
ice_streamer_config_shutdown ()
{
    ice_streamer_object_unref (global_config);

}

/**
 * Look up a config key from the global config
 * @param path A configuration path. Could be core.myconfig or
 * effect.foo.myconfig
 * @return An #ice_streamer_config_property_t
 */
ice_streamer_config_property_t *
ice_streamer_config_lookup (const char *path)
{
    ice_streamer_config_property_t *prop;
    frost_return_val_if_fail (global_config, NULL);

    FROST_MutexLock (global_config->mutex);
    prop = (ice_streamer_config_property_t *)frost_tree_lookup (global_config->properties, path);
    FROST_MutexUnlock (global_config->mutex);

    return prop;
}

/**
 * Get the name of a config property.
 * @param prop The config property
 * @return Name of config property
 */
const char *
ice_streamer_config_property_get_name (const ice_streamer_config_property_t *prop)
{
    frost_return_val_if_fail (prop, NULL);

    return prop->name;
}

/**
 * Set the data of the config property to a new value
 * @param prop The config property
 * @param data The value to set
 */
void
ice_streamer_config_property_set_data (ice_streamer_config_property_t *prop, const char *data)
{
    FROST_Tree *dict;

    frost_return_if_fail (prop);
    frost_return_if_fail (data);

    /* check whether the value changed at all */
    if (prop->value && !strcmp (prop->value, data))
        return;

    frost_free (prop->value);
    prop->value = frost_strdup (data);
//    ice_streamer_object_emit (ICEST_OBJECT (prop),
//                      ICE_IPC_SIGNAL_CONFIGVALUE_CHANGED,
//                      (frost_pointer) data);

    dict = frost_tree_new_full (compare_key, NULL,
                            NULL, (FROSTDestroyNotify) ice_val_unref);
    frost_tree_insert (dict, (char *) prop->name,
                   ice_val_new_string (prop->value));

//    ice_streamer_object_emit_f (ICEST_OBJECT (global_config),
//                        ICE_IPC_SIGNAL_CONFIGVALUE_CHANGED,
//                        ICE_VAL_TYPE_DICT,
//                        dict);

    frost_tree_destroy (dict);

    /* save the database to disk, so we don't lose any data
     * if the daemon crashes
     */
//    ice_streamer_config_save ();
}

/**
 * Return the value of a config property as a string
 * @param prop The config property
 * @return value as string
 */
const char *
ice_streamer_config_property_get_string (const ice_streamer_config_property_t *prop)
{
    frost_return_val_if_fail (prop, NULL);
    return prop->value;
}

/**
 * Return the value of a config property as an int
 * @param prop The config property
 * @return value as int
 */
frost_int
ice_streamer_config_property_get_int (const ice_streamer_config_property_t *prop)
{
    frost_return_val_if_fail (prop, 0);
    if (prop->value)
        return atoi (prop->value);

    return 0;
}

/**
 * Return the value of a config property as a float
 * @param prop The config property
 * @return value as float
 */
float
ice_streamer_config_property_get_float (const ice_streamer_config_property_t *prop)
{
    frost_return_val_if_fail (prop, 0.0);
    if (prop->value)
        return atof (prop->value);

    return 0.0;
}

/**
 * Set a callback function for a config property.
 * This will be called each time the property's value changes.
 * @param prop The config property
 * @param cb The callback to set
 * @param userdata Data to pass on to the callback
 */
void
ice_streamer_config_property_callback_set (ice_streamer_config_property_t *prop,
                                   ice_streamer_object_handler_t cb,
                                   frost_pointer userdata)
{
    frost_return_if_fail (prop);

    if (!cb)
        return;

//    ice_streamer_object_connect (ICEST_OBJECT (prop),
//                         ICE_IPC_SIGNAL_CONFIGVALUE_CHANGED,
//                         (ice_streamer_object_handler_t) cb, userdata);
}

/**
 * Remove a callback from a config property
 * @param prop The config property
 * @param cb The callback to remove
 */
void
ice_streamer_config_property_callback_remove (ice_streamer_config_property_t *prop,
                                      ice_streamer_object_handler_t cb,
                                      frost_pointer userdata)
{
    frost_return_if_fail (prop);

    if (!cb)
        return;

//    ice_streamer_object_disconnect (ICEST_OBJECT (prop),
//                            ICE_IPC_SIGNAL_CONFIGVALUE_CHANGED, cb, userdata);
}

void
ice_streamer_config_property_destroy (ice_streamer_object_t *object)
{
    ice_streamer_config_property_t *prop = (ice_streamer_config_property_t *) object;

    /* don't free val->name here, it's taken care of in
     * ice_streamer_config_destroy()
     */
    frost_free (prop->value);
}

/**
 * @internal Create a new config value
 * @param name The name of the new config value
 */
ice_streamer_config_property_t *
ice_streamer_config_property_new (const char *name)
{
    ice_streamer_config_property_t *ret;

    ret = ice_streamer_object_new (ice_streamer_config_property_t, ice_streamer_config_property_destroy);
    ret->name = name;

    return ret;
}

/**
 * Register a new config property. This should be called from the init code
 * as ICE STREAMER won't allow set/get on properties that haven't been registered.
 *
 * @param path The path in the config tree.
 * @param default_value If the value was not found in the configfile, what
 * should we use?
 * @param cb A callback function that will be called if the value is changed by
 * the client. Can be set to NULL.
 * @param userdata Data to pass to the callback function.
 * @return A newly allocated #ice_streamer_config_property_t for the registered
 * property.
 */
ice_streamer_config_property_t *
ice_streamer_config_property_register (const char *path,
                               const char *default_value,
                               ice_streamer_object_handler_t cb,
                               frost_pointer userdata)
{

    ice_streamer_config_property_t *prop;

    FROST_MutexLock (global_config->mutex);

    prop = (ice_streamer_config_property_t *)frost_tree_lookup (global_config->properties, path);
    if (!prop) {
        prop = ice_streamer_config_property_new (frost_strdup (path));

        ice_streamer_config_property_set_data (prop, (char *) default_value);
        frost_tree_replace (global_config->properties,
                       (char *) prop->name, prop);
    }

    if (cb) {
        ice_streamer_config_property_callback_set (prop, cb, userdata);
    }

    FROST_MutexUnlock (global_config->mutex);

    return prop;
}


ice_streamer_handler_t *
add_effects (ice_streamer_handler_t *last, frost_int32 entry,
             FROST_List *goal_formats)
{
    frost_int effect_no;

    for (effect_no = 0; frost_true; effect_no++) {
        ice_streamer_config_property_t *cfg;
        char key[64];
        const char *name;

        frost_snprintf (key, sizeof (key), "effect.order.%i", effect_no);

        cfg = ice_streamer_config_lookup (key);
        if (!cfg) {
            break;
        }

        name = ice_streamer_config_property_get_string (cfg);

        if (!name[0]) {
            continue;
        }

        last = ice_streamer_handler_new_effect (last, entry, goal_formats, name);
    }

    return last;
}

ice_streamer_handler_t *
ice_streamer_handler_new_effect (ice_streamer_handler_t *last, frost_int32 entry,
                       FROST_List *goal_formats, const char *name)
{
    ice_streamer_plugin_t *plugin;
    ice_streamer_handler_plugin_t *xform_plugin;
    ice_streamer_handler_t *xform;
    frost_int priority;

    plugin = ice_streamer_plugin_find (ICE_PLUGIN_TYPE_XFORM, name);
    if (!plugin) {
        TRACE_ERROR(("Couldn't find any effect named '%s'", name));
        return last;
    }

    xform_plugin = (ice_streamer_handler_plugin_t *) plugin;
    if (!ice_streamer_handler_plugin_supports (xform_plugin, last->out_type, &priority)) {
        TRACE_INFO(("Effect '%s' doesn't support format, skipping",
                       ice_streamer_plugin_shortname_get (plugin)));
        ice_streamer_object_unref (plugin);
        return last;
    }

    xform = ice_streamer_handler_new (xform_plugin, last, entry, goal_formats);

    if (xform) {
        ice_streamer_object_unref (last);
        last = xform;
    } else {
        TRACE_INFO(("Effect '%s' failed to initialize, skipping",
                       ice_streamer_plugin_shortname_get (plugin)));
    }
    ice_streamer_handler_plugin_config_property_register (xform_plugin,
                                                "enabled", "0",
                                                NULL, NULL);
    ice_streamer_object_unref (plugin);
    return last;
}

static void
update_effect_properties (ice_streamer_object_t *object, ice_val_t *data,
                          frost_pointer userdata)
{
    frost_int effect_no = frost_pointer_TO_INT (userdata);
    const char *name;

    ice_streamer_config_property_t *cfg;
    ice_streamer_handler_plugin_t *xform_plugin;
    ice_streamer_plugin_t *plugin;
    char key[64];

    name = ice_streamer_config_property_get_string ((ice_streamer_config_property_t *) object);

    if (name[0]) {
        plugin = ice_streamer_plugin_find (ICE_PLUGIN_TYPE_XFORM, name);
        if (!plugin) {
            TRACE_ERROR(("Couldn't find any effect named '%s'", name));
        } else {
            xform_plugin = (ice_streamer_handler_plugin_t *) plugin;
            ice_streamer_handler_plugin_config_property_register (xform_plugin, "enabled",
                                                        "1", NULL, NULL);
            ice_streamer_object_unref (plugin);
        }

        /* setup new effect.order.n */
        frost_snprintf (key, sizeof (key), "effect.order.%i", effect_no + 1);

        cfg = ice_streamer_config_lookup (key);
        if (!cfg) {
            ice_streamer_config_property_register (key, "", update_effect_properties,
                                           frost_int_TO_POINTER (effect_no + 1));
        }
    }
}

void
effect_callbacks_init (void)
{
    frost_int effect_no;

    ice_streamer_config_property_t *cfg;
    ice_streamer_handler_plugin_t *xform_plugin;
    ice_streamer_plugin_t *plugin;
    char key[64];
    const char *name;

    for (effect_no = 0; ; effect_no++) {
        frost_snprintf (key, sizeof (key), "effect.order.%i", effect_no);

        cfg = ice_streamer_config_lookup (key);
        if (!cfg) {
            break;
        }
        ice_streamer_config_property_callback_set (cfg, update_effect_properties,
                                           frost_int_TO_POINTER (effect_no));

        name = ice_streamer_config_property_get_string (cfg);
        if (!name[0]) {
            continue;
        }

        plugin = ice_streamer_plugin_find (ICE_PLUGIN_TYPE_XFORM, name);
        if (!plugin) {
            TRACE_ERROR(("Couldn't find any effect named '%s'", name));
            continue;
        }

        xform_plugin = (ice_streamer_handler_plugin_t *) plugin;
        ice_streamer_handler_plugin_config_property_register (xform_plugin, "enabled",
                                                    "1", NULL, NULL);

        ice_streamer_object_unref (plugin);
    }

    /* the name stored in the last present property was not "" or there was no
       last present property */
    if ((!effect_no) || name[0]) {
            ice_streamer_config_property_register (key, "", update_effect_properties,
                                           frost_int_TO_POINTER (effect_no));
    }
}

