/* ev-bookmarks.c
 *  this file is part of evince, a gnome document viewer
 *
 * Copyright (C) 2010 Carlos Garcia Campos  <carlosgc@gnome.org>
 *
 * Evince is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Evince is distributed in the hope that 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"

#include <string.h>

#include "ev-bookmarks.h"

enum {
        PROP_0,
        PROP_METADATA
};

enum {
        CHANGED,
        N_SIGNALS
};

struct _EvBookmarks {
        GObject base;

        EvMetadata *metadata;
        GList *items;
};

struct _EvBookmarksClass {
        GObjectClass base_class;

        void (*changed) (EvBookmarks *bookmarks);
};

G_DEFINE_TYPE (EvBookmarks, ev_bookmarks, G_TYPE_OBJECT)

static guint signals[N_SIGNALS];

static gint
ev_bookmark_compare (EvBookmark *a,
                     EvBookmark *b)
{
        if (a->page < b->page)
                return -1;
        if (a->page > b->page)
                return 1;
        return 0;
}

static void
ev_bookmark_free (EvBookmark *bm)
{
        if (G_UNLIKELY(!bm))
                return;

        g_free (bm->title);
        g_slice_free (EvBookmark, bm);
}

static void
ev_bookmarks_finalize (GObject *object)
{
        EvBookmarks *bookmarks = EV_BOOKMARKS (object);

        if (bookmarks->items) {
                g_list_free_full (bookmarks->items, (GDestroyNotify)ev_bookmark_free);
                bookmarks->items = NULL;
        }

        if (bookmarks->metadata) {
                g_object_unref (bookmarks->metadata);
                bookmarks->metadata = NULL;
        }

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

static void
ev_bookmarks_init (EvBookmarks *bookmarks)
{
}

static void
ev_bookmarks_set_property (GObject      *object,
                           guint         prop_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
        EvBookmarks *bookmarks = EV_BOOKMARKS (object);

        switch (prop_id) {
        case PROP_METADATA:
                bookmarks->metadata = (EvMetadata *)g_value_dup_object (value);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        }
}

static void
ev_bookmarks_constructed (GObject *object)
{
        EvBookmarks *bookmarks = EV_BOOKMARKS (object);
        gchar       *bm_list_str;
        GVariant    *bm_list;
        GVariantIter iter;
        GVariant    *child;
        GError      *error = NULL;

        if (!ev_metadata_get_string (bookmarks->metadata, "bookmarks", &bm_list_str))
                return;

        if (!bm_list_str || bm_list_str[0] == '\0')
                return;

        bm_list = g_variant_parse ((const GVariantType *)"a(us)",
                                   bm_list_str, NULL, NULL,
                                   &error);
        if (!bm_list) {
                g_warning ("Error getting bookmarks: %s\n", error->message);
                g_error_free (error);

                return;
        }

        g_variant_iter_init (&iter, bm_list);
        while ((child = g_variant_iter_next_value (&iter))) {
                EvBookmark *bm = g_slice_new (EvBookmark);

                g_variant_get (child, "(us)", &bm->page, &bm->title);
                if (bm->title && strlen (bm->title) > 0)
                        bookmarks->items = g_list_prepend (bookmarks->items, bm);
                g_variant_unref (child);
        }
        g_variant_unref (bm_list);

        bookmarks->items = g_list_reverse (bookmarks->items);
}

static void
ev_bookmarks_class_init (EvBookmarksClass *klass)
{
        GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

        gobject_class->set_property = ev_bookmarks_set_property;
        gobject_class->finalize = ev_bookmarks_finalize;
        gobject_class->constructed = ev_bookmarks_constructed;

        g_object_class_install_property (gobject_class,
                                         PROP_METADATA,
                                         g_param_spec_object ("metadata",
                                                              "Metadata",
                                                              "The document metadata",
                                                              EV_TYPE_METADATA,
                                                              G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
        /* Signals */
        signals[CHANGED] =
                g_signal_new ("changed",
                              EV_TYPE_BOOKMARKS,
                              G_SIGNAL_RUN_LAST,
                              G_STRUCT_OFFSET (EvBookmarksClass, changed),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__VOID,
                              G_TYPE_NONE, 0);
}

EvBookmarks *
ev_bookmarks_new (EvMetadata *metadata)
{
        g_return_val_if_fail (EV_IS_METADATA (metadata), NULL);

        return EV_BOOKMARKS (g_object_new (EV_TYPE_BOOKMARKS,
                                           "metadata", metadata, NULL));
}

static void
ev_bookmarks_save (EvBookmarks *bookmarks)
{
        GList          *l;
        GVariantBuilder builder;
        GVariant       *bm_list;
        gchar          *bm_list_str;

        if (!bookmarks->items) {
                ev_metadata_set_string (bookmarks->metadata, "bookmarks", "");
                return;
        }

        g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
        for (l = bookmarks->items; l; l = g_list_next (l)) {
                EvBookmark *bm = (EvBookmark *)l->data;

                g_variant_builder_add (&builder, "(u&s)", bm->page, bm->title);
        }
        bm_list = g_variant_builder_end (&builder);

        bm_list_str = g_variant_print (bm_list, FALSE);
        g_variant_unref (bm_list);
        ev_metadata_set_string (bookmarks->metadata, "bookmarks", bm_list_str);
        g_free (bm_list_str);
}

GList *
ev_bookmarks_get_bookmarks (EvBookmarks *bookmarks)
{
        g_return_val_if_fail (EV_IS_BOOKMARKS (bookmarks), NULL);

        return g_list_copy (bookmarks->items);
}

void
ev_bookmarks_add (EvBookmarks *bookmarks,
                  EvBookmark  *bookmark)
{
        EvBookmark *bm;

        g_return_if_fail (EV_IS_BOOKMARKS (bookmarks));
        g_return_if_fail (bookmark->title != NULL);

        if (g_list_find_custom (bookmarks->items, bookmark, (GCompareFunc)ev_bookmark_compare))
                return;

        bm = g_slice_new (EvBookmark);
        *bm = *bookmark;
        bookmarks->items = g_list_append (bookmarks->items, bm);
        g_signal_emit (bookmarks, signals[CHANGED], 0);
        ev_bookmarks_save (bookmarks);
}

void
ev_bookmarks_delete (EvBookmarks *bookmarks,
                     EvBookmark  *bookmark)
{
        GList *bm_link;

        g_return_if_fail (EV_IS_BOOKMARKS (bookmarks));

        bm_link = g_list_find_custom (bookmarks->items, bookmark, (GCompareFunc)ev_bookmark_compare);
        if (!bm_link)
                return;

        bookmarks->items = g_list_delete_link (bookmarks->items, bm_link);
        g_signal_emit (bookmarks, signals[CHANGED], 0);
        ev_bookmarks_save (bookmarks);
}

void
ev_bookmarks_update (EvBookmarks *bookmarks,
                     EvBookmark  *bookmark)
{
        GList      *bm_link;
        EvBookmark *bm;

        g_return_if_fail (EV_IS_BOOKMARKS (bookmarks));
        g_return_if_fail (bookmark->title != NULL);

        bm_link = g_list_find_custom (bookmarks->items, bookmark, (GCompareFunc)ev_bookmark_compare);
        if (!bm_link)
                return;

        bm = (EvBookmark *)bm_link->data;

        if (strcmp (bookmark->title, bm->title) == 0)
                return;

        g_free (bm->title);
        *bm = *bookmark;
        g_signal_emit (bookmarks, signals[CHANGED], 0);
        ev_bookmarks_save (bookmarks);
}