/*
 * 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 <stdio.h>

#include <glib/gi18n.h>

#include <libnotify/notify.h>

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

enum {
    PROP_0,
};

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

struct _BognorQueuePrivate {
    GQueue *play_queue;
    GVolumeMonitor *volume_monitor;

    char *name;
    gboolean playing;

    BognorQueueItem *current_item;
};

#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), BOGNOR_TYPE_QUEUE, BognorQueuePrivate))
G_DEFINE_TYPE (BognorQueue, bognor_queue, G_TYPE_OBJECT);
static guint32 signals[LAST_SIGNAL] = {0, };

static gboolean bognor_queue_stop (BognorQueue *queue,
                                   GError     **error);
static gboolean bognor_queue_next (BognorQueue *queue,
                                   GError     **error);
static gboolean bognor_queue_play_uri (BognorQueue *queue,
                                       const char  *uri,
                                       const char  *mimetype,
                                       GError     **error);
static gboolean bognor_queue_get_playing (BognorQueue *queue,
                                          gboolean    *playing,
                                          GError     **error);

static gboolean bognor_queue_get_now_playing (BognorQueue *queue,
                                              char       **uri,
                                              GError     **error);
static gboolean bognor_queue_list_uris (BognorQueue *queue,
                                        char      ***uris,
                                        GError     **error);
static gboolean bognor_queue_add_uri (BognorQueue *queue,
                                      const char  *uri,
                                      const char  *mimetype,
                                      GError     **error);
static gboolean bognor_queue_clear (BognorQueue *queue,
                                    GError     **error);
static gboolean bognor_queue_remove (BognorQueue *queue,
                                     int          index,
                                     GError     **error);
static gboolean bognor_queue_insert_uri (BognorQueue *queue,
                                         const char  *uri,
                                         const char  *mimetype,
                                         int          position,
                                         GError     **error);
static gboolean bognor_queue_get_name (BognorQueue *queue,
                                       char       **name,
                                       GError     **error);
static gboolean bognor_queue_set_position (BognorQueue *queue,
                                           double       position,
                                           GError     **error);
static gboolean bognor_queue_get_position (BognorQueue *queue,
                                           double      *position,
                                           GError     **error);

#include "bognor-queue-glue.h"

static void
free_queue_item (BognorQueueItem *item)
{
    g_free (item->uri);
    g_free (item->mimetype);

    g_object_unref (item->file);
    g_object_unref (item->monitor);

    g_slice_free (BognorQueueItem, item);
}

gboolean
bognor_queue_play (BognorQueue *queue,
                   GError     **error)
{
    BognorQueueClass *klass = BOGNOR_QUEUE_GET_CLASS (queue);
    BognorQueuePrivate *priv = queue->priv;

    if (priv->current_item == NULL) {
        bognor_queue_get_next_item (queue);
    }

    if (priv->current_item) {
        klass->set_playing (queue, TRUE);
    }

    priv->playing = TRUE;
    g_signal_emit (queue, signals[PLAYING_CHANGED], 0, TRUE);
    return TRUE;
}

static gboolean
bognor_queue_stop (BognorQueue *queue,
                   GError     **error)
{
    BognorQueueClass *klass = BOGNOR_QUEUE_GET_CLASS (queue);
    BognorQueuePrivate *priv = queue->priv;

    klass->set_playing (queue, FALSE);

    priv->playing = FALSE;
    g_signal_emit (queue, signals[PLAYING_CHANGED], 0, FALSE);
    return TRUE;
}

static gboolean
bognor_queue_next (BognorQueue *queue,
                   GError     **error)
{
    bognor_queue_play_next (queue);
    return TRUE;
}

static void
queue_item_changed_cb (GFileMonitor     *monitor,
                       GFile            *file,
                       GFile            *other_file,
                       GFileMonitorEvent event_type,
                       BognorQueue      *queue)
{
    BognorQueuePrivate *priv = queue->priv;
    GList *u;
    int i;

    g_print ("Got event %d for %s\n", event_type, g_file_get_uri (file));
    if (event_type != G_FILE_MONITOR_EVENT_DELETED) {
        return;
    }

    g_print ("Got delete for %s\n", g_file_get_uri (file));
    /* Find the queue item */
    for (u = priv->play_queue->head, i = 0; u; u = u->next, i++) {
        BognorQueueItem *item = (BognorQueueItem *) u->data;

        if (item->monitor == monitor) {
            g_queue_delete_link (priv->play_queue, u);

            g_signal_emit (queue, signals[REMOVED], 0, item->uri, i);
            free_queue_item (item);
            break;
        }
    }
}

static BognorQueueItem *
queue_item_new (BognorQueue *queue,
                const char  *uri,
                const char  *mimetype)
{
    BognorQueueItem *item;
    GError *error = NULL;

    item = g_slice_new (BognorQueueItem);
    item->uri = g_strdup (uri);
    item->mimetype = g_strdup (mimetype);
    item->file = g_file_new_for_uri (uri);
    item->monitor = g_file_monitor_file (item->file, G_FILE_MONITOR_WATCH_MOUNTS,
                                         NULL, &error);
    if (error) {
        g_warning ("Error watching %s: %s", uri, error->message);
        g_error_free (error);
    } else {
        g_signal_connect (item->monitor, "changed",
                          G_CALLBACK (queue_item_changed_cb), queue);
    }
    return item;
}

static gboolean
bognor_queue_play_uri (BognorQueue *queue,
                       const char  *uri,
                       const char  *mimetype,
                       GError     **error)
{
    BognorQueueClass *klass = BOGNOR_QUEUE_GET_CLASS (queue);
    BognorQueuePrivate *priv = queue->priv;
    BognorQueueItem *item;

    item = queue_item_new (queue, uri, mimetype);

    if (priv->current_item) {
        free_queue_item (priv->current_item);
    }
    priv->current_item = item;

    klass->set_uri (queue, priv->current_item);
    klass->set_playing (queue, TRUE);
    g_signal_emit (queue, signals[NOW_PLAYING], 0, uri);

    priv->playing = TRUE;
    klass->add_item_to_recent (queue, item);
    g_signal_emit (queue, signals[PLAYING_CHANGED], 0, TRUE);

    return TRUE;
}

static gboolean
bognor_queue_get_playing (BognorQueue *queue,
                          gboolean    *playing,
                          GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;

    *playing = priv->playing;
    return TRUE;
}

static gboolean
bognor_queue_list_uris (BognorQueue *queue,
                        char      ***uris,
                        GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;
    char **uri_array;
    GList *u = NULL;
    int n_uris, i;

    n_uris = g_queue_get_length (priv->play_queue);
    uri_array = g_new (char *, n_uris + 1);

    /* Wonder if you're allowed to do this */
    for (u = priv->play_queue->head, i = 0; u; u = u->next, i++) {
        BognorQueueItem *item = u->data;

        uri_array[i] = g_strdup (item->uri);
    }

    /* NULL terminate the array */
    uri_array[n_uris] = NULL;
    *uris = uri_array;

    return TRUE;
}

static gboolean
bognor_queue_get_now_playing (BognorQueue *queue,
                              char       **uri,
                              GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;

    if (priv->current_item == NULL) {
        *uri = NULL;
    } else {
        *uri = g_strdup (priv->current_item->uri);
    }

    return TRUE;
}

static gboolean
bognor_queue_add_uri (BognorQueue *queue,
                      const char  *uri,
                      const char  *mimetype,
                      GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;
    BognorQueueItem *item;
    gboolean empty;

    item = queue_item_new (queue, uri, mimetype);

    g_queue_push_tail (priv->play_queue, item);

    g_signal_emit (queue, signals[ADDED], 0, uri,
                   g_queue_get_length (priv->play_queue) - 1);
    return TRUE;
}

static gboolean
bognor_queue_clear (BognorQueue *queue,
                    GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;
    BognorQueueItem *item;

    while ((item = g_queue_pop_tail (priv->play_queue))) {
        g_signal_emit (queue, signals[REMOVED], 0, item->uri,
                       g_queue_get_length (priv->play_queue));

        free_queue_item (item);
    }

    return TRUE;
}

static gboolean
bognor_queue_remove_uri (BognorQueue *queue,
                         const char  *uri,
                         GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;
    GList *l;
    int position = 0;

    for (l = priv->play_queue->head; l; l = l->next) {
        BognorQueueItem *item = l->data;

        if (g_str_equal (item->uri, uri)) {
            g_queue_delete_link (priv->play_queue, l);

            g_signal_emit (queue, signals[REMOVED], 0, item->uri, position);

            free_queue_item (item);
            break;
        }

        position++;
    }

    return TRUE;
}

static gboolean
bognor_queue_remove (BognorQueue *queue,
                     int          index,
                     GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;
    BognorQueueItem *item;

    item = g_queue_pop_nth (priv->play_queue, index);

    if (item) {
        g_print ("Removing %s from %d\n", item->uri, index);
        g_signal_emit (queue, signals[REMOVED], 0, item->uri, index);

        free_queue_item (item);
    }

    return TRUE;
}

static gboolean
bognor_queue_insert_uri (BognorQueue *queue,
                         const char  *uri,
                         const char  *mimetype,
                         int          position,
                         GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;
    BognorQueueItem *item;
    gboolean empty;

    item = queue_item_new (queue, uri, mimetype);

    g_queue_push_nth (priv->play_queue, item, position);

    g_signal_emit (queue, signals[ADDED], 0, uri, position);

    return TRUE;
}

static gboolean
bognor_queue_get_name (BognorQueue *queue,
                       char       **name,
                       GError     **error)
{
    BognorQueuePrivate *priv = queue->priv;

    *name = g_strdup (priv->name);
    return TRUE;
}

static gboolean
bognor_queue_set_position (BognorQueue *queue,
                           double       position,
                           GError     **error)
{
    BognorQueueClass *klass = BOGNOR_QUEUE_GET_CLASS (queue);

    klass->set_position (queue, position);

    return TRUE;
}

static gboolean
bognor_queue_get_position (BognorQueue *queue,
                           double      *position,
                           GError     **error)
{
    BognorQueueClass *klass = BOGNOR_QUEUE_GET_CLASS (queue);

    klass->get_position (queue, position);

    return TRUE;
}

static void
bognor_queue_finalize (GObject *object)
{
    BognorQueue *self = (BognorQueue *) object;
    BognorQueuePrivate *priv = self->priv;

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

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

static void
bognor_queue_dispose (GObject *object)
{
    BognorQueue *self = (BognorQueue *) object;

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

static void
bognor_queue_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
    BognorQueue *self = (BognorQueue *) object;

    switch (prop_id) {

    default:
        break;
    }
}

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

    switch (prop_id) {

    default:
        break;
    }
}

static void
bognor_queue_class_init (BognorQueueClass *klass)
{
    GObjectClass *o_class = (GObjectClass *)klass;

    o_class->dispose = bognor_queue_dispose;
    o_class->finalize = bognor_queue_finalize;
    o_class->set_property = bognor_queue_set_property;
    o_class->get_property = bognor_queue_get_property;

    g_type_class_add_private (klass, sizeof (BognorQueuePrivate));
    dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (klass),
                                     &dbus_glib_bognor_queue_object_info);

    signals[ADDED] = g_signal_new ("uri-added",
                                   G_TYPE_FROM_CLASS (klass),
                                   G_SIGNAL_RUN_FIRST |
                                   G_SIGNAL_NO_RECURSE, 0, NULL, NULL,
                                   bognor_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,
                                     bognor_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
mount_removed_cb (GVolumeMonitor *monitor,
                  GMount         *mount,
                  BognorQueue    *queue)
{
    BognorQueuePrivate *priv = queue->priv;
    GFile *root;
    GList *u;
    int position;

    root = g_mount_get_root (mount);
    if (root == NULL) {
        g_warning ("Got NULL root for mount removed");
        return;
    }

    position = 0;
    for (u = priv->play_queue->head; u;) {
        BognorQueueItem *item = (BognorQueueItem *) u->data;
        char *relative;

        relative = g_file_get_relative_path (root, item->file);
        if (relative) {
            GList *old = u;

            u = u->next;
            g_queue_delete_link (priv->play_queue, old);

            g_signal_emit (queue, signals[REMOVED], 0, item->uri, position);
            free_queue_item (item);
            g_free (relative);
        } else {
            position++;
            u = u->next;
        }
    }

    g_object_unref (root);
}

static void
bognor_queue_init (BognorQueue *self)
{
    BognorQueuePrivate *priv;

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

    priv->volume_monitor = g_volume_monitor_get ();
    g_signal_connect (priv->volume_monitor, "mount-removed",
                      G_CALLBACK (mount_removed_cb), self);

    priv->play_queue = g_queue_new ();
}

BognorQueue *
bognor_queue_new (void)
{
    BognorQueue *q;

    q = g_object_new (BOGNOR_TYPE_QUEUE, NULL);

    return q;
}

BognorQueue *
bognor_queue_new_from_playlist (const char *playlist)
{
    BognorQueue *q;
    FILE *f;

    q = g_object_new (BOGNOR_TYPE_QUEUE, NULL);

    f = fopen (playlist, "r");
    if (f == NULL) {
        g_object_unref (q);
        return NULL;
    }

    return q;
}

void
bognor_queue_set_name (BognorQueue *queue,
                       const char  *name)
{
    BognorQueuePrivate *priv = queue->priv;

    priv->name = g_strdup (name);
}

void
bognor_queue_emit_position_changed (BognorQueue *queue,
                                    double       position)
{
    g_signal_emit (queue, signals[POSITION_CHANGED], 0, position);
}

BognorQueueItem *
bognor_queue_get_current_item (BognorQueue *queue)
{
    BognorQueuePrivate *priv = queue->priv;

    return priv->current_item;
}

BognorQueueItem *
bognor_queue_get_next_item (BognorQueue *queue)
{
    BognorQueueClass *klass = BOGNOR_QUEUE_GET_CLASS (queue);
    BognorQueuePrivate *priv = queue->priv;
    BognorQueueItem *item = NULL;

    item = g_queue_pop_head (priv->play_queue);

    if (priv->current_item) {
        free_queue_item (priv->current_item);
    }
    priv->current_item = item;

    klass->set_uri (queue, priv->current_item);
    if (item) {
        g_signal_emit (queue, signals[REMOVED], 0, item->uri, 0);
        klass->add_item_to_recent (queue, item);
    }
    if (item == NULL) {
        g_signal_emit (queue, signals[NOW_PLAYING], 0, NULL);
    } else {
        g_signal_emit (queue, signals[NOW_PLAYING], 0, item->uri);
    }

    return item;
}

void
bognor_queue_play_next (BognorQueue *queue)
{
    BognorQueueClass *klass = BOGNOR_QUEUE_GET_CLASS (queue);
    BognorQueueItem *item = NULL;

    if (item = bognor_queue_get_next_item (queue)) {
        klass->set_playing (queue, TRUE);
    } else {
        klass->set_playing (queue, FALSE);
    }
}

gboolean
bognor_queue_get_is_playing (BognorQueue *queue)
{
    BognorQueuePrivate *priv = queue->priv;

    return priv->playing;
}

void
bognor_queue_notify_unknown_format (BognorQueue *queue,
                                    const char  *uri)
{
    NotifyNotification *notification;
    char *basename, *unesc, *message;

    basename = g_path_get_basename (uri);
    unesc = g_uri_unescape_string (basename, NULL);
    g_free (basename);

#ifdef ENABLE_HELIX
    message = g_strdup_printf (_("Sorry, we can't play %s, as we don't have the correct plugin."), unesc);
#else
    message = g_strdup_printf (_("Sorry, we can't play %s, as we don't have the correct plugin. You could try searching for ❝gstreamer codecs❞ on the web."), unesc);
#endif
    g_free (unesc);

    notification = notify_notification_new (_("We can't play this"),
                                            message, NULL, NULL);
    notify_notification_show (notification, NULL);
    g_object_unref (notification);

    g_free (message);
}
