/*
 * Bognor-Regis - a media player/queue daemon.
 * Copyright © 2009, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <glib.h>
#include <glib/gi18n.h>
#include <gst/gst.h>
#include <gtk/gtk.h>

#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-bindings.h>

#include "bognor-local-queue.h"
#include "bognor-player-bindings.h"

enum {
    PROP_0,
};

struct _BognorLocalQueuePrivate {
    GstElement *playbin;
    GstState audio_state;
    gboolean audio_set;

    guint32 tracker_id;
    gint64 duration;
    double position;

    GtkRecentManager *recent_manager;
    gboolean error_occured;
};

#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), BOGNOR_TYPE_LOCAL_QUEUE, BognorLocalQueuePrivate))
G_DEFINE_TYPE (BognorLocalQueue, bognor_local_queue, BOGNOR_TYPE_QUEUE);

static void make_playbin (BognorLocalQueue *queue);

static void
bognor_local_queue_finalize (GObject *object)
{
    G_OBJECT_CLASS (bognor_local_queue_parent_class)->finalize (object);
}

static void
bognor_local_queue_dispose (GObject *object)
{
    G_OBJECT_CLASS (bognor_local_queue_parent_class)->dispose (object);
}

static void
bognor_local_queue_set_property (GObject      *object,
                                 guint         prop_id,
                                 const GValue *value,
                                 GParamSpec   *pspec)
{
    switch (prop_id) {

    default:
        break;
    }
}

static void
bognor_local_queue_get_property (GObject    *object,
                                 guint       prop_id,
                                 GValue     *value,
                                 GParamSpec *pspec)
{
    switch (prop_id) {

    default:
        break;
    }
}

static gboolean
add_item_to_recent (BognorQueue     *queue,
                    BognorQueueItem *item)
{
    BognorLocalQueue *local = (BognorLocalQueue *) queue;
    BognorLocalQueuePrivate *priv = local->priv;
    GtkRecentData data;
    gboolean ret;

    /* FIXME: This should just run through bognor... */
    data.display_name = NULL;
    data.description = NULL;
    data.mime_type = item->mimetype;
    data.app_name = "Hornsey";
    data.app_exec = "hornsey %u";
    data.groups = NULL;
    data.is_private = FALSE;

    ret = gtk_recent_manager_add_full (priv->recent_manager, item->uri, &data);
    if (ret == FALSE) {
        g_warning ("Error registering recent use of %s\n", item->uri);
    }

    return ret;
}

static gboolean
set_uri (BognorQueue     *queue,
         BognorQueueItem *item)
{
    BognorLocalQueue *local = (BognorLocalQueue *) queue;
    BognorLocalQueuePrivate *priv = local->priv;
    gboolean ret;
    GstState state;

    if (item == NULL) {
        priv->audio_set = FALSE;
        g_object_set (priv->playbin,
                      "uri", "",
                      NULL);

        if (priv->audio_state == GST_STATE_PLAYING) {
            gst_element_set_state (priv->playbin, GST_STATE_PAUSED);
            priv->audio_state = GST_STATE_PAUSED;
        }
        return;
    }

    if(priv->error_occured) {
        state = GST_STATE_PLAYING;
        priv->error_occured = FALSE;
    } else {
        gst_element_get_state (priv->playbin, &state, NULL,
                               GST_CLOCK_TIME_NONE);
    }

    gst_element_set_state (priv->playbin, GST_STATE_READY);
    /* Audio is played locally */
    g_object_set (priv->playbin,
                  "uri", item->uri,
                  NULL);
    priv->audio_set = TRUE;
    gst_element_set_state (priv->playbin, state);

    return TRUE;
}

static gboolean
set_playing (BognorQueue *queue,
             gboolean     playing)
{
    BognorLocalQueue *local = (BognorLocalQueue *) queue;
    BognorLocalQueuePrivate *priv = local->priv;

    if (priv->audio_set == FALSE) {
        return TRUE;
    }

    if (playing) {
        if (priv->audio_state != GST_STATE_PLAYING) {
            gst_element_set_state (priv->playbin, GST_STATE_PLAYING);
            priv->audio_state = GST_STATE_PLAYING;
        }
    } else {
        gst_element_set_state (priv->playbin, GST_STATE_PAUSED);
        priv->audio_state = GST_STATE_PAUSED;
    }

    return TRUE;
}

static gboolean
set_position (BognorQueue *queue,
              double       position)
{
    BognorLocalQueue *local = (BognorLocalQueue *) queue;
    BognorLocalQueuePrivate *priv = local->priv;
    GstFormat format = GST_FORMAT_TIME;
    gint64 dur;

    if (gst_element_query_duration (priv->playbin, &format, &dur)) {
        gst_element_seek_simple (priv->playbin, format,
                                 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
                                 (gint64) (dur * position));
    } else {
        g_warning ("Query duration failed");
    }

    return TRUE;
}

static gboolean
get_position (BognorQueue *queue,
              double      *position)
{
    BognorLocalQueue *local = (BognorLocalQueue *) queue;
    BognorLocalQueuePrivate *priv = local->priv;

    *position = priv->position;

    return TRUE;
}

static void
bognor_local_queue_class_init (BognorLocalQueueClass *klass)
{
    GObjectClass *o_class = (GObjectClass *) klass;
    BognorQueueClass *q_class = (BognorQueueClass *) klass;

    o_class->dispose = bognor_local_queue_dispose;
    o_class->finalize = bognor_local_queue_finalize;
    o_class->set_property = bognor_local_queue_set_property;
    o_class->get_property = bognor_local_queue_get_property;

    q_class->set_uri = set_uri;
    q_class->set_playing = set_playing;
    q_class->set_position = set_position;
    q_class->get_position = get_position;
    q_class->add_item_to_recent = add_item_to_recent;

    g_type_class_add_private (klass, sizeof (BognorLocalQueuePrivate));
}

static gboolean
gst_get_position (gpointer userdata)
{
    BognorQueue *queue = (BognorQueue *) userdata;
    BognorLocalQueue *local = (BognorLocalQueue *) userdata;
    BognorLocalQueuePrivate *priv = local->priv;
    double position = 0.0;
    GstFormat format = GST_FORMAT_TIME;
    gint64 cur, dur;

    if (gst_element_query_duration (priv->playbin, &format, &dur)) {
        if (gst_element_query_position (priv->playbin, &format, &cur)) {
            position = ((double) cur) / (double) dur;
        } else {
            g_warning ("Query position failed");
        }
    } else {
        g_print ("Query duration failed");
    }

    priv->position = position;
    bognor_queue_emit_position_changed (queue, position);
    return TRUE;
}

static gboolean
bus_callback (GstBus     *bus,
              GstMessage *message,
              gpointer    data)
{
    BognorQueue *queue = (BognorQueue *) data;
    BognorQueueItem *item;
    BognorLocalQueue *local = (BognorLocalQueue *) data;
    BognorLocalQueuePrivate *priv = local->priv;
    GstState oldstate, newstate;
    GstFormat format;

    switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_ERROR: {
        GError *error;
        char *debug;

        gst_message_parse_error (message, &error, &debug);

        g_warning ("Error while playing: %s: %s", error->message, debug);

        g_error_free (error);
        g_free (debug);

        item = bognor_queue_get_current_item (queue);
        if (item) {
            bognor_queue_notify_unknown_format (queue, item->uri);
        }

        /* When there's been an error, tear down the playbin and
           rebuild */
        gst_element_set_state (priv->playbin, GST_STATE_NULL);
        g_object_unref (priv->playbin);

        make_playbin (local);
        priv->audio_state = GST_STATE_READY;
        bognor_queue_play_next (queue);

        /* priv->error_occured = TRUE; */
        break;
    }

    case GST_MESSAGE_EOS:
        /* Once the local GStreamer queue is done we want the next audio */
        gst_element_set_state (priv->playbin, GST_STATE_READY);
        priv->audio_state = GST_STATE_READY;

        bognor_queue_play_next (queue);

        break;

    case GST_MESSAGE_STATE_CHANGED:
        gst_message_parse_state_changed (message, &oldstate, &newstate, NULL);
        if (newstate == GST_STATE_PLAYING) {
            if (priv->tracker_id == 0) {
                priv->tracker_id = g_timeout_add_seconds (1, gst_get_position,
                                                          queue);
            }
        } else {
            if (priv->tracker_id > 0) {
                g_source_remove (priv->tracker_id);
                priv->tracker_id = 0;
            }
        }
        break;

    default:
        break;
    }

    return TRUE;
}

static void
make_playbin (BognorLocalQueue *queue)
{
    BognorLocalQueuePrivate *priv = queue->priv;
    GstElement *fakesink;
    GstBus *bus;

    priv->playbin = gst_element_factory_make ("playbin", "playbin");
    priv->error_occured = FALSE;
    priv->audio_set = FALSE;

    fakesink = gst_element_factory_make ("fakesink", "video_sink");
    g_object_set (priv->playbin,
                  "video-sink", fakesink,
                  NULL);

    bus = gst_pipeline_get_bus (GST_PIPELINE (priv->playbin));
    gst_bus_add_watch (bus, bus_callback, queue);
    gst_object_unref (bus);
}

static void
bognor_local_queue_init (BognorLocalQueue *self)
{
    BognorLocalQueuePrivate *priv;

    bognor_queue_set_name ((BognorQueue *) self, _("Playqueue"));

    priv = self->priv = GET_PRIVATE (self);
    priv->recent_manager = gtk_recent_manager_get_default ();

    make_playbin (self);
}

BognorLocalQueue *
bognor_local_queue_new (void)
{
    BognorLocalQueue *q;

    q = g_object_new (BOGNOR_TYPE_LOCAL_QUEUE, NULL);

    return q;
}
