#include "ice_streamer.h"

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

#include "utilities_debug/cabot_assert.h"
#include "utilities_debug/trace.h"
#include "frost/frost_condvar.h"
#include "frost/frost_mutex.h"
#include "frost_mem.h"

TRACE_IMPLEMENT("ice/ice_streamer_outputplugin");


static frost_bool ice_streamer_output_plugin_writer_status (ice_streamer_output_plugin_t *plugin,
                                                  ice_streamer_output_t *output,
                                                  ice_streamer_playback_status_t s);
static void ice_streamer_output_plugin_writer_status_wait (ice_streamer_output_plugin_t *plugin,
                                                   ice_streamer_output_t *output,
                                                   ice_streamer_playback_status_t st);
void ice_streamer_output_plugin_writer (void* data);


static void
ice_streamer_output_plugin_destroy (ice_streamer_object_t *obj)
{
    ice_streamer_output_plugin_t *plugin = (ice_streamer_output_plugin_t *)obj;

    FROST_MutexDestroy (plugin->api_mutex);
    FROST_MutexDestroy (plugin->write_mutex);
    FROST_CondVarDestroy (plugin->write_cond);

    FROST_CondVarDestroy(plugin->status_cond);
    FROST_MutexDestroy (plugin->status_mutex);

    ice_streamer_plugin_destroy ((ice_streamer_plugin_t *)obj);
}


ice_streamer_plugin_t *
ice_streamer_output_plugin_new (void)
{
    ice_streamer_output_plugin_t *res;

    res = ice_streamer_object_new (ice_streamer_output_plugin_t, ice_streamer_output_plugin_destroy);
    res->api_mutex = FROST_MutexCreate ();
    res->write_mutex = FROST_MutexCreate ();
    res->write_cond = FROST_CondVarCreate();

    res->status_cond = FROST_CondVarCreate ();
    res->status_mutex = FROST_MutexCreate ();

    return (ice_streamer_plugin_t *)res;
}


void
ice_streamer_output_plugin_methods_set (ice_streamer_output_plugin_t *plugin,
                                ice_streamer_output_methods_t *methods)
{
    frost_return_if_fail (plugin);
    frost_return_if_fail (plugin->plugin.type == ICE_PLUGIN_TYPE_OUTPUT);

    TRACE_INFO(("Registering output '%s'",
              ice_streamer_plugin_shortname_get ((ice_streamer_plugin_t *)plugin)));

    memcpy (&plugin->methods, methods, sizeof (ice_streamer_output_methods_t));
}


frost_bool
ice_streamer_output_plugin_verify (ice_streamer_plugin_t *_plugin)
{
    ice_streamer_output_plugin_t *plugin = (ice_streamer_output_plugin_t *)_plugin;
    frost_bool h, w, s, o, c;

    frost_return_val_if_fail (plugin, frost_false);
    frost_return_val_if_fail (_plugin->type == ICE_PLUGIN_TYPE_OUTPUT, frost_false);

    if (!(plugin->methods.create &&
          plugin->methods.destroy &&
          plugin->methods.flush)) {
        TRACE_INFO(("Missing: new, destroy or flush!\n"));
        return frost_false;
    }

    h = (!!plugin->methods.header) ? frost_true : frost_false;
    w = (!!plugin->methods.write) ? frost_true : frost_false;
    s = (!!plugin->methods.status) ? frost_true : frost_false;

    if (w == s) {
        TRACE_INFO(("Plugin needs to provide either write or status.\n"));
        return frost_false;
    }

    o = (!!plugin->methods.open) ? frost_true : frost_false;
    c = (!!plugin->methods.close) ? frost_true : frost_false;

    if (w) {
        /* 'write' type. */
        if (!(o && c)) {
            TRACE_INFO(("Write type misses open or close.\n"));
            return frost_false;
        }
    } else {
        /* 'self driving' type */
        if (o || c) {
            TRACE_INFO(("Status type has open or close.\n"));
            return frost_false;
        }
    }

    return frost_true;
}


ice_streamer_config_property_t *
ice_streamer_output_plugin_config_property_register (ice_streamer_output_plugin_t *plugin,
                                             const char *name,
                                             const char *default_value,
                                             ice_streamer_object_handler_t cb,
                                             frost_pointer userdata)
{
    ice_streamer_plugin_t *p = (ice_streamer_plugin_t *) plugin;

    return ice_streamer_plugin_config_property_register (p, name, default_value,
                                                 cb, userdata);
}


frost_bool
ice_streamer_output_plugin_method_new (ice_streamer_output_plugin_t *plugin,
                               ice_streamer_output_t *output)
{
    frost_bool ret = frost_true;

    frost_return_val_if_fail (output, frost_false);
    frost_return_val_if_fail (plugin, frost_false);

    if (plugin->methods.create) {
        ret = plugin->methods.create (output);
    }

    if (ret && !plugin->methods.status) {
        plugin->write_running = frost_true;
        plugin->write_thread = FROST_ThreadCreate(ice_streamer_output_plugin_writer,
                                                (void*)plugin,2048 + 2*CURRENT_MAX_PACKET_SIZE, FROST_high_priority, "x2 Output Writer");
        plugin->wanted_status = ICE_PLAYBACK_STATUS_STOP;
        plugin->status = ICE_PLAYBACK_STATUS_STOP;
    }

    return ret;
}


void
ice_streamer_output_plugin_method_destroy (ice_streamer_output_plugin_t *plugin,
                                   ice_streamer_output_t *output)
{
    frost_return_if_fail (output);
    frost_return_if_fail (plugin);

    if (plugin->write_thread) {
        ice_streamer_output_plugin_writer_status_wait (plugin, output,
                                               ICE_PLAYBACK_STATUS_STOP);

        plugin->write_running = frost_false;

        FROST_CondVarSignal (plugin->write_cond);
        FROST_ThreadDelete (plugin->write_thread);
        plugin->write_thread = NULL;
    }

    if (plugin->methods.destroy) {
        FROST_MutexLock (plugin->api_mutex);
        plugin->methods.destroy (output);
        FROST_MutexUnlock (plugin->api_mutex);
    }
    
    ((ice_streamer_plugin_t *)plugin)->in_use = frost_false;
}


void
ice_streamer_output_plugin_method_flush (ice_streamer_output_plugin_t *plugin,
                                 ice_streamer_output_t *output)
{
    frost_return_if_fail (output);
    frost_return_if_fail (plugin);

    if (plugin->methods.flush) {
        FROST_MutexLock (plugin->api_mutex);
        plugin->methods.flush (output);
        FROST_MutexUnlock (plugin->api_mutex);
    }
}


frost_bool
ice_streamer_output_plugin_format_set_always (ice_streamer_output_plugin_t *plugin)
{
    frost_return_val_if_fail (plugin, frost_false);

    if (plugin->methods.format_set_always) {
        return frost_true;
    }
    return frost_false;
}


frost_bool
ice_streamer_output_plugin_method_format_set (ice_streamer_output_plugin_t *plugin,
                                      ice_streamer_output_t *output,
                                      ice_streamer_stream_type_t *st)
{
    frost_bool res = frost_true;

    frost_return_val_if_fail (output, frost_false);
    frost_return_val_if_fail (plugin, frost_false);

    if (plugin->methods.format_set) {
        FROST_MutexLock (plugin->api_mutex);
        res = plugin->methods.format_set (output, st);
        FROST_MutexUnlock (plugin->api_mutex);
    } else if (plugin->methods.format_set_always) {
        FROST_MutexLock (plugin->api_mutex);
        res = plugin->methods.format_set_always (output, st);
        FROST_MutexUnlock (plugin->api_mutex);
    }

    return res;
}


frost_bool
ice_streamer_output_plugin_method_status (ice_streamer_output_plugin_t *plugin,
                                  ice_streamer_output_t *output, frost_int st)
{
    frost_bool res = frost_true;

    frost_return_val_if_fail (output, frost_false);
    frost_return_val_if_fail (plugin, frost_false);

    if (plugin->methods.status) {
        res = plugin->methods.status (output, (ice_streamer_playback_status_t)st);
    } else if (plugin->write_thread) {
        TRACE_INFO(("Running status changed... %d", st));
        res = ice_streamer_output_plugin_writer_status (plugin, output, (ice_streamer_playback_status_t)st);
    }
    return res;
}


frost_uint
ice_streamer_output_plugin_method_latency_get (ice_streamer_output_plugin_t *plugin,
                                       ice_streamer_output_t *output)
{
    frost_int ret = 0;

    frost_return_val_if_fail (output, frost_false);
    frost_return_val_if_fail (plugin, frost_false);

    if (plugin->methods.latency_get) {
        ret = plugin->methods.latency_get (output);
    }

    return ret;
}

/* Used when we have to drive the output... */

static frost_bool
ice_streamer_output_plugin_writer_status (ice_streamer_output_plugin_t *plugin,
                                  ice_streamer_output_t *output,
                                  ice_streamer_playback_status_t status)
{
    FROST_MutexLock (plugin->write_mutex);
    plugin->wanted_status = status;
    plugin->write_output = output;
    FROST_CondVarSignal (plugin->write_cond);
    FROST_MutexUnlock (plugin->write_mutex);

    return frost_true;
}

static void
ice_streamer_output_plugin_writer_status_wait (ice_streamer_output_plugin_t *plugin,
                                       ice_streamer_output_t *output,
                                       ice_streamer_playback_status_t status)
{
    FROST_MutexLock (plugin->status_mutex);

    if (plugin->wanted_status != status) {
        ice_streamer_output_plugin_writer_status (plugin, output, status);
    }

    while (plugin->status != status) {
        FROST_CondVarWait (plugin->status_cond, plugin->status_mutex);
    }

    FROST_MutexUnlock (plugin->status_mutex);
}


void
ice_streamer_output_plugin_writer (void* data)
{
    ice_streamer_output_plugin_t *plugin = (ice_streamer_output_plugin_t *) data;
    ice_streamer_output_t *output = NULL;
    char buffer[CURRENT_MAX_PACKET_SIZE];
    frost_int ret;

    FROST_MutexLock (plugin->write_mutex);

    while (plugin->write_running) {

        if(plugin->status != plugin->wanted_status)
        {
            plugin->methods.set_status(output, plugin->wanted_status );
        }

        if (plugin->wanted_status == ICE_PLAYBACK_STATUS_STOP ||
                plugin->wanted_status == ICE_PLAYBACK_STATUS_EOS) {
            if (output) {
                FROST_MutexLock (plugin->api_mutex);
                plugin->methods.close (output);
                FROST_MutexUnlock (plugin->api_mutex);

                output = NULL;
            }

            FROST_MutexLock (plugin->status_mutex);
            plugin->status = plugin->wanted_status;
            FROST_CondVarSignal (plugin->status_cond);
            FROST_MutexUnlock (plugin->status_mutex);

            FROST_CondVarWaitTimeout(plugin->write_cond, plugin->write_mutex,1000);
        } else if (plugin->wanted_status == ICE_PLAYBACK_STATUS_PAUSE) {
            /*ice_streamer_config_property_t *p;

            p = ice_streamer_config_lookup ("output.flush_on_pause");
            if (ice_streamer_config_property_get_int (p)) {
                FROST_MutexLock (plugin->api_mutex);
                plugin->methods.flush (output);
                FROST_MutexUnlock (plugin->api_mutex);
            }*/

            FROST_MutexLock (plugin->status_mutex);
            plugin->status = plugin->wanted_status;
            FROST_CondVarSignal (plugin->status_cond);
            FROST_MutexUnlock (plugin->status_mutex);

            FROST_CondVarWaitTimeout(plugin->write_cond, plugin->write_mutex,1000);
        } else if (plugin->wanted_status == ICE_PLAYBACK_STATUS_PLAY ||
                    plugin->wanted_status == ICE_PLAYBACK_STATUS_IDLE) {
            if (!output) {
                frost_bool ret;

                output = plugin->write_output;

                FROST_MutexLock (plugin->api_mutex);
                ret = plugin->methods.open (output);
                FROST_MutexUnlock (plugin->api_mutex);

                if (!ret) {
                    TRACE_ERROR(("Could not open output\n"));
                    plugin->wanted_status = ICE_PLAYBACK_STATUS_STOP;
                    output = NULL;
                    continue;
                }
            }

            FROST_MutexLock (plugin->status_mutex);
            plugin->status = plugin->wanted_status;
            FROST_CondVarSignal (plugin->status_cond);
            FROST_MutexUnlock (plugin->status_mutex);

            FROST_MutexUnlock (plugin->write_mutex);

            if(plugin->wanted_status == ICE_PLAYBACK_STATUS_PLAY)
            {
                ret = ice_streamer_output_read (output, buffer, CURRENT_PACKET_SIZE);
                if (ret > 0) {
                    ice_error_code_t err;

                    ice_streamer_error_reset (&err);

                    FROST_MutexLock (plugin->api_mutex);
                    plugin->methods.write (output, buffer, ret, &err);
                    FROST_MutexUnlock (plugin->api_mutex);

                    if (ice_streamer_error_iserror (&err)) {
                        TRACE_INFO(("Write method set error bit\n"));

                        FROST_MutexLock (plugin->write_mutex);
                        plugin->wanted_status = ICE_PLAYBACK_STATUS_STOP;
                        FROST_MutexUnlock (plugin->write_mutex);

                        ice_streamer_output_set_error (output, &err);
                    }
                }
            }
            FROST_MutexLock (plugin->write_mutex);
        }
    }

    CABOT_ASSERT (!output);

    FROST_MutexUnlock (plugin->write_mutex);

    TRACE_INFO(("Output driving thread exiting!\n"));

}

