/*
 * GNOME Online Miners - crawls through your online content
 * Copyright (C) 2014 Red Hat, Inc.
 *
 * This program 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.
 *
 * This program 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.
 *
 * Author: Debarshi Ray <debarshir@gnome.org>
 *
 */

#include "config.h"

#include "gom-application.h"
#include "gom-dbus.h"
#include "gom-miner.h"

#define AUTOQUIT_TIMEOUT 5 /* seconds */

struct _GomApplication
{
  GApplication parent;
  GCancellable *cancellable;
  GomDBus *skeleton;
  GomMiner *miner;
  GQueue *queue;
  GType miner_type;
  gboolean refreshing;
};

struct _GomApplicationClass
{
  GApplicationClass parent_class;
};

enum
{
  PROP_0,
  PROP_MINER_TYPE
};

G_DEFINE_TYPE (GomApplication, gom_application, G_TYPE_APPLICATION);

static void gom_application_refresh_db_cb (GObject *source, GAsyncResult *res, gpointer user_data);

static void
gom_application_insert_shared_content_cb (GObject *source,
                                          GAsyncResult *res,
                                          gpointer user_data)
{
  GomApplication *self;
  GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data);
  GError *error;

  self = GOM_APPLICATION (g_application_get_default ());
  g_application_release (G_APPLICATION (self));

  error = NULL;
  if (!gom_miner_insert_shared_content_finish (GOM_MINER (source), res, &error))
    {
      g_printerr ("Failed to insert shared content: %s\n", error->message);
      g_dbus_method_invocation_take_error (invocation, error);
      goto out;
    }

  gom_dbus_complete_insert_shared_content (self->skeleton, invocation);

 out:
  g_object_unref (invocation);
}

static gboolean
gom_application_insert_shared_content (GomApplication *self,
                                       GDBusMethodInvocation *invocation,
                                       const gchar *account_id,
                                       const gchar *shared_id,
                                       const gchar *shared_type,
                                       const gchar *source_urn)
{
  g_application_hold (G_APPLICATION (self));
  gom_miner_insert_shared_content_async (self->miner,
                                         account_id,
                                         shared_id,
                                         shared_type,
                                         source_urn,
                                         self->cancellable,
                                         gom_application_insert_shared_content_cb,
                                         g_object_ref (invocation));
  return TRUE;
}

static void
gom_application_process_queue (GomApplication *self)
{
  GDBusMethodInvocation *invocation = NULL;
  const gchar **index_types;

  if (self->refreshing)
    goto out;

  if (g_queue_is_empty (self->queue))
    goto out;

  invocation = G_DBUS_METHOD_INVOCATION (g_queue_pop_head (self->queue));
  index_types = g_object_get_data (G_OBJECT (invocation), "index-types");
  gom_miner_set_index_types (self->miner, index_types);

  self->refreshing = TRUE;
  g_application_hold (G_APPLICATION (self));
  gom_miner_refresh_db_async (self->miner,
                              self->cancellable,
                              gom_application_refresh_db_cb,
                              g_object_ref (invocation));

 out:
  g_clear_object (&invocation);
}

static void
gom_application_refresh_db_cb (GObject *source,
                               GAsyncResult *res,
                               gpointer user_data)
{
  GomApplication *self;
  GDBusMethodInvocation *invocation = user_data;
  GError *error = NULL;

  self = GOM_APPLICATION (g_application_get_default ());
  g_application_release (G_APPLICATION (self));
  self->refreshing = FALSE;

  gom_miner_refresh_db_finish (GOM_MINER (source), res, &error);
  if (error != NULL)
    {
      g_printerr ("Failed to refresh the DB cache: %s\n", error->message);
      g_dbus_method_invocation_take_error (invocation, error);
      goto out;
    }

  gom_dbus_complete_refresh_db (self->skeleton, invocation);

 out:
  g_object_unref (invocation);
  gom_application_process_queue (self);
}

static gboolean
gom_application_refresh_db (GomApplication *self,
                            GDBusMethodInvocation *invocation,
                            const gchar *const *arg_index_types)
{
  gchar **index_types;

  index_types = g_strdupv ((gchar **) arg_index_types);
  g_object_set_data_full (G_OBJECT (invocation), "index-types", index_types, (GDestroyNotify) g_strfreev);
  g_queue_push_tail (self->queue, g_object_ref (invocation));
  gom_application_process_queue (self);
  return TRUE;
}

static gboolean
gom_application_dbus_register (GApplication *application,
                               GDBusConnection *connection,
                               const gchar *object_path,
                               GError **error)
{
  GomApplication *self = GOM_APPLICATION (application);
  gboolean retval = FALSE;

  if (!G_APPLICATION_CLASS (gom_application_parent_class)->dbus_register (application,
                                                                          connection,
                                                                          object_path,
                                                                          error))
    goto out;

  if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton),
                                         connection,
                                         object_path,
                                         error))
    goto out;

  retval = TRUE;

 out:
  return retval;
}

static void
gom_application_dbus_unregister (GApplication *application,
                                 GDBusConnection *connection,
                                 const gchar *object_path)
{
  GomApplication *self = GOM_APPLICATION (application);

  if (self->skeleton != NULL)
    {
      if (g_dbus_interface_skeleton_has_connection (G_DBUS_INTERFACE_SKELETON (self->skeleton), connection))
        g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (self->skeleton),
                                                            connection);
    }

  G_APPLICATION_CLASS (gom_application_parent_class)->dbus_unregister (application, connection, object_path);
}

static void
gom_application_shutdown (GApplication *application)
{
  GomApplication *self = GOM_APPLICATION (application);

  g_cancellable_cancel (self->cancellable);

  G_APPLICATION_CLASS (gom_application_parent_class)->shutdown (application);
}

static void
gom_application_constructed (GObject *object)
{
  GomApplication *self = GOM_APPLICATION (object);
  const gchar *display_name;

  G_OBJECT_CLASS (gom_application_parent_class)->constructed (object);

  self->miner = g_object_new (self->miner_type, NULL);
  display_name = gom_miner_get_display_name (self->miner);
  gom_dbus_set_display_name (self->skeleton, display_name);
}

static void
gom_application_dispose (GObject *object)
{
  GomApplication *self = GOM_APPLICATION (object);

  g_clear_object (&self->cancellable);
  g_clear_object (&self->miner);
  g_clear_object (&self->skeleton);

  if (self->queue != NULL)
    {
      g_queue_free_full (self->queue, g_object_unref);
      self->queue = NULL;
    }

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

static void
gom_application_set_property (GObject *object,
                              guint prop_id,
                              const GValue *value,
                              GParamSpec *pspec)
{
  GomApplication *self = GOM_APPLICATION (object);

  switch (prop_id)
    {
    case PROP_MINER_TYPE:
      self->miner_type = g_value_get_gtype (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gom_application_init (GomApplication *self)
{
  self->cancellable = g_cancellable_new ();

  self->skeleton = gom_dbus_skeleton_new ();
  g_signal_connect_swapped (self->skeleton,
                            "handle-insert-shared-content",
                            G_CALLBACK (gom_application_insert_shared_content),
                            self);
  g_signal_connect_swapped (self->skeleton, "handle-refresh-db", G_CALLBACK (gom_application_refresh_db), self);

  self->queue = g_queue_new ();
}

static void
gom_application_class_init (GomApplicationClass *klass)
{
  GObjectClass *oclass = G_OBJECT_CLASS (klass);
  GApplicationClass *application_class = G_APPLICATION_CLASS (klass);

  oclass->constructed = gom_application_constructed;
  oclass->dispose = gom_application_dispose;
  oclass->set_property = gom_application_set_property;
  application_class->dbus_register = gom_application_dbus_register;
  application_class->dbus_unregister = gom_application_dbus_unregister;
  application_class->shutdown = gom_application_shutdown;

  g_object_class_install_property (oclass,
                                   PROP_MINER_TYPE,
                                   g_param_spec_gtype ("miner-type",
                                                       "Miner type",
                                                       "A GType representing the miner class",
                                                       GOM_TYPE_MINER,
                                                       G_PARAM_CONSTRUCT_ONLY
                                                       | G_PARAM_STATIC_STRINGS
                                                       | G_PARAM_WRITABLE));
}

GApplication *
gom_application_new (const gchar *application_id,
                     GType miner_type)
{
  return g_object_new (GOM_TYPE_APPLICATION,
                       "application-id", application_id,
                       "flags", G_APPLICATION_IS_SERVICE,
                       "inactivity-timeout", AUTOQUIT_TIMEOUT,
                       "miner-type", miner_type,
                       NULL);
}
