/*
 * unity-webapps-context-daemon.c
 * Copyright (C) Canonical LTD 2011
 *
 * Author: Robert Carr <racarr@canonical.com>
 * 
 unity-webapps is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * unity-webapps 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 Lesser 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, see <http://www.gnu.org/licenses/>.";
 */

#include "config.h"
#include <stdlib.h>

#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gtk/gtk.h>

#include "unity-webapps-context-daemon.h"
#include "unity-webapps-gen-context.h"

#include "unity-webapps-dbus-defs.h"

#include "unity-webapps-application-info.h"
#include "unity-webapps-preinstalled-application-info.h"

//#include "unity-webapps-dirs.h"
#include "unity-webapps-resource-db.h"
#include "unity-webapps-app-db.h"

#include "unity-webapps-icon-theme.h"

#include "unity-webapps-debug.h"

#include "unity-webapps-interest-manager.h"
#include "unity-webapps-action-manager.h"

#include "unity-webapps-interest-tracker.h"

#include "unity-webapps-wire-version.h"

#define WNCK_I_KNOW_THIS_IS_UNSTABLE
#include <libwnck/libwnck.h>

/* 
 * If a local icon file has been downloaded more recently than this time (in seconds) we will simply use our existing
 * download. In the future maybe this should be more intelligent and use HTTP IfModifiedSince 
 *
 */
#define UNITY_WEBAPPS_CONTEXT_ICON_STALE_TIME 60 /*seconds*/ * 30 /*minutes*/

static GMainLoop *mainloop = NULL;

static GDBusConnection *session_bus = NULL;

static UnityWebappsGenContext *context_service_interface;

static UnityWebappsInterestTracker *interest_tracker = NULL;
static UnityWebappsActionManager   *action_manager = NULL;
static UnityWebappsInterestManager *interest_manager = NULL;


static void
export_object (GDBusConnection *connection,
	       UnityWebappsContextDaemon *daemon);

static void
emit_context_ready (GDBusConnection *connection);
static void
emit_no_interest (GDBusConnection *connection, gboolean user_abandoned);
static void
emit_interest_appeared (GDBusConnection *connection, gint interest_id);
static void
emit_interest_vanished (GDBusConnection *connection, gint interest_id);

void unity_webapps_context_daemon_emit_close (GDBusConnection *connection, gint interest_id);
void unity_webapps_context_daemon_emit_view_is_active_changed (GDBusConnection *connection, gint interest_id, gboolean is_active);
void unity_webapps_context_daemon_emit_view_location_changed (GDBusConnection *connection, gint interest_id, const gchar *location);
void unity_webapps_context_daemon_emit_view_window_changed (GDBusConnection *connection, gint interest_id, guint64 window);
void unity_webapps_context_daemon_emit_preview_requested (GDBusConnection *connection, gint interest_id);
void unity_webapps_context_daemon_emit_action_invoked (GDBusConnection *connection, const gchar *name);

static UnityWebappsApplicationInfo *application_info = NULL;


static void
emit_no_interest (GDBusConnection *connection,
                  gboolean user_abandoned)
{
  GError *error;
  
  error = NULL;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting NoInterest signal");

  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "NoInterest",
				 g_variant_new ("(b)", user_abandoned, NULL),
				 &error /* Error */);
  
  if (error != NULL)
    {
      /*
       * TODO: Should this be fatal? If this fails we can not guarantee that the daemon will ever close...
	  */
      g_critical ("Error emitting NoInterest signal (from context daemon): %s", error->message);
      g_error_free (error);
      return;
    }
}

static void
emit_context_ready (GDBusConnection *connection)
{
  GError *error;
  
  error = NULL;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting ContextReady signal");
  
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "ContextReady",
				 g_variant_new ("(s)", UNITY_WEBAPPS_WIRE_PROTOCOL_VERSION, NULL),
				 &error);
  /* 
   * TODO:
   * Maybe this should be a fatal error instead of a critical.
   * In this case the service isn't managing the daemon, and so we can't be assured
   * the daemon will ever quit.
   */
  if (error != NULL)
    {
      g_critical("Error emiting ContextReady signal: %s", error->message);
      g_error_free (error);
      
      return;
    }
}

static void
emit_interest_appeared (GDBusConnection *connection, gint interest_id)
{
  GError *error;
  
  error = NULL;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting InterestAppeared signal");
  
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "InterestAppeared",
				 g_variant_new ("(i)", interest_id, NULL),
				 &error);
  if (error != NULL)
    {
      g_critical("Error emiting InterestAppeared signal: %s", error->message);
      g_error_free (error);
      
      return;
    }
}

static void
emit_interest_vanished (GDBusConnection *connection, gint interest_id)
{
  GError *error;
  
  error = NULL;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting InterestVanished signal");
  
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "InterestVanished",
				 g_variant_new ("(i)", interest_id, NULL),
				 &error);
  if (error != NULL)
    {
      g_critical("Error emiting InterestVanished signal: %s", error->message);
      g_error_free (error);
      
      return;
    }
}

static void
bus_filter_service_ready (GDBusConnection *connection,
			  const gchar *sender,
			  const gchar *object,
			  const gchar *interface,
			  const gchar *signal,
			  GVariant *params,
			  gpointer user_data)
{

  if (g_strcmp0(signal, "ServiceReady") != 0)
    {
      g_warning ("Unexpected signal on context daemon instance: %s", signal);
      return;
    }

  /* 
   * If we have received the ServiceReady signal, it means that the service has crashed.
   * So, we should emit the ContextReady signal again, such that the new service daemon
   * can track the context.
   */
  UNITY_WEBAPPS_NOTE (CONTEXT, "Got ServiceReady signal");
  emit_context_ready (session_bus);
}

static gint
add_interest (GDBusConnection *connection,
	      UnityWebappsContextDaemon *daemon,
	      const gchar *name)
{
  return unity_webapps_interest_manager_add_interest (interest_manager, name);
}

/*
 * A client has explicitly removed it's interest. If we have interests we need to emit the signal.
 */
 
static void
lost_interest (GDBusConnection *connection,
	       UnityWebappsContextDaemon *daemon,
	       gint interest_id,
         gboolean user_abandoned)
{
  unity_webapps_interest_manager_remove_interest (interest_manager, interest_id, user_abandoned);
}

static void
bus_get_cb (GObject *object, GAsyncResult *res, gpointer user_data)
{
  GError *error = NULL;
  GDBusConnection *connection = g_bus_get_finish(res, &error);
  UnityWebappsContextDaemon *daemon;
 
  
  if (error != NULL) 
    {
      g_error("Unable to get session bus: %s", error->message);
      g_error_free(error);
      return;
    }
  
  session_bus = connection;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Connected to the Session Bus");
  
  // TODO: Investigate free on shutdown, just for correctness.
  daemon = unity_webapps_context_daemon_new ();
  
  export_object (session_bus, daemon);

  emit_context_ready (session_bus);
  
  g_dbus_connection_signal_subscribe (session_bus,
				      NULL,
				      UNITY_WEBAPPS_SERVICE_IFACE,
				      "ServiceReady",
				      NULL,
				      NULL,
				      G_DBUS_SIGNAL_FLAGS_NONE,
				      bus_filter_service_ready,
				      daemon /* user data */,
				      NULL /* destroy notify */);
				 
}

static gboolean
parse_args (gint argc, gchar **argv, gchar **name, gchar **domain, gchar **icon_url, gchar **mime_types, gchar **desktop_name)
{
  if (argc != 5 && argc != 6)
    return FALSE;

  *name = argv[1];
  *domain = argv[2];
  *icon_url = argv[3];
  *mime_types = argv[4];
  if (argc == 6)
    {
      *desktop_name = argv[5];
    }
  else 
    {
      *desktop_name = NULL;
    }

  if (g_strcmp0 (*icon_url, "none") == 0)
    {
      *icon_url = NULL;
    }
  if (g_strcmp0 (*mime_types, "none") == 0)
    {
      *mime_types = NULL;
    }

  return TRUE;
}

gint 
main (gint argc, gchar **argv)
{
  gchar *name, *domain, *icon_url, *mime_types, *desktop_name;
  gtk_init (&argc, &argv);

  bindtextdomain (GETTEXT_PACKAGE, NULL);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);

  desktop_name = NULL;
  if (!parse_args (argc, argv, &name, &domain, &icon_url, &mime_types, &desktop_name))
    return 1;
  
#ifdef UNITY_WEBAPPS_ENABLE_DEBUG
  unity_webapps_debug_initialize_flags ();
#endif
  
  unity_webapps_resource_db_open ();

  if (desktop_name == NULL)
    {
      application_info = unity_webapps_application_info_new (name,
                                                             domain,
                                                             icon_url,
                                                             mime_types);
    }
  else
    {
      application_info = unity_webapps_preinstalled_application_info_new (name,
                                                                          domain,
                                                                          icon_url,
                                                                          mime_types,
                                                                          desktop_name);
    }
  
  wnck_set_client_type (WNCK_CLIENT_TYPE_PAGER);
  
  g_bus_get(G_BUS_TYPE_SESSION,
	    NULL, /* Cancellable */
	    bus_get_cb,
	    NULL /* User data */);
  mainloop = g_main_loop_new(NULL, FALSE);
  g_main_loop_run(mainloop);

  return 0;
}

static gboolean
on_handle_add_icon (UnityWebappsGenContext *context,
		    GDBusMethodInvocation *invocation,
		    const gchar *icon_url,
		    gint size,
		    gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling AddIcon call %s with size %d", icon_url, size);
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  
  unity_webapps_application_info_add_icon_at_size (application_info, icon_url, size);
  
  return TRUE;
}

static gboolean
on_handle_get_icon_name (UnityWebappsGenContext *context,
			 GDBusMethodInvocation *invocation,
			 gpointer user_data)
{
  gchar *icon_file_name;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling GetIconName call");

  icon_file_name = unity_webapps_application_info_get_desktop_icon_name (application_info, TRUE);
  UNITY_WEBAPPS_NOTE (CONTEXT, "Icon name is: %s", icon_file_name);
  
  g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", icon_file_name, NULL));
  
  g_free (icon_file_name);

  return TRUE;
}

static gboolean
on_handle_add_interest (UnityWebappsGenContext *context,
			GDBusMethodInvocation *invocation,
			gpointer user_data)
{
  UnityWebappsContextDaemon *context_daemon;
  const gchar *fan;
  gint interest_id;
  
  context_daemon = (UnityWebappsContextDaemon *)user_data;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling AddInterest call");
  
  fan = g_dbus_method_invocation_get_sender (invocation);
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Interest is from %s", fan);

  interest_id = add_interest (session_bus, context_daemon, fan);
  
  g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", interest_id, NULL));
  
  return TRUE;
}

static gboolean
on_handle_lost_interest (UnityWebappsGenContext *context,
			 GDBusMethodInvocation *invocation,
			 gint interest_id,
       gboolean user_abandoned,
			 gpointer user_data)
{
  UnityWebappsContextDaemon *context_daemon;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling LostInterest call");

  context_daemon = (UnityWebappsContextDaemon *)user_data;

  g_dbus_method_invocation_return_value (invocation, NULL);
  
  lost_interest (session_bus, context_daemon, interest_id, user_abandoned);
  
  return TRUE;
}

static gboolean
on_handle_list_interests (UnityWebappsGenContext *context,
			  GDBusMethodInvocation *invocation,
			  gpointer user_data)
{
  GVariant *interests_list;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling ListInterests call");
  
  interests_list = unity_webapps_interest_manager_list_interests (interest_manager);
  
  if (!interests_list)
    {
      GVariant *array;
      array = g_variant_new ("ai", NULL, 0);
      g_dbus_method_invocation_return_value (invocation, g_variant_new_tuple (&array, 1));
    }
  else
    {
      g_dbus_method_invocation_return_value (invocation, g_variant_new_tuple (&interests_list, 1));
    }
  
  return TRUE;
}

static gboolean
on_handle_get_interest_count (UnityWebappsGenContext *context,
			      GDBusMethodInvocation *invocation,
			      gpointer user_data)
{
  gint num_interests;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling GetInterestCount call");
  
  num_interests = (gint) unity_webapps_interest_manager_get_num_interests (interest_manager);
  
  g_dbus_method_invocation_return_value (invocation,
					 g_variant_new ("(i)", num_interests, NULL));
  UNITY_WEBAPPS_NOTE (CONTEXT, "Number of interests is %d", num_interests);
  
  return TRUE;

}

static gboolean
on_handle_get_interest_owner (UnityWebappsGenContext *context,
			      GDBusMethodInvocation *invocation,
			      gint interest_id,
			      gpointer user_data)
{
  const gchar *owner;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling GetInterestOwner call");
  
  owner = unity_webapps_interest_manager_get_interest_owner (interest_manager, interest_id);
  
  if (owner == NULL)
    {
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "", NULL));
      UNITY_WEBAPPS_NOTE (CONTEXT, "Interest not found (%d)", interest_id);
    }
  else
    {
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", owner, NULL));
      UNITY_WEBAPPS_NOTE (CONTEXT, "Interest Owner is: %s", owner);
    }
  
  return TRUE;
}

static gboolean
on_handle_get_interest_is_active (UnityWebappsGenContext *context,
				  GDBusMethodInvocation *invocation,
				  gint interest_id,
				  gpointer user_data)
{
  gboolean is_active;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling GetInterestIsActive call");
  
  is_active = unity_webapps_interest_manager_get_interest_is_active (interest_manager, interest_id);
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Interest %d %s", interest_id, is_active ? "is active" : "is not active");
  
  g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", is_active, NULL));
  
  return TRUE;
}

static gboolean
on_handle_set_interest_is_active (UnityWebappsGenContext *context,
				  GDBusMethodInvocation *invocation,
				  gint interest_id,
				  gboolean is_active,
				  gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling SetInterestIsActive call (%d)", interest_id);
  
  unity_webapps_interest_manager_set_interest_is_active (interest_manager, 
							 interest_id,
							 is_active);
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  
  return TRUE;
}


static gboolean
on_handle_get_interest_window (UnityWebappsGenContext *context,
				  GDBusMethodInvocation *invocation,
				  gint interest_id,
				  gpointer user_data)
{
  guint64 window;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling GetInterestWindow call (%d)", interest_id);
  
  window = unity_webapps_interest_manager_get_interest_window (interest_manager, interest_id);
  
  g_dbus_method_invocation_return_value (invocation, g_variant_new ("(t)", window, NULL));
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Window for interest (%d) is %lu", interest_id, (unsigned long)window);
  
  return TRUE;
}

static gboolean
on_handle_set_interest_window (UnityWebappsGenContext *context,
				  GDBusMethodInvocation *invocation,
				  gint interest_id,
				  guint64 window,
				  gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling SetInterestWindow call (%d)", interest_id);
  
  unity_webapps_interest_manager_set_interest_window (interest_manager, interest_id,
							 window);
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  
  return TRUE;
}



static gboolean
on_handle_get_interest_location (UnityWebappsGenContext *context,
				 GDBusMethodInvocation *invocation,
				 gint interest_id,
				 gpointer user_data)
{
  const gchar *location;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling GetInterestLocation call (%d)", interest_id);
  
  
  location = unity_webapps_interest_manager_get_interest_location (interest_manager, interest_id);
  
  g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", location ? location : "", NULL));
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Location for interest (%d) is '%s'", interest_id, location);
  
  return TRUE;
}

static gboolean
on_handle_set_interest_location (UnityWebappsGenContext *context,
				 GDBusMethodInvocation *invocation,
				 gint interest_id,
				 const gchar *location,
				 gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling SetInterestLocation call (%d): %s", interest_id, location);
  
  unity_webapps_interest_manager_set_interest_location (interest_manager, interest_id,
							location);
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  
  return TRUE;
}

static gboolean
on_handle_request_preview (UnityWebappsGenContext *context,
			   GDBusMethodInvocation *invocation,
			   gint interest_id,
			   gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling RequestPreview call");
  
  unity_webapps_context_daemon_emit_preview_requested (session_bus, interest_id);
  
  unity_webapps_interest_manager_add_preview_request (interest_manager, interest_id,
						      invocation);
  
  return TRUE;
}

static gboolean
on_handle_preview_ready (UnityWebappsGenContext *context,
			 GDBusMethodInvocation *invocation,
			 gint interest_id,
			 const gchar *preview_data,
			 gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling PreviewReady call");
  
  unity_webapps_interest_manager_clear_preview_requests (interest_manager, interest_id, preview_data);
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  
  return TRUE;
}

static gboolean
on_handle_raise (UnityWebappsGenContext *context,
                 GDBusMethodInvocation *invocation,
                 const gchar * const *files,
                 gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling Raise call");
  unity_webapps_context_daemon_emit_raise (session_bus, unity_webapps_interest_tracker_get_most_recent_interest (interest_tracker), files);

  g_dbus_method_invocation_return_value (invocation, NULL);

  return TRUE;
}

static gboolean
on_handle_raise_interest (UnityWebappsGenContext *context,
			  GDBusMethodInvocation *invocation,
			  gint interest_id,
			  gpointer user_data)
{
  g_message ("Handling RaiseInterest call (%d)", interest_id);
  unity_webapps_context_daemon_emit_raise (session_bus, interest_id, NULL);

  g_dbus_method_invocation_return_value (invocation, NULL);
  
  return TRUE;
}

static gboolean
on_handle_close (UnityWebappsGenContext *context,
		    GDBusMethodInvocation *invocation,
		    gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling Close call");
  unity_webapps_context_daemon_emit_close (session_bus, -1);

  g_dbus_method_invocation_return_value (invocation, NULL);
  
  return TRUE;
}

static gboolean
on_handle_close_interest (UnityWebappsGenContext *context,
			  GDBusMethodInvocation *invocation,
			  gint interest_id,
			  gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling CloseInterest call (%d)", interest_id);

  unity_webapps_context_daemon_emit_close (session_bus, interest_id);

  g_dbus_method_invocation_return_value (invocation, NULL);
  
  return TRUE;
}

static gboolean
on_handle_get_application_accept_data (UnityWebappsGenContext *context,
                                       GDBusMethodInvocation *invocation,
                                       gpointer user_data)
{
  UnityWebappsContextDaemon *daemon = (UnityWebappsContextDaemon *)user_data;
  gchar **mimes = daemon->dnd_mimes;
  GVariantBuilder *builder;
  GVariant *value;
  gchar **it;

  builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
  if (mimes)
    for (it = mimes; *it; it++)
      {
        g_variant_builder_add (builder, "s", *it);
      }
  value = g_variant_new ("(as)", builder);

  g_dbus_method_invocation_return_value (invocation,
                                         value);

  g_variant_builder_unref (builder);

  return TRUE;
}

static gboolean
on_handle_set_application_accept_data (UnityWebappsGenContext *context,
                                       GDBusMethodInvocation *invocation,
                                       const gchar * const *mimes,
                                       gint interest_id,
                                       gpointer user_data)
{
  UnityWebappsContextDaemon *daemon = (UnityWebappsContextDaemon *)user_data;

  if (daemon->dnd_mimes)
    g_strfreev (daemon->dnd_mimes);

  daemon->dnd_mimes = g_strdupv ((gchar **)mimes);

  g_dbus_method_invocation_return_value (invocation, NULL);

  GVariantBuilder *builder;
  const gchar * const *it;

  builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
  if (mimes)
    for (it = mimes; *it; it++)
      {
        g_variant_builder_add (builder, "s", *it);
      }


  g_dbus_connection_emit_signal (session_bus,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "AcceptDataChanged",
				 g_variant_new ("(as)",
                                                builder,
						NULL),
				 NULL);

  return TRUE;
}

static gboolean
on_handle_add_application_actions (UnityWebappsGenContext *context,
                                   GDBusMethodInvocation *invocation,
                                   const gchar *labels[],
                                   gint interest_id,
                                   gpointer user_data)
{
  gint i;
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling AddApplicationActions call");

  if (interest_id == -1)
    {
      UNITY_WEBAPPS_NOTE (CONTEXT, "Ignoring request to add action from uninterested client");
      goto out;
    }

  for (i = 0; labels[i]; i++)
    {
      unity_webapps_action_manager_add_action (action_manager, labels[i], interest_id);
    }

 out:

  g_dbus_method_invocation_return_value (invocation, NULL);

  return TRUE;
}

static gboolean
on_handle_remove_application_action (UnityWebappsGenContext *context,
				     GDBusMethodInvocation *invocation,
				     const gchar *label,
				     gint interest_id,
				     gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling RemoveApplicationAction call (%s)", label);
  
  if (interest_id == -1)
    {
      UNITY_WEBAPPS_NOTE (CONTEXT, "Ignoring request to add action from uninterested client");
      goto out;
    }
  
  unity_webapps_action_manager_remove_action (action_manager, label, interest_id);
 out:
  g_dbus_method_invocation_return_value (invocation, NULL);

  return TRUE;
}

static gboolean
on_handle_remove_application_actions (UnityWebappsGenContext *context,
				      GDBusMethodInvocation *invocation,
				      gint interest_id,
				      gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling RemoveApplicationActions call");
  
  if (interest_id == -1)
    {
      UNITY_WEBAPPS_NOTE (CONTEXT, "Ignoring request to add action from uninterested client");
      goto out;
    }
  
  unity_webapps_action_manager_remove_all_actions (action_manager, interest_id);
 out:
  g_dbus_method_invocation_return_value (invocation, NULL);

  return TRUE;
}

static gboolean
on_handle_set_homepage (UnityWebappsGenContext *context,
			GDBusMethodInvocation *invocation,
			const gchar *homepage,
			gpointer user_data)
{
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling SetHomepage call");
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  
  unity_webapps_application_info_set_homepage (application_info, homepage);
  
  return TRUE;
}

static gboolean
on_handle_shutdown (UnityWebappsGenContext *context,
		    GDBusMethodInvocation *invocation,
		    gpointer user_data)
{
  UnityWebappsContextDaemon *context_daemon;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Handling Shutdown call");
  
  context_daemon = (UnityWebappsContextDaemon *)user_data;
  
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  g_dbus_connection_flush_sync (session_bus, NULL, NULL);
  
  // TODO: Really should only do this in debug mode?
  unity_webapps_context_daemon_free (context_daemon);
  
  exit (0);
  
  return TRUE;
}


static void
export_object (GDBusConnection *connection,
	       UnityWebappsContextDaemon *daemon)
{
  GError *error;
  
  context_service_interface = unity_webapps_gen_context_skeleton_new ();
  
  g_signal_connect (context_service_interface,
		    "handle-add-icon",
		    G_CALLBACK (on_handle_add_icon),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-get-icon-name",
		    G_CALLBACK (on_handle_get_icon_name),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-add-interest",
		    G_CALLBACK (on_handle_add_interest),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-lost-interest",
		    G_CALLBACK (on_handle_lost_interest),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-list-interests",
		    G_CALLBACK (on_handle_list_interests),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-get-interest-count",
		    G_CALLBACK (on_handle_get_interest_count),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-get-interest-owner",
		    G_CALLBACK (on_handle_get_interest_owner),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-get-view-is-active",
		    G_CALLBACK (on_handle_get_interest_is_active),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-set-view-is-active",
		    G_CALLBACK (on_handle_set_interest_is_active),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-get-view-location",
		    G_CALLBACK (on_handle_get_interest_location),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-set-view-location",
		    G_CALLBACK (on_handle_set_interest_location),
		    daemon);		    
  g_signal_connect (context_service_interface,
		    "handle-request-preview",
		    G_CALLBACK (on_handle_request_preview),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-preview-ready",
		    G_CALLBACK (on_handle_preview_ready),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-get-view-window",
		    G_CALLBACK (on_handle_get_interest_window),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-set-view-window",
		    G_CALLBACK (on_handle_set_interest_window),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-raise",
		    G_CALLBACK (on_handle_raise),
		    daemon);  
  g_signal_connect (context_service_interface,
		    "handle-raise-interest",
		    G_CALLBACK (on_handle_raise_interest),
		    daemon);  
  g_signal_connect (context_service_interface,
		    "handle-close",
		    G_CALLBACK (on_handle_close),
		    daemon);  
  g_signal_connect (context_service_interface,
		    "handle-close-interest",
		    G_CALLBACK (on_handle_close_interest),
		    daemon);  
  g_signal_connect (context_service_interface,
		    "handle-set-application-accept-data",
		    G_CALLBACK (on_handle_set_application_accept_data),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-get-application-accept-data",
		    G_CALLBACK (on_handle_get_application_accept_data),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-add-application-actions",
		    G_CALLBACK (on_handle_add_application_actions),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-remove-application-action",
		    G_CALLBACK (on_handle_remove_application_action),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-remove-application-actions",
		    G_CALLBACK (on_handle_remove_application_actions),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-set-homepage",
		    G_CALLBACK (on_handle_set_homepage),
		    daemon);
  g_signal_connect (context_service_interface,
		    "handle-shutdown",
		    G_CALLBACK (on_handle_shutdown),
		    daemon);
  
  
  unity_webapps_gen_context_set_view_is_active (context_service_interface, FALSE);
  
  unity_webapps_gen_context_set_name (context_service_interface, unity_webapps_application_info_get_name (application_info));
  unity_webapps_gen_context_set_domain (context_service_interface, unity_webapps_application_info_get_domain (application_info));
  unity_webapps_gen_context_set_desktop_name (context_service_interface, unity_webapps_application_info_get_desktop_file_name (application_info));
  
  unity_webapps_gen_context_set_focus_interest (context_service_interface, -1);
  
  error = NULL;
  
  g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (context_service_interface),
				    connection,
				    UNITY_WEBAPPS_CONTEXT_PATH,
				    &error);
  
  if (error != NULL)
    {
      g_error ("Error exporting Unity Webapps Context object: %s", error->message);
      g_error_free (error);
      
      return;
    }
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Exported Context object");
}

static void
on_manager_became_lonely (UnityWebappsInterestManager *manager,
        gboolean user_abandoned,
			  gpointer user_data)
{
  emit_no_interest (session_bus, user_abandoned);
}

static void
on_manager_interest_added (UnityWebappsInterestManager *manager,
			   UnityWebappsInterest *interest,
			   gpointer user_data)
{
  emit_interest_appeared (session_bus, interest->id);
}

static void
on_manager_interest_removed (UnityWebappsInterestManager *manager,
			     UnityWebappsInterest *interest,
			     gpointer user_data)
{
  emit_interest_vanished (session_bus, interest->id);
}

static void
on_manager_active_changed (UnityWebappsInterestManager *manager,
			   gint interest_id,
			   gboolean is_active,
			   gpointer user_data)
{
  unity_webapps_context_daemon_emit_view_is_active_changed (session_bus, interest_id, is_active);
}

static void
on_manager_window_changed (UnityWebappsInterestManager *manager,
			   gint interest_id,
			   guint64 window,
			   gpointer user_data)
{
  unity_webapps_context_daemon_emit_view_window_changed (session_bus, interest_id, window);
}

static void
on_manager_location_changed (UnityWebappsInterestManager *manager,
			     gint interest_id,
			     const gchar *location,
			     gpointer user_data)
{
  unity_webapps_context_daemon_emit_view_location_changed (session_bus, interest_id, location);
}

static void
on_action_manager_action_activated (UnityWebappsActionManager *manager,
				    const gchar *name,
				    gpointer user_data)
{
  unity_webapps_context_daemon_emit_raise (session_bus, -1, NULL);

  unity_webapps_context_daemon_emit_action_invoked (session_bus, name);
}

static void
unity_webapps_context_daemon_most_recent_interest_changed (GObject *object,
							   GParamSpec *pspec,
							   gpointer user_data)
{
  UnityWebappsInterestTracker *interest_tracker;
  gint most_recent_interest;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Updating FocusWindow property");
  
  interest_tracker = UNITY_WEBAPPS_INTEREST_TRACKER (object);
  most_recent_interest = unity_webapps_interest_tracker_get_most_recent_interest (interest_tracker);

  unity_webapps_gen_context_set_focus_interest (context_service_interface, most_recent_interest);
}

UnityWebappsContextDaemon *
unity_webapps_context_daemon_new ()
{
  UnityWebappsContextDaemon *daemon;
  gchar *icon_path, *canonical_name;
  const gchar *name;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Constructing new UnityWebappsContextDaemon object");
  
  daemon = g_malloc0 (sizeof (UnityWebappsContextDaemon));
  
  //  daemon->icon_theme_dir = unity_webapps_icon_theme_get_theme_dir ();
  daemon->icon_theme_dir = NULL;
  daemon->dnd_mimes = NULL;
  daemon->interests = 0;
  
  daemon->interest_vanished_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  daemon->name_interest_counts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  
  daemon->icon_file_name = unity_webapps_application_info_save_icon_file (application_info);

  // TODO: Error
  unity_webapps_application_info_ensure_desktop_file (application_info, NULL);
  daemon->desktop_file_name = unity_webapps_application_info_get_desktop_file_name (application_info);

  icon_path = unity_webapps_icon_theme_get_application_icon_path (daemon->icon_file_name);
  
  canonical_name = unity_webapps_application_info_get_canonical_full_name (application_info);
  name = unity_webapps_application_info_get_name (application_info);
  
  interest_manager = unity_webapps_interest_manager_new();
  
  g_signal_connect (interest_manager, "became-lonely",
		    G_CALLBACK (on_manager_became_lonely),
		    daemon);
  /* There are some awkward races if we expose an interest before it has a XID
     i.e. BAMF isn't really prepared to handle the concept of a view without a window
     So we wait until the interest is 'ready' i.e. has a XID.
     the DBus side API should probably be updated to reflect this in 13.04 cycle. */
  g_signal_connect (interest_manager, "interest-ready",
		    G_CALLBACK (on_manager_interest_added),
		    daemon);
  g_signal_connect (interest_manager, "interest-removed",
		    G_CALLBACK (on_manager_interest_removed),
		    daemon);
  g_signal_connect (interest_manager, "active-changed",
		    G_CALLBACK (on_manager_active_changed),
		    daemon);
  g_signal_connect (interest_manager, "location-changed",
		    G_CALLBACK (on_manager_location_changed),
		    daemon);
  g_signal_connect (interest_manager, "window-changed",
		    G_CALLBACK (on_manager_window_changed),
		    daemon);

  {
    UnityWebappsWindowTracker*
      window_tracker;

    // TODO(aau): remove this and make a cleaner abstraction
    // Keep the #ifdef for now, just to make sure that it does not mess up with
    // "normal" code path.
#if defined(ENABLE_TESTS)
    if (!g_strcmp0(g_getenv("UNITY_WEBAPPS_USE_DBUS_CONTROLLABLE_WINDOW_TRACKING"), "yes"))
      {
        UNITY_WEBAPPS_NOTE (CONTEXT, "Using Dbus Controllable window tracker instance");

        window_tracker = unity_webapps_window_tracker_get_dbus_controllable(session_bus);
      }
    else
#endif
      {
        UNITY_WEBAPPS_NOTE (CONTEXT, "Using default window tracker instance");

        window_tracker = unity_webapps_window_tracker_get_default();
      }

    interest_tracker = unity_webapps_interest_tracker_new (window_tracker, interest_manager);
  }

  g_signal_connect (interest_tracker, "notify::most-recent-interest",
			   G_CALLBACK (unity_webapps_context_daemon_most_recent_interest_changed), daemon);
		    

  action_manager = unity_webapps_action_manager_new (interest_tracker, "/com/canonical/Unity/Webapps/Context/ApplicationActions");
  
  g_signal_connect (action_manager, "action-invoked",
		    G_CALLBACK (on_action_manager_action_activated),
		    daemon);

  daemon->notification_context = unity_webapps_notification_context_new (session_bus, name, icon_path);
  daemon->indicator_context = unity_webapps_indicator_context_new (session_bus, unity_webapps_application_info_get_desktop_file_path (application_info), canonical_name, interest_tracker);
  daemon->music_player_context = unity_webapps_music_player_context_new (session_bus, interest_manager, interest_tracker, daemon->desktop_file_name, canonical_name, name);
  daemon->launcher_context = unity_webapps_launcher_context_new (session_bus, daemon->desktop_file_name, interest_tracker, application_info);
  
  g_free (canonical_name);
  g_free (icon_path);

  return daemon;
}

/*
 * TODO: This probably leaks...but in the current implementation this means we are shutting down.
 * It would be useful to free everything just to aid memory debuggers.
 */
void
unity_webapps_context_daemon_free (UnityWebappsContextDaemon *daemon)
{
  // TODO: Can we come back after this?
  g_hash_table_destroy (daemon->interest_vanished_ids);
  g_hash_table_destroy (daemon->name_interest_counts);
  
  unity_webapps_notification_context_free (daemon->notification_context);
  unity_webapps_indicator_context_free (daemon->indicator_context);
  unity_webapps_music_player_context_free (daemon->music_player_context);
  unity_webapps_launcher_context_free (daemon->launcher_context);
  
  
  g_free (daemon->desktop_file_name);
  g_free (daemon->icon_file_name);
  g_free (daemon->icon_theme_dir);

  g_strfreev (daemon->dnd_mimes);

  g_free (daemon);
}

void
unity_webapps_context_daemon_emit_raise (GDBusConnection *connection, gint interest_id, const gchar * const *files)
{
  GError *error;
  
  if ((interest_id < 0) && (unity_webapps_interest_tracker_get_most_recent_interest (interest_tracker) > 0))
    {
      interest_id = unity_webapps_interest_tracker_get_most_recent_interest (interest_tracker);
    }

  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting Raise signal to interest: %d", interest_id);

  GVariantBuilder *builder;
  const gchar *const *it;
  builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
  if (files)
    for (it = files; *it; it++)
      {
        g_variant_builder_add (builder, "s", *it);
      }

  error = NULL;
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "RaiseRequested",
				 g_variant_new ("(ias)",
						interest_id,
            builder,
						NULL),
				 &error);

  g_variant_builder_unref (builder);

  if (error != NULL)
    {
      g_warning ("Error emitting raise signal in context: %s", error->message);
      
      g_error_free (error);

      return;
    }

}


void
unity_webapps_context_daemon_emit_close (GDBusConnection *connection, gint interest_id)
{
  GError *error;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting Raise signal to interest: %d", interest_id);
  
  error = NULL;
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "CloseRequested",
				 g_variant_new ("(i)",
						interest_id,
						NULL),
				 &error);
  
  if (error != NULL)
    {
      g_warning ("Error emitting close signal in context: %s", error->message);
      
      g_error_free (error);

      return;
    }

}

void 
unity_webapps_context_daemon_emit_view_is_active_changed (GDBusConnection *connection, gint interest_id, gboolean is_active)
{
  GError *error;

  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting ViewIsActiveChanged signal (interest %d %s)", interest_id,
		      is_active ? "is active" : "is not active");
  
  error = NULL;
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "ViewIsActiveChanged",
				 g_variant_new ("(ib)",
						interest_id,
						is_active,
						NULL),
				 &error);
  
  if (error != NULL)
    {
      g_warning ("Error emitting ViewIsActiveChanged signal in context: %s", error->message);
      
      g_error_free (error);

      return;
    }
}


void 
unity_webapps_context_daemon_emit_view_window_changed (GDBusConnection *connection, gint interest_id, guint64 window)
{
  GError *error;
    
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting ViewWindowChanged signal (interest %d has window %lu)", interest_id, (unsigned long) window);
  
  error = NULL;
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "ViewWindowChanged",
				 g_variant_new ("(it)",
						interest_id,
						window,
						NULL),
				 &error);
  
  if (error != NULL)
    {
      g_warning ("Error emitting ViewWindowChanged signal in context: %s", error->message);
      
      g_error_free (error);

      return;
    }
}

void 
unity_webapps_context_daemon_emit_view_location_changed (GDBusConnection *connection, gint interest_id, const gchar *location)
{
  GError *error;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting ViewLocationChanged signal (interest %d navigated to %s)",
		      interest_id, location);
  
  error = NULL;
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "ViewLocationChanged",
				 g_variant_new ("(is)",
						interest_id,
						location,
						NULL),
				 &error);
  
  if (error != NULL)
    {
      g_warning ("Error emitting ViewLocationChanged signal in context: %s", error->message);
      
      g_error_free (error);

      return;
    }
}

void 
unity_webapps_context_daemon_emit_preview_requested (GDBusConnection *connection, gint interest_id)
{
  GError *error;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting PreviewRequested signal to interest %d", interest_id);
  
  error = NULL;
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "PreviewRequested",
				 g_variant_new ("(i)",
						interest_id,
						NULL),
				 &error);
  
  if (error != NULL)
    {
      g_warning ("Error emitting PreviewRequested signal in context: %s", error->message);
      
      g_error_free (error);

      return;
    }
}


void 
unity_webapps_context_daemon_emit_action_invoked (GDBusConnection *connection, const gchar *name)
{
  GError *error;
  
  UNITY_WEBAPPS_NOTE (CONTEXT, "Emitting ApplicationActionInvoked signal (%s)", name);
  
  error = NULL;
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_CONTEXT_PATH,
				 UNITY_WEBAPPS_CONTEXT_IFACE,
				 "ApplicationActionInvoked",
				 g_variant_new ("(s)",
						name,
						NULL),
				 &error);
  
  if (error != NULL)
    {
      g_warning ("Error emitting ApplicationActionInvoked signal in context: %s", error->message);
      
      g_error_free (error);

      return;
    }
}
void
unity_webapps_context_daemon_raise_interest (gint interest_id)
{
  unity_webapps_context_daemon_emit_raise (session_bus, interest_id, NULL);
}

void 
unity_webapps_context_daemon_raise_most_recent() 
{
  unity_webapps_interest_tracker_raise_most_recent (interest_tracker, 0);
  unity_webapps_context_daemon_emit_raise (session_bus, -1, NULL);
}
