/*
 * 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 "config.h"
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-bindings.h>

#ifdef USE_QUEUE_CONFIG
#include <gconf/gconf-client.h>
#endif

#include "br-marshal.h"
#include "br-queue.h"
#include "bognor-queue-bindings.h"

/* default settings */
#define BR_QUEUE_DBUS_SERVICE "org.moblin.BognorRegis"
#define BR_LOCAL_QUEUE_PATH "/org/moblin/BognorRegis/Queues/local_queue"

enum {
    PROP_0,
    PROP_PATH,
    PROP_SERVICE,
};

enum {
    ADDED,
    REMOVED,
    NOW_PLAYING,
    POSITION_CHANGED,
    PLAYING_CHANGED,
    LAST_SIGNAL
};

struct _BrQueuePrivate {
    DBusGProxy *proxy;
    char *object_path;
    char *service_name;
};

struct _AsyncClosure {
    BrQueue *queue;
    GCallback cb;
    gpointer userdata;
};

#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), BR_TYPE_QUEUE, BrQueuePrivate))
G_DEFINE_TYPE (BrQueue, br_queue, G_TYPE_OBJECT);
static guint32 signals[LAST_SIGNAL] = {0, };

static void uri_added_cb (DBusGProxy *proxy,
                          const char *uri,
                          int         index,
                          BrQueue    *queue);
static void uri_removed_cb (DBusGProxy *proxy,
                            const char *uri,
                            int         index,
                            BrQueue    *queue);
static void now_playing_changed (DBusGProxy *proxy,
                                 const char *uri,
                                 BrQueue    *queue);
static void position_changed (DBusGProxy *proxy,
                              double      position,
                              BrQueue    *queue);
static void playing_changed (DBusGProxy *proxy,
                             gboolean    playing,
                             BrQueue    *queue);

static void
br_queue_finalize (GObject *object)
{
    BrQueue *self = (BrQueue *) object;
    BrQueuePrivate *priv = self->priv;

    if (priv->object_path) {
        g_free (priv->object_path);
        priv->object_path = NULL;
    }

    if (priv->service_name) {
        g_free (priv->service_name);
        priv->service_name = NULL;
    }

    G_OBJECT_CLASS (br_queue_parent_class)->finalize (object);
}

static void
br_queue_dispose (GObject *object)
{
    BrQueue *self = (BrQueue *) object;
    BrQueuePrivate *priv = self->priv;

    if (priv->proxy) {
        dbus_g_proxy_disconnect_signal (priv->proxy, "UriAdded",
                                        G_CALLBACK (uri_added_cb), self);
        dbus_g_proxy_disconnect_signal (priv->proxy, "UriRemoved",
                                        G_CALLBACK (uri_removed_cb), self);
        dbus_g_proxy_disconnect_signal (priv->proxy, "NowPlayingChanged",
                                        G_CALLBACK (now_playing_changed), self);
        dbus_g_proxy_disconnect_signal (priv->proxy, "PositionChanged",
                                        G_CALLBACK (position_changed), self);
        dbus_g_proxy_disconnect_signal (priv->proxy, "PlayingChanged",
                                        G_CALLBACK (playing_changed), self);

        g_object_unref (priv->proxy);
        priv->proxy = NULL;
    }

    G_OBJECT_CLASS (br_queue_parent_class)->dispose (object);
}

static void
br_queue_set_property (GObject      *object,
                       guint         prop_id,
                       const GValue *value,
                       GParamSpec   *pspec)
{
    BrQueue *self = (BrQueue *) object;
    BrQueuePrivate *priv = self->priv;

    switch (prop_id) {

    case PROP_PATH:
        priv->object_path = g_value_dup_string (value);
        break;

    case PROP_SERVICE:
        priv->service_name = g_value_dup_string (value);
        break;

    default:
        break;
    }
}

static void
br_queue_get_property (GObject    *object,
                       guint       prop_id,
                       GValue     *value,
                       GParamSpec *pspec)
{
    BrQueue *self = (BrQueue *) object;

    switch (prop_id) {

    default:
        break;
    }
}

static void
uri_added_cb (DBusGProxy *proxy,
              const char *uri,
              int         index,
              BrQueue    *queue)
{
    g_signal_emit (queue, signals[ADDED], 0, uri, index);
}

static void
uri_removed_cb (DBusGProxy *proxy,
                const char *uri,
                int         index,
                BrQueue    *queue)
{
    g_signal_emit (queue, signals[REMOVED], 0, uri, index);
}

static void
now_playing_changed (DBusGProxy *proxy,
                     const char *uri,
                     BrQueue    *queue)
{
    g_signal_emit (queue, signals[NOW_PLAYING], 0, uri);
}

static void
position_changed (DBusGProxy *proxy,
                  double      position,
                  BrQueue    *queue)
{
    g_signal_emit (queue, signals[POSITION_CHANGED], 0, position);
}

static void
playing_changed (DBusGProxy *proxy,
                 gboolean    playing,
                 BrQueue    *queue)
{
    g_signal_emit (queue, signals[PLAYING_CHANGED], 0, playing);
}

static GObject *
br_queue_constructor (GType                  type,
                      guint                  n_construct_properties,
                      GObjectConstructParam *construct_properties)
{
    BrQueue *queue;
    BrQueuePrivate *priv;
    DBusGConnection *connection;
    DBusGProxy *proxy;
    GError *error = NULL;
    guint start_ret;

    queue = BR_QUEUE (G_OBJECT_CLASS (br_queue_parent_class)->constructor
                      (type, n_construct_properties, construct_properties));
    priv = queue->priv;

    connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
    if (connection == NULL) {
        g_warning ("Failed to open connection to bus: %s",
                   error->message);
        g_error_free (error);

        priv->proxy = NULL;
        return G_OBJECT (queue);
    }

    proxy = dbus_g_proxy_new_for_name (connection,
                                       DBUS_SERVICE_DBUS,
                                       DBUS_PATH_DBUS,
                                       DBUS_INTERFACE_DBUS);
    if (!org_freedesktop_DBus_start_service_by_name (proxy,
                                                     priv->service_name, 0,
                                                     &start_ret, &error)) {
        g_warning ("bognor-regis could not start queue service '%s': %s",
		   priv->service_name, error->message);
        g_error_free (error);

        priv->proxy = NULL;
        return G_OBJECT (queue);
    }

    priv->proxy = dbus_g_proxy_new_for_name (connection, priv->service_name,
                                             priv->object_path,
                                             BR_QUEUE_DBUS_INTERFACE);

    dbus_g_proxy_add_signal (priv->proxy, "UriAdded",
                             G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
    dbus_g_proxy_add_signal (priv->proxy, "UriRemoved",
                             G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
    dbus_g_proxy_add_signal (priv->proxy, "NowPlayingChanged",
                             G_TYPE_STRING, G_TYPE_INVALID);
    dbus_g_proxy_add_signal (priv->proxy, "PositionChanged",
                             G_TYPE_DOUBLE, G_TYPE_INVALID);
    dbus_g_proxy_add_signal (priv->proxy, "PlayingChanged",
                             G_TYPE_BOOLEAN, G_TYPE_INVALID);
    dbus_g_proxy_connect_signal (priv->proxy, "UriAdded",
                                 G_CALLBACK (uri_added_cb), queue, NULL);
    dbus_g_proxy_connect_signal (priv->proxy, "UriRemoved",
                                 G_CALLBACK (uri_removed_cb), queue, NULL);
    dbus_g_proxy_connect_signal (priv->proxy, "NowPlayingChanged",
                                 G_CALLBACK (now_playing_changed), queue, NULL);
    dbus_g_proxy_connect_signal (priv->proxy, "PositionChanged",
                                 G_CALLBACK (position_changed), queue, NULL);
    dbus_g_proxy_connect_signal (priv->proxy, "PlayingChanged",
                                 G_CALLBACK (playing_changed), queue, NULL);

    return G_OBJECT (queue);
}

static void
br_queue_class_init (BrQueueClass *klass)
{
    GObjectClass *o_class = (GObjectClass *)klass;

    o_class->dispose = br_queue_dispose;
    o_class->finalize = br_queue_finalize;
    o_class->set_property = br_queue_set_property;
    o_class->get_property = br_queue_get_property;
    o_class->constructor = br_queue_constructor;

    g_type_class_add_private (klass, sizeof (BrQueuePrivate));

    dbus_g_object_register_marshaller (br_marshal_VOID__STRING_INT,
                                       G_TYPE_NONE, G_TYPE_STRING,
                                       G_TYPE_INT, G_TYPE_INVALID);
#if 0
    dbus_g_object_register_marshaller (br_marshal_VOID__INT_DOUBLE,
                                       G_TYPE_NONE, G_TYPE_INT,
                                       G_TYPE_DOUBLE, G_TYPE_INVALID);
#endif

    g_object_class_install_property (o_class, PROP_PATH,
                                     g_param_spec_string ("object-path",
                                                          "", "", "",
                                                          G_PARAM_WRITABLE |
                                                          G_PARAM_CONSTRUCT_ONLY |
                                                          G_PARAM_STATIC_STRINGS));
    g_object_class_install_property (o_class, PROP_SERVICE,
                                     g_param_spec_string ("service-name",
                                                          "", "", "",
                                                          G_PARAM_WRITABLE |
                                                          G_PARAM_CONSTRUCT_ONLY |
                                                          G_PARAM_STATIC_STRINGS));

    signals[ADDED] = g_signal_new ("uri-added",
                                   G_TYPE_FROM_CLASS (klass),
                                   G_SIGNAL_RUN_FIRST |
                                   G_SIGNAL_NO_RECURSE, 0, NULL, NULL,
                                   br_marshal_VOID__STRING_INT,
                                   G_TYPE_NONE, 2, G_TYPE_STRING,
                                   G_TYPE_INT);
    signals[REMOVED] = g_signal_new ("uri-removed",
                                     G_TYPE_FROM_CLASS (klass),
                                     G_SIGNAL_RUN_FIRST |
                                     G_SIGNAL_NO_RECURSE, 0, NULL, NULL,
                                     br_marshal_VOID__STRING_INT,
                                     G_TYPE_NONE, 2, G_TYPE_STRING,
                                     G_TYPE_INT);
    signals[NOW_PLAYING] = g_signal_new ("now-playing-changed",
                                         G_TYPE_FROM_CLASS (klass),
                                         G_SIGNAL_RUN_FIRST |
                                         G_SIGNAL_NO_RECURSE, 0, NULL, NULL,
                                         g_cclosure_marshal_VOID__STRING,
                                         G_TYPE_NONE, 1, G_TYPE_STRING);
    signals[POSITION_CHANGED] = g_signal_new ("position-changed",
                                              G_TYPE_FROM_CLASS (klass),
                                              G_SIGNAL_RUN_FIRST |
                                              G_SIGNAL_NO_RECURSE,
                                              0, NULL, NULL,
                                              g_cclosure_marshal_VOID__DOUBLE,
                                              G_TYPE_NONE, 1, G_TYPE_DOUBLE);
    signals[PLAYING_CHANGED] = g_signal_new ("playing-changed",
                                             G_TYPE_FROM_CLASS (klass),
                                             G_SIGNAL_RUN_FIRST |
                                             G_SIGNAL_NO_RECURSE,
                                             0, NULL, NULL,
                                             g_cclosure_marshal_VOID__BOOLEAN,
                                             G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
}

static void
br_queue_init (BrQueue *self)
{
    self->priv = GET_PRIVATE (self);
}

BrQueue *
br_queue_new (const char *service_name,
              const char *object_path)
{
    BrQueue *queue;

    queue = g_object_new (BR_TYPE_QUEUE,
                          "object-path", object_path,
                          "service-name", service_name,
                          NULL);
    return queue;
}

/* allow different implementations of the queue service */

static char *
get_queue_dbus_service (void)
{
#ifdef USE_QUEUE_CONFIG
    char *queue;

    queue = gconf_client_get_string
        (gconf_client_get_default (),
	 "/desktop/moblin/media_queue/service", NULL);
    if (queue)
        return queue;
#endif

    return g_strdup (BR_QUEUE_DBUS_SERVICE);
}

static char *
get_local_dbus_path (void)
{
#ifdef USE_QUEUE_CONFIG
    char *path;

    path = gconf_client_get_string
        (gconf_client_get_default (),
	 "/desktop/moblin/media_queue/local_queue", NULL);
    if (path)
        return path;
#endif

    return g_strdup (BR_LOCAL_QUEUE_PATH);
}

BrQueue *
br_queue_new_local (void)
{
    BrQueue *queue;

#ifdef USE_QUEUE_CONFIG
    char *path, *service;

    path = get_local_dbus_path ();
    service = get_queue_dbus_service ();

    queue = br_queue_new (service, path);

    g_free (path);
    g_free (service);
#else
    queue = br_queue_new (BR_QUEUE_DBUS_SERVICE, BR_LOCAL_QUEUE_PATH);
#endif

    return queue;
}

static void
async_reply (DBusGProxy *proxy,
             GError     *error,
             gpointer    userdata)
{
    if (error != NULL) {
        g_warning ("Error talking to Bognor-Regis : %s", error->message);
        g_error_free (error);
    }
}

void
br_queue_play (BrQueue *queue)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    org_moblin_BognorRegis_Queue_play_async (priv->proxy, async_reply, queue);
}

void
br_queue_play_uri (BrQueue    *queue,
                   const char *uri,
                   const char *mimetype)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    org_moblin_BognorRegis_Queue_play_uri_async (priv->proxy, uri,
                                                 mimetype, async_reply, queue);
}

void
br_queue_stop (BrQueue *queue)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    org_moblin_BognorRegis_Queue_stop_async (priv->proxy, async_reply, queue);
}

void
br_queue_next (BrQueue *queue)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    org_moblin_BognorRegis_Queue_next_async (priv->proxy, async_reply, queue);
}

void
br_queue_add_uri (BrQueue    *queue,
                  const char *uri,
                  const char *mimetype)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    org_moblin_BognorRegis_Queue_add_uri_async (priv->proxy, uri,
                                                mimetype, async_reply, queue);
}

void
br_queue_clear (BrQueue *queue)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    org_moblin_BognorRegis_Queue_clear_async (priv->proxy, async_reply, queue);
}

void
br_queue_remove (BrQueue *queue,
                 int      index)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    org_moblin_BognorRegis_Queue_remove_async (priv->proxy, index,
                                               async_reply, queue);
}

void
br_queue_insert_uri (BrQueue    *queue,
                     const char *uri,
                     const char *mimetype,
                     int         position)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    org_moblin_BognorRegis_Queue_insert_uri_async (priv->proxy, uri, mimetype,
                                                   position, async_reply,
                                                   queue);
}

static void
list_uris_reply (DBusGProxy *proxy,
                 char      **OUT_uris,
                 GError     *error,
                 gpointer    userdata)
{
    struct _AsyncClosure *data = (struct _AsyncClosure *) userdata;
    BrQueueListUrisCallback cb;

    cb = (BrQueueListUrisCallback) (data->cb);

    if (error) {
        /* Initialise the parameters correctly */
        OUT_uris = NULL;
    }

    cb (data->queue, OUT_uris, error, data->userdata);

    if (error) {
        g_error_free (error);
    } else {
        g_strfreev (OUT_uris);
    }

    g_free (data);
}

void
br_queue_list_uris (BrQueue                *queue,
                    BrQueueListUrisCallback cb,
                    gpointer                userdata)
{
    struct _AsyncClosure *data;
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    data = g_new (struct _AsyncClosure, 1);
    data->queue = queue;
    data->cb = G_CALLBACK (cb);
    data->userdata = userdata;

    org_moblin_BognorRegis_Queue_list_uris_async (priv->proxy,
                                                  list_uris_reply, data);
}

static void
get_now_playing_reply (DBusGProxy *proxy,
                       char       *OUT_uri,
                       GError     *error,
                       gpointer    userdata)
{
    struct _AsyncClosure *data = (struct _AsyncClosure *) userdata;
    BrQueueGetNowPlayingCallback cb;

    cb = (BrQueueGetNowPlayingCallback) (data->cb);

    if (error) {
        OUT_uri = NULL;
    }

    cb (data->queue, OUT_uri, error, data->userdata);

    if (error) {
        g_error_free (error);
    } else {
        g_free (OUT_uri);
    }

    g_free (data);
}

void
br_queue_get_now_playing (BrQueue                     *queue,
                          BrQueueGetNowPlayingCallback cb,
                          gpointer                     userdata)
{
    struct _AsyncClosure *data;
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    data = g_new (struct _AsyncClosure, 1);
    data->queue = queue;
    data->cb = G_CALLBACK (cb);
    data->userdata = userdata;

    org_moblin_BognorRegis_Queue_get_now_playing_async (priv->proxy,
                                                        get_now_playing_reply,
                                                        data);
}

static void
get_name_reply (DBusGProxy *proxy,
                char       *OUT_name,
                GError     *error,
                gpointer    userdata)
{
    struct _AsyncClosure *data = (struct _AsyncClosure *) userdata;
    BrQueueGetNameCallback cb;

    cb = (BrQueueGetNameCallback) (data->cb);

    if (error) {
        OUT_name = NULL;
    }

    cb (data->queue, OUT_name, error, data->userdata);

    if (error) {
        g_error_free (error);
    } else {
        g_free (OUT_name);
    }

    g_free (data);
}

void
br_queue_get_name (BrQueue               *queue,
                   BrQueueGetNameCallback cb,
                   gpointer               userdata)
{
    struct _AsyncClosure *data;
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    data = g_new (struct _AsyncClosure, 1);
    data->queue = queue;
    data->cb = G_CALLBACK (cb);
    data->userdata = userdata;

    org_moblin_BognorRegis_Queue_get_name_async (priv->proxy,
                                                 get_name_reply, data);
}

void
br_queue_set_position (BrQueue    *queue,
                       double      position)
{
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;
    org_moblin_BognorRegis_Queue_set_position_async (priv->proxy, position,
                                                     async_reply, queue);
}

static void
get_position_reply (DBusGProxy *proxy,
                    double      OUT_position,
                    GError     *error,
                    gpointer    userdata)
{
    struct _AsyncClosure *data = (struct _AsyncClosure *) userdata;
    BrQueueGetPositionCallback cb;

    cb = (BrQueueGetPositionCallback) (data->cb);

    if (error) {
        OUT_position = 0.0;
    }

    cb (data->queue, OUT_position, error, data->userdata);

    if (error) {
        g_error_free (error);
    }

    g_free (data);
}

void
br_queue_get_position (BrQueue                   *queue,
                       BrQueueGetPositionCallback cb,
                       gpointer                   userdata)
{
    struct _AsyncClosure *data;
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    data = g_new (struct _AsyncClosure, 1);
    data->queue = queue;
    data->cb = G_CALLBACK (cb);
    data->userdata = userdata;

    org_moblin_BognorRegis_Queue_get_position_async (priv->proxy,
                                                     get_position_reply, data);
}

static void
get_playing_reply (DBusGProxy *proxy,
                   gboolean    OUT_playing,
                   GError     *error,
                   gpointer    userdata)
{
    struct _AsyncClosure *data = (struct _AsyncClosure *) userdata;
    BrQueueGetPlayingCallback cb;

    cb = (BrQueueGetPlayingCallback) (data->cb);

    if (error) {
        OUT_playing = FALSE;
    }

    cb (data->queue, OUT_playing, error, data->userdata);

    if (error) {
        g_error_free (error);
    }

    g_free (data);
}

void
br_queue_get_playing (BrQueue                  *queue,
                      BrQueueGetPlayingCallback cb,
                      gpointer                  userdata)
{
    struct _AsyncClosure *data;
    BrQueuePrivate *priv;

    g_return_if_fail (IS_BR_QUEUE (queue));

    priv = queue->priv;

    data = g_new (struct _AsyncClosure, 1);
    data->queue = queue;
    data->cb = G_CALLBACK (cb);
    data->userdata = userdata;

    org_moblin_BognorRegis_Queue_get_playing_async (priv->proxy,
                                                    get_playing_reply,
                                                    data);
}

