/*
 * unity-webapps-service.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/>.";
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#include <gio/gio.h>
#include <glib/gstdio.h>
#include <glib/gprintf.h>
#include <glib.h>

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

#include "unity-webapps-service-preinstalled-app.h"

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

#include "unity-webapps-debug.h"

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

#include "unity-webapps-app-db.h"

#define UNITY_WEBAPPS_SCHEMA_NAME "com.canonical.unity.webapps"
#define INDEX_UPDATE_TIME_KEY "index-update-time"
#define MINIMUM_UPDATE_TIME 60*60

static GMainLoop *mainloop = NULL;

guint context_ready_signal_subscription;
guint context_lost_interest_signal_subscription;

static gboolean replace_service = FALSE;

static GOptionEntry option_entries[] =
  {
    {"replace", 'r', 0, G_OPTION_ARG_NONE, &replace_service, "Replace running service", NULL},
    { NULL }
  };

static void
unity_webapps_service_spawn_index_updater ()
{
  GError *error;
  
  if (!g_strcmp0(g_getenv("UNITY_WEBAPPS_TESTS_DISABLE_INDEX_UPDATER"), "yes"))
    {
      UNITY_WEBAPPS_NOTE (SERVICE, "Index updater run explicitly disabled");
      return;
    }

  error = NULL;
  g_spawn_command_line_async ("ubuntu-webapps-update-index", &error);
  
  if (error != NULL)
    {
      g_critical ("Failed to spawn ubuntu-webapps-update-index: %s", error->message);
      g_error_free (error);
    }

}

static void
unity_webapps_service_emit_context_appeared (GDBusConnection *connection,
					     const gchar *context)
{
  GError *error;
  
  error = NULL;
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Emitting ContextAppeared signal for %s", context);
  
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_SERVICE_PATH,
				 UNITY_WEBAPPS_SERVICE_IFACE,
				 "ContextAppeared",
				 g_variant_new ("(s)", context, NULL),
				 &error);
  
  if (error != NULL)
    {
      g_warning ("Error emiting ContextAppeared signal in service: %s", error->message);
      
      g_error_free (error);
      
      return;
    }
}

static void
unity_webapps_service_emit_context_vanished (GDBusConnection *connection,
					     const gchar *context)
{
  GError *error;
  
  error = NULL;
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Emitting ContextVanished signal for %s", context);
  
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_SERVICE_PATH,
				 UNITY_WEBAPPS_SERVICE_IFACE,
				 "ContextVanished",
				 g_variant_new ("(s)", context, NULL),
				 &error);
  
  if (error != NULL)
    {
      g_warning ("Error emiting ContextVanished signal in service: %s", error->message);
      
      g_error_free (error);
      
      return;
    }
}

static void
add_pending_ready (UnityWebappsService *service, const gchar *id, GDBusMethodInvocation *invocation)
{
  GList *invocations;
  
  invocations = g_hash_table_lookup (service->pending_ready, id);
  
  invocations = g_list_append (invocations, g_object_ref (G_OBJECT (invocation)));
  
  g_hash_table_replace (service->pending_ready, g_strdup (id), invocations);
}

static void
remove_pending_ready (UnityWebappsService *service, const gchar *id)
{
  GList *invocations, *walk;
  
  invocations = g_hash_table_lookup (service->pending_ready, id);
  
  for (walk = invocations; walk != NULL; walk = walk->next)
    {
      //      GDBusMethodInvocation *invocation;
      
      //      invocation = (GDBusMethodInvocation *)walk->data;

      // TODO : Whats happening here?
      //      g_object_unref (G_OBJECT (invocation));
    }
  
  g_list_free (invocations);

  g_hash_table_remove (service->pending_ready, id);
}

UnityWebappsService *
unity_webapps_service_new ()
{
  UnityWebappsService *service;
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Initializing Service object");
  
  service = g_malloc0 (sizeof (UnityWebappsService));
  
  service->contexts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
  service->pending_ready = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  service->lonely_daemons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  
  return service;
}

void
unity_webapps_service_destroy (UnityWebappsService *service)
{
  g_hash_table_destroy (service->contexts);
  g_hash_table_destroy (service->pending_ready);
  g_hash_table_destroy (service->lonely_daemons);
  
  g_free (service);
}

#ifdef UNITY_WEBAPPS_ENABLE_DEBUG

static gchar *
get_context_log_file_name (const gchar *name)
{
  gchar *log_file_name;
  time_t current_time;
  
  current_time = time (NULL);
  
  log_file_name = g_strdup_printf("./%s-%lu", name, (unsigned long) current_time);
  
  return log_file_name;
}

void
child_setup (gpointer user_data)
{
  gchar *log_name;
  int fd;
  
  return;
  
  if (g_getenv ("UNITY_WEBAPPS_CONTEXT_USE_LOG_FILES") == NULL)
    {
      return;
    }
  
  log_name = (gchar *)user_data;

  fd = open (log_name, O_WRONLY | O_CREAT, 0666);
  
  dup2 (fd, STDOUT_FILENO);
  dup2 (fd, STDERR_FILENO);
  
  close (fd);
}

#endif

static gchar **
unity_webapps_service_get_context_daemon_argv (const gchar *name,
					       const gchar *domain,
					       const gchar *icon_url,
					       const gchar *mime_types)
{
  gchar **argv;
  const gchar *execdir;
  const gchar *preinstalled_desktop;
  
  execdir = g_getenv("UNITY_WEBAPPS_DAEMON_EXEC_DIR");
  
  if (execdir == NULL)
    {
      execdir = PACKAGE_LIBEXEC_DIR;
    }
  
  preinstalled_desktop = unity_webapps_service_get_preinstalled_app_desktop_id (name, domain);

  if (preinstalled_desktop == NULL)
    {
      argv = g_malloc (6 * sizeof (gchar *));
    }
  else
    {
      argv = g_malloc (7 * sizeof (gchar *));
    }

  argv[0] = g_strdup_printf("%s/unity-webapps-context-daemon", execdir);
  argv[1] = g_strdup (name);
  argv[2] = g_strdup (domain);
  argv[3] = icon_url ? g_strdup (icon_url) : g_strdup ("none");
  argv[4] = mime_types ? g_strdup (mime_types) : g_strdup("none");
  if (preinstalled_desktop == NULL)
    {
      argv[5] = NULL;
    }
  else
    {
      argv[5] = g_strdup (preinstalled_desktop);
      argv[6] = NULL;
    }
  
  return argv;
}

static gboolean
spawn_context_daemon (const gchar *name,
		      const gchar *domain,
		      const gchar *icon_url,
		      const gchar *mime_types,
		      GError **error)
{
  gchar **argv;
  gboolean ret;
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Spawning context daemon %s %s", name, domain);

  argv = unity_webapps_service_get_context_daemon_argv (name, domain, icon_url, mime_types);

  *error = NULL;
  ret = TRUE;

#ifdef UNITY_WEBAPPS_ENABLE_DEBUG  
  g_spawn_async_with_pipes (NULL, argv, NULL,
			    0, NULL,
			    get_context_log_file_name (name), NULL, NULL, NULL, NULL, error);
#else 
  g_spawn_async_with_pipes (NULL, argv, NULL,
			    0, NULL,
			    NULL, NULL, NULL, NULL, NULL, error);
#endif
  
  
  if (*error != NULL)
    {
      g_critical("Error spawning context daemon: %s", (*error)->message);
      
      ret = FALSE;
      
      goto out;
      
    }
  

 out:

  g_strfreev (argv);
  
  return ret;
}

static UnityWebappsGenContext *
make_context_proxy (GDBusConnection *connection,
		    const gchar *context_name)
{
  UnityWebappsGenContext *context_proxy;
  GError *error;
  
  error = NULL;

  UNITY_WEBAPPS_NOTE (SERVICE, "Creating context proxy for %s", context_name);
  
  context_proxy = unity_webapps_gen_context_proxy_new_sync (connection,
						      G_DBUS_PROXY_FLAGS_NONE,
						      context_name,
						      UNITY_WEBAPPS_CONTEXT_PATH,
						      NULL /* Cancellable */,
						      &error);
  
  if (error != NULL)
    {
      g_critical ("Couldn't create proxy for interesting context (%s): %s", context_name, error->message);
      g_error_free (error);
      
      return NULL;
    }
  
  return context_proxy;
}

typedef struct _context_vanished_user_data {
  UnityWebappsService *service;
  guint watcher_id;
} context_vanished_user_data;

static void
context_name_vanished (GDBusConnection *connection,
		       const gchar *name,
		       gpointer user_data)
{
  context_vanished_user_data *data= (context_vanished_user_data *)user_data;
  GHashTableIter iter;
  gpointer key, value;
  guint timeout_id;
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Watched context (%s) vanished", name);
  
  if ((timeout_id = GPOINTER_TO_UINT (g_hash_table_lookup (data->service->lonely_daemons, name))) != 0)
    {
      gboolean removed = FALSE;
      
      UNITY_WEBAPPS_NOTE (SERVICE, "Lost context (%s) was slated to be killed, removing it's timeout", name);

      removed = g_source_remove (timeout_id);
      
      if (removed == FALSE)
	{
	  g_warning ("Failed to remove timeout_id (%u) when context vanished (%s)", timeout_id, name);
	}
      
      g_hash_table_remove (data->service->lonely_daemons, name);
    }
  
  g_hash_table_iter_init (&iter, data->service->contexts);
  
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      if (g_strcmp0(value, name) == 0)
	{
	  UNITY_WEBAPPS_NOTE (SERVICE, "Unregistering context: %s", (gchar *)key);
	  g_hash_table_remove(data->service->contexts, key);

	  unity_webapps_service_emit_context_vanished (connection, name);
	  
	  g_bus_unwatch_name (data->watcher_id);
	  
	  return;
	}
    }
  

}

static void
clear_queue (GHashTable *pending_ready, GError *error)
{
  GHashTableIter iter;
  gpointer key, value;
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Clearing queue of pending invocations (from GetContext)");
  
  g_hash_table_iter_init (&iter, pending_ready);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      GList *invocations, *walk;
      
      invocations = (GList *)value;
      
      for (walk = invocations; walk != NULL; walk = walk->next)
	{
	  GDBusMethodInvocation *invocation = (GDBusMethodInvocation *)walk->data;
	  
	  g_assert (invocation != NULL);
	  
	  g_dbus_method_invocation_return_value (invocation, NULL);
	  
	  g_object_unref (G_OBJECT (invocation));
	}
    }
  g_hash_table_remove_all (pending_ready);
}

static void
register_proxy (UnityWebappsService *service,
		GDBusConnection *connection,
		const gchar *sender,
		const gchar *object)
{
 GDBusMethodInvocation *invocation;
 GList *invocations, *walk;
 UnityWebappsGenContext *context_proxy;
 const gchar *domain;
 const gchar *name;
 gchar *id;

 UNITY_WEBAPPS_NOTE (SERVICE, "Registering context proxy for context at %s", sender);

 context_proxy = 
   make_context_proxy (connection, sender);
 
 domain = unity_webapps_gen_context_get_domain (context_proxy);
 name = unity_webapps_gen_context_get_name (context_proxy);
 
 if ((name == NULL) || (domain == NULL))
   {
     printf("Error getting Name and Domain of proxy\n");
     
     clear_queue (service->pending_ready, NULL);

     return;
   }
 
 UNITY_WEBAPPS_NOTE(SERVICE, "Found context information for %s, (%s, %s)", sender, name, domain);
 
 id = g_strdup_printf("%s%s", name, domain);
 
 
 invocations = (GList *)g_hash_table_lookup(service->pending_ready, id);
 
 for (walk = invocations; walk != NULL; walk = walk->next)
   {
     
     invocation = (GDBusMethodInvocation *)walk->data;
     
     if (invocation != NULL)
       {
	 
	 UNITY_WEBAPPS_NOTE (SERVICE, "Found pending invocation for Context Daemon (%s, %s), Context at %s is ready for %s", name, domain, g_dbus_method_invocation_get_sender (invocation), sender);
	 
	 g_dbus_method_invocation_return_value (invocation, g_variant_new("(ss)", sender, UNITY_WEBAPPS_WIRE_PROTOCOL_VERSION));

       }
   }
 
 remove_pending_ready (service, id);
 
 unity_webapps_service_emit_context_appeared (connection, sender);
 
 g_hash_table_insert (service->contexts, id, g_strdup (sender));
 
 g_object_unref (G_OBJECT (context_proxy));
}

static void
bus_filter_context_ready (GDBusConnection *connection,
			  const gchar *sender,
			  const gchar *object,
			  const gchar *interface,
			  const gchar *signal,
			  GVariant *params,
			  gpointer user_data)
{
  UnityWebappsService *service = (UnityWebappsService *)user_data;
  context_vanished_user_data *data;
  const gchar *wire_version;
  guint watcher_id;
 

  if (g_strcmp0(signal, "ContextReady") != 0) 
    {
      g_warning ("Unexpected signal for webapps service: %s", signal);
      return;
    }
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Handling ContextReady signal from %s", sender);
  
  g_variant_get (params, "(s)", &wire_version, NULL);
  
  if (g_strcmp0 (wire_version, UNITY_WEBAPPS_WIRE_PROTOCOL_VERSION) != 0)
    {
      // TODO: FIXME: Should we ask for shutdown here?
      g_critical ("Wire protocol verison mismatch from service version %s to context version %s, ignoring ContextReady signal",
		  UNITY_WEBAPPS_WIRE_PROTOCOL_VERSION, wire_version);
      
      return;
    }
  
  data = g_malloc0 (sizeof (context_vanished_user_data));
  data->service = service;

  watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
		    sender,
		    G_BUS_NAME_WATCHER_FLAGS_NONE,
		    NULL,
		    context_name_vanished,
		    data /* User data*/,
		    g_free /* User data free*/);
  
  data->watcher_id = watcher_id;
  
  register_proxy (service, connection, sender, object);
  
}

typedef struct _context_lost_interest_user_data {
  GDBusConnection *connection;
  gchar *sender;
  UnityWebappsService *service;
} context_lost_interest_user_data;

static void 
context_lost_interest_user_data_free (gpointer p)
{
  context_lost_interest_user_data *data = (context_lost_interest_user_data *)p;
  
  // Sender is managed by the hash table.
  g_free (data->sender);
  g_object_unref(data->connection);
  
  g_free(data);
}

static gint
context_proxy_get_interest_count (UnityWebappsGenContext *context)
{
  gint interest_count;
  GError *error;
  
  error = NULL;
  
  unity_webapps_gen_context_call_get_interest_count_sync (context, &interest_count,
							  NULL /* Cancellable */,
							  &error);
  
  if (error != NULL)
    {
      g_critical ("Error calling GetInterestCount: %s", error->message);
      
      g_error_free (error);
      
      interest_count = 0;
    }

  return interest_count;
}

static gboolean
kill_lonely_daemon (gpointer user_data)
{
  context_lost_interest_user_data *data;
  UnityWebappsGenContext *context_proxy;
  gint interest_count;
  GError *error;
  
  data = (context_lost_interest_user_data *)user_data;
  
  error = NULL;

  context_proxy = 
    make_context_proxy (data->connection, data->sender);
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Killing lonely context at: %s", data->sender);

  if (context_proxy == NULL) 
    {
      g_hash_table_remove(data->service->lonely_daemons, data->sender);
      
      return FALSE;
    }
  
  interest_count = context_proxy_get_interest_count (context_proxy);
  
  if (interest_count != 0)
    {
      UNITY_WEBAPPS_NOTE (SERVICE, "Lonely context at %s got an interest. Removing Context from kill list", data->sender);
      g_hash_table_remove(data->service->lonely_daemons, data->sender);
      
      return FALSE;
    }
  
  unity_webapps_gen_context_call_shutdown_sync (context_proxy,
						NULL /* Cancellable */,
						&error);
  if (error != NULL)
    {
      g_warning ("Failed to call shutdown on lonely daemon (%s), treating it as dead: %s", data->sender, error->message);

      g_error_free (error);
      g_object_unref (G_OBJECT (context_proxy));
      g_hash_table_remove(data->service->lonely_daemons, data->sender);
      
      return FALSE;
    }

  g_object_unref (G_OBJECT (context_proxy));
  g_hash_table_remove(data->service->lonely_daemons, data->sender);

  return FALSE;
}

static void
bus_filter_context_lost_interest (GDBusConnection *connection,
				  const gchar *sender,
				  const gchar *object,
				  const gchar *interface,
				  const gchar *signal,
				  GVariant *params,
				  gpointer user_data)
{
  UnityWebappsService *service = (UnityWebappsService *)user_data;
  context_lost_interest_user_data *data;
  gpointer existing_data;
  gboolean user_abandoned;
  guint timeout_id;
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Context (%s) has no interest", sender);
  
  existing_data = g_hash_table_lookup (service->lonely_daemons, sender);
  
  if (existing_data != NULL)
    {
      UNITY_WEBAPPS_NOTE (SERVICE, "Context (%s) was already slated to be killed, pushing back the sentence", sender);
      timeout_id = GPOINTER_TO_UINT (existing_data);
      
      g_source_remove (timeout_id);
      
      g_hash_table_remove (service->lonely_daemons, sender);
    }
  data = g_malloc0 (sizeof (context_lost_interest_user_data));

  data->sender = g_strdup (sender);
  data->connection = g_object_ref(connection);
  data->service = service;
  
  // perhaps kill the daemon without the 15 second timeout.
  g_variant_get (params, "(b)", &user_abandoned, NULL);
  if (user_abandoned)
  {
    UNITY_WEBAPPS_NOTE(SERVICE, "User abandoned the lonely daemon");
    timeout_id = g_idle_add_full (G_PRIORITY_LOW, kill_lonely_daemon, data, context_lost_interest_user_data_free);
  }
  else
  { 
    timeout_id = g_timeout_add_seconds_full (G_PRIORITY_LOW, 15, kill_lonely_daemon, data, context_lost_interest_user_data_free);
  }
  
  g_hash_table_insert (service->lonely_daemons, g_strdup (data->sender), GUINT_TO_POINTER(timeout_id));
}

static gboolean
on_handle_get_context (UnityWebappsGenService *service_interface,
		       GDBusMethodInvocation *invocation,
		       const gchar *name,
		       const gchar *domain,
		       const gchar *icon_url,
		       const gchar *mime_types,
		       gpointer user_data)
{
  UnityWebappsService *service;
  const gchar *context;
  gchar *id;
  GError *error;

  service = (UnityWebappsService *)user_data;  
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Handling GetContext call for (%s, %s", 
		      name, domain);
  
  id = g_strdup_printf("%s%s", name, domain);
  
  context = g_hash_table_lookup (service->contexts, id);
  
  if (context != NULL)
    {
      UNITY_WEBAPPS_NOTE (SERVICE, "Found existing context for (%s, %s): %s", name, domain, context);
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ss)", context, UNITY_WEBAPPS_WIRE_PROTOCOL_VERSION));
    }
  else
    {
      UNITY_WEBAPPS_NOTE (SERVICE, "No existing context for (%s, %s)", name, domain);
     
      if (g_hash_table_lookup (service->pending_ready, id) != NULL)
	{
	  UNITY_WEBAPPS_NOTE (SERVICE, "Found existing pending invocation for Context (%s, %s)", name, domain);
	  add_pending_ready (service, id, invocation);
	}
      else
	{
	  
	  error = NULL;
	  spawn_context_daemon (name, domain, icon_url, mime_types, &error);
	  
	  if (error != NULL)
	    {
	      g_warning("Couldn't spawn context daemon for %s (%s)", name, domain);
	      g_dbus_method_invocation_return_gerror (invocation, error);
	      g_error_free (error);
	    }
	  else
	    {
	      UNITY_WEBAPPS_NOTE (SERVICE, "Waiting for context (%s, %s) to send ContextReady", name, domain);
	      add_pending_ready (service, id, invocation);
	    }
	}
    }
  
  
  g_free (id);
  
  return TRUE;
}

#if 0

static gchar*
_firefox_profile_get_name_for_app (const gchar *homepage)
{
  gchar *res = g_base64_encode ((guchar *)homepage, strlen (homepage) + 1);

  return res;
}

static GFile*
_firefox_webapps_profile_get_location (GFile *profiles,
				       const gchar *id)
{
  GFile *res = NULL;
  gchar *profiles_path = g_file_get_path (profiles);

  GDir *dir = g_dir_open (profiles_path, 0, NULL);

  if (dir)
    {
      for (const gchar *name = g_dir_read_name (dir); name; name = g_dir_read_name (dir))
	{
	  if (g_str_has_suffix (name, id))
	    {
	      res = g_file_get_child (profiles, name);
	      if (g_file_query_file_type (res, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY)
		{
		  g_object_unref (res);
		  res = NULL;
		}
	      else
		{
		  break;
		}
	    }
	}

      g_dir_close (dir);
    }

  g_free (profiles_path);

  return res;
}

static GFile*
_firefox_default_profile_get_location (GFile *dir)
{
  gchar **i;
  GFile *res = NULL;

  GFile *profiles_file = g_file_get_child (dir, "profiles.ini");
  GKeyFile *profiles = g_key_file_new ();
  gchar *profiles_file_path = g_file_get_path (profiles_file);
  if (g_key_file_load_from_file (profiles, profiles_file_path, G_KEY_FILE_NONE, NULL))
    {
      gchar **groups = g_key_file_get_groups (profiles, NULL);

      for (i = groups; i && *i && !res; i++)
	{
	  if (g_str_has_prefix (*i, "Profile"))
	    {
	      if (g_key_file_get_integer (profiles, *i, "Default", NULL))
		{
		  gchar *path = g_key_file_get_string (profiles, *i, "Path", NULL);
		  if (g_key_file_get_integer (profiles, *i, "IsRelative", NULL))
		    {
		      res = g_file_get_child (dir, path);
		    }
		  else
		    {
		      res = g_file_new_for_path (path);
		    }

		  g_free (path);
		  if (g_file_query_file_type (res, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY)
		    {
		      g_object_unref (res);
		      res = NULL;
		    }

		  break;
		}
	    }
	}
      g_strfreev (groups);
    }

  g_free (profiles_file_path);
  g_key_file_unref (profiles);
  g_object_unref (profiles_file);

  return res;
}

static gboolean
_firefox_profile_create (const gchar *id,
			 GFile *dir)
{
  gboolean res = FALSE;
  GFile *default_dir = NULL;
  gchar *default_extensions_path = NULL;
  gchar *default_dir_path = NULL;
  gchar *res_dir_name = NULL, *res_dir_path = NULL;

  default_dir = _firefox_default_profile_get_location (dir);

  if (!default_dir)
    goto out;

  default_dir_path = g_file_get_path (default_dir);

  res_dir_name = g_strdup_printf ("%d.%s", g_random_int() % 10000000, id);
  res_dir_path = g_strdup_printf ("%s/%s", g_file_get_path(dir), res_dir_name);

  if (g_mkdir (res_dir_path, 0700) != 0)
    goto out;

#define create_link(name, filename)					\
  gchar *tmp##name = g_strconcat (res_dir_path, filename, NULL);	\
  GFile *file##name = g_file_new_for_path (tmp##name);		\
  g_free (tmp##name);							\
  tmp##name = g_strconcat (default_dir_path, filename, NULL);		\
  if (!g_file_make_symbolic_link (file##name, tmp##name, NULL, NULL))	\
    {									\
      g_free (tmp##name); g_object_unref (file##name);			\
      goto out;\
    }								\
  g_free (tmp##name); g_object_unref (file##name);		\

  create_link (cookies, "/cookies.sqlite");

#ifdef UNITY_WEBAPPS_ENABLE_DEBUG
  default_extensions_path = g_strdup_printf ("%s/extensions", default_dir_path);
  if (!g_file_test (default_extensions_path, G_FILE_TEST_IS_DIR))
    {
      if (g_mkdir (res_dir_path, 0700) != 0)
	goto out;
    }

  create_link (extensions, "/extensions");
  create_link (extensions_ini, "/extensions.ini");
#endif

#undef create_link
  res = TRUE;
 out:
  g_free (default_extensions_path);
  g_free (res_dir_name);
  g_free (res_dir_path);
  g_free (default_dir_path);
  g_object_unref (default_dir);

  return res;
}

static GFile*
_firefox_get_profiles_dir (void)
{
  gchar *tmp = g_strdup_printf ("%s/.mozilla/firefox/", g_get_home_dir ());
  GFile *res = g_file_new_for_path (tmp);

  g_free (tmp);

  return res;
}

static gchar*
_firefox_get_profile_for_app (const gchar *homepage)
{
  gchar *res = NULL;

  GFile *dir = _firefox_get_profiles_dir ();

  gchar *id = _firefox_profile_get_name_for_app (homepage);
  GFile *location = _firefox_webapps_profile_get_location (dir, id);
  if (location)
    {
      res = g_file_get_path (location);
    }
  else
    {
      if (_firefox_profile_create (id, dir))
	{
	  location = _firefox_webapps_profile_get_location (dir, id);
	  res = g_file_get_path (location);
	}
    }
  g_free (id);

  g_object_unref (dir);
  g_object_unref (location);

  return res;
}
#endif

/**
 *
 * @return command line format, have to be freed
 */
static char *
get_browser_command_line_format (const gchar *homepage)
{
  static const char * URI_SCHEME_HTTP = "http";

  GAppInfo * info = g_app_info_get_default_for_uri_scheme (URI_SCHEME_HTTP);
  g_return_val_if_fail (NULL != info, NULL);

  const char * exe_name = g_app_info_get_executable (info);
  g_return_val_if_fail (NULL != exe_name, NULL);

  if (g_str_has_suffix (exe_name, "firefox"))
    {
#if 0
      gchar *profile_path = _firefox_get_profile_for_app (homepage);

      if (profile_path)
	{
	  return g_strdup_printf ("%s --chromeless -no-remote -profile %s '%%s'", exe_name, profile_path);
        }
#endif
      return g_strdup_printf ("%s --new-window '%%s'", exe_name);
    }
  return g_strdup_printf ("%s --new-window --app='%%s'", exe_name);
}

/**
 *
 * @return command line, have to be freed
 */
static char * 
format_browser_commandline_for_homepage (const char * const format, const char * const homepage)
{
  return g_strdup_printf (format, homepage);
}

static gboolean
unity_webapps_service_launch_browser_with_homepage (const gchar *homepage)
{
  gchar *commandline_format = NULL;
  gchar *commandline = NULL;
  gboolean ret = FALSE;
  GError *error;
  
  commandline_format = get_browser_command_line_format (homepage);
  if (commandline_format == NULL)
    {
      g_critical ("Error could not retrieve the browser command line format");
      goto out;
    }

  commandline = format_browser_commandline_for_homepage (commandline_format, homepage);
  if (commandline == NULL)
    {
      g_critical ("Error could not retrieve the browser command line");
      goto out;
    }

  UNITY_WEBAPPS_NOTE (SERVICE, "Invoking Browser (in response to ActivateApplication): %s", commandline);

  error = NULL;
  g_spawn_command_line_async (commandline, &error);
  
  ret = TRUE;

  if (error != NULL)
    {
      g_critical ("Error invoking browser: %s", error->message);
      g_error_free (error);
      
      ret = FALSE;
    }

 out:
  g_free (commandline);
  g_free (commandline_format);

  return ret;
}

static gboolean
run_application (const gchar *name,
		 const gchar *domain)
{
  gchar *homepage;
  gboolean success;
  
  homepage = unity_webapps_app_db_get_homepage (name, domain);
  if (homepage == NULL)
    homepage = g_strdup_printf("http://%s", domain);
  
  success = unity_webapps_service_launch_browser_with_homepage (homepage);
  g_free (homepage);
  
  return success;
}


static gboolean
on_handle_activate_application (UnityWebappsGenService *service_interface,
				GDBusMethodInvocation *invocation,
				const gchar *name,
				const gchar *domain,
				const gchar * const *files,
				gpointer user_data)
{
  GDBusConnection *connection;
  UnityWebappsService *service;
  const gchar *context, *lonely_context;
  gchar *id;

  service = (UnityWebappsService *)user_data;
  connection = g_dbus_method_invocation_get_connection (invocation);
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Handling ActivateApplication call: (%s, %s)", name, domain);
  
  id = g_strdup_printf("%s%s", name, domain);
  
  lonely_context = NULL;

  context = g_hash_table_lookup (service->contexts, id);
  if (context != NULL)
    lonely_context = g_hash_table_lookup (service->lonely_daemons, context);

  // TODO: When we activate a lonely context maybe we should extend its death to give the page time to load.

  if ((context != NULL) && (lonely_context == NULL))
    {
      UnityWebappsGenContext *context_proxy;
      GError *error;
      
      context_proxy = make_context_proxy (connection, context);
      
      error = NULL;
	  
      unity_webapps_gen_context_call_raise_sync (context_proxy,
                                                 files,
						 NULL /* Cancellable */,
						 &error);
      
      if (error != NULL)
	{
	  g_warning ("Error calling DoRaise method of proxy (%s): %s", context, error->message);
	  g_error_free (error);
	}
      
      g_object_unref (G_OBJECT (context_proxy));
      
      g_dbus_method_invocation_return_value (invocation, NULL);
    }
  else
    {
      gchar *id;
      run_application (name, domain);
      
      id = g_strdup_printf("%s%s", name, domain);
      
      //      add_pending_ready (service, id, invocation);
      g_dbus_method_invocation_return_value (invocation, NULL);
      
      g_free (id);
    }
  
  return TRUE;
}

static gboolean
on_destroy_interest_for_context (UnityWebappsGenService *service_interface,
                                 GDBusMethodInvocation *invocation,
                                 const gchar *name,
                                 const gchar *domain,
                                 gint interest_id,
                                 gboolean abandoned,
                                 gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (SERVICE, "Handling destroy interest call for (%s, %s) - id %d (abandoned %d)"
                      , name, domain
                      , interest_id
                      , abandoned);

  UnityWebappsService* service = (UnityWebappsService *)user_data;

  GDBusConnection* connection =
    g_dbus_method_invocation_get_connection (invocation);

  gchar * id = g_strdup_printf("%s%s", name, domain);

  const gchar *context = g_hash_table_lookup (service->contexts, id);
  if (context != NULL)
    {
      UNITY_WEBAPPS_NOTE (SERVICE, "Found existing context for (%s, %s) %s, sending request"
                          , name, domain, context);

      UnityWebappsGenContext* context_proxy =
        make_context_proxy (connection, context);

      // dispatch & fallback on lost-interest event w/ abandoned semantics
      GError *error = NULL;
      unity_webapps_gen_context_call_lost_interest_sync (context_proxy,
                                                         interest_id,
                                                         abandoned,
                                                         NULL /* Cancellable */,
                                                          &error);
      
      if (error != NULL)
	{
	  g_warning ("Error calling LostInterest method of proxy (%s): %s"
                     , context
                     , error->message);
	  g_error_free (error);
	}
      g_object_unref (G_OBJECT (context_proxy));

      g_dbus_method_invocation_return_value (invocation, NULL);
    }
  else
    {
      g_warning("Error handling on destroy interest: context not found (%s, %s)"
                , name, domain);
    }

  g_free(id);

  return TRUE;
}

static gboolean
on_handle_list_contexts (UnityWebappsGenService *service_interface,
			 GDBusMethodInvocation *invocation,
			 gpointer user_data)
{
  GVariant *ret;
  GVariantBuilder *builder;
  UnityWebappsService *service;
  GHashTableIter iter;
  gpointer key, value;



  service = (UnityWebappsService *)user_data;
  
  builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
  
  g_hash_table_iter_init (&iter, service->contexts);
  
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      g_variant_builder_add_value (builder, g_variant_new_string ((const gchar *)value));
    }
  
  if (g_hash_table_size (service->contexts) == 0)
    {
      g_variant_builder_add_value (builder, g_variant_new_string (""));
    }
  
  ret = g_variant_builder_end (builder);
  
  g_dbus_method_invocation_return_value (invocation, g_variant_new_tuple (&ret, 1));
  
  g_variant_builder_unref (builder);
  
  return TRUE;
}

static gboolean
on_handle_open_homepage (UnityWebappsGenService *service,
			 GDBusMethodInvocation *invocation,
			 const gchar *homepage,
			 gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (SERVICE, "Handling OpenHomepage call");
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  unity_webapps_service_launch_browser_with_homepage (homepage);
  
  return TRUE;
}

static gboolean
on_handle_shutdown (UnityWebappsGenService *service,
		    GDBusMethodInvocation *invocation,
		    gpointer user_data)
{
  UNITY_WEBAPPS_NOTE (SERVICE, "Handling Shutdown call");
  
  g_dbus_method_invocation_return_value (invocation, NULL);
  g_dbus_connection_flush_sync (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL), NULL,
				NULL);
  
  exit(0);
  
  return TRUE;
}

static void
export_object (GDBusConnection *connection, UnityWebappsService *service)
{
  UnityWebappsGenService *service_interface;
  GError *error;

  service_interface = unity_webapps_gen_service_skeleton_new ();
  
  g_signal_connect (service_interface,
		    "handle-get-context",
		    G_CALLBACK (on_handle_get_context),
		    service);
  
  g_signal_connect (service_interface,
		    "handle-activate-application",
		    G_CALLBACK (on_handle_activate_application),
		    service);
  
  g_signal_connect (service_interface,
		    "handle-open-homepage",
		    G_CALLBACK (on_handle_open_homepage),
		    service);
  
  g_signal_connect (service_interface,
		    "handle-list-contexts",
		    G_CALLBACK (on_handle_list_contexts),
		    service);
  g_signal_connect (service_interface,
		    "handle-shutdown",
		    G_CALLBACK (on_handle_shutdown),
		    service);
  
  g_signal_connect (service_interface,
		    "handle-destroy-interest-for-context",
		    G_CALLBACK (on_destroy_interest_for_context),
		    service);


  error = NULL;
  
  g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (service_interface),
				    connection,
				    UNITY_WEBAPPS_SERVICE_PATH,
				    &error);
  
  if (error != NULL)
    {
      g_error ("Error exporting Unity Webapps Service object: %s", error->message);
      
      g_error_free (error);
      
      return;
    }
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Exported Service interface");
  
  
}

static void
on_bus_acquired (GDBusConnection *connection,
		 const gchar *name,
		 gpointer user_data)
{
  UnityWebappsService *service;
  GError *error;
  
  service = (UnityWebappsService *)user_data;
  
  export_object (connection, service);
  
  error = NULL;

  context_ready_signal_subscription = 
    g_dbus_connection_signal_subscribe (connection,
					NULL,
					UNITY_WEBAPPS_CONTEXT_IFACE,
					"ContextReady",
					NULL,
					NULL,
					G_DBUS_SIGNAL_FLAGS_NONE,
					bus_filter_context_ready,
					service /* user data*/,
					NULL /* destroy notify */);

  context_lost_interest_signal_subscription = 
    g_dbus_connection_signal_subscribe (connection,
					NULL,
					UNITY_WEBAPPS_CONTEXT_IFACE,
					"NoInterest",
					NULL,
					NULL,
					G_DBUS_SIGNAL_FLAGS_NONE,
					bus_filter_context_lost_interest,
					service /* user data*/,
					NULL /* destroy notify */);
  
  g_dbus_connection_emit_signal (connection,
				 NULL,
				 UNITY_WEBAPPS_SERVICE_PATH,
				 UNITY_WEBAPPS_SERVICE_IFACE,
				 "ServiceReady",
				 NULL /* Params*/,
				 &error /* Error */);
  
  if (error != NULL)
    {
      g_error ("Can not emit signal ServiceReady on com.canonical.Unity.Webapps.Service: %s",
	       error->message);
      g_error_free (error);
      
      return;
    }
}

static void
on_name_acquired (GDBusConnection *connection,
		  const gchar *name,
		  gpointer user_data)
{
}

static void
on_name_lost (GDBusConnection *connection,
	      const gchar *name,
	      gpointer user_data)
{
  g_main_loop_quit (mainloop);
}

static void
replace_existing_service ()
{
  GDBusConnection *connection;
  UnityWebappsGenService *service_proxy;
  GError *error;

  UNITY_WEBAPPS_NOTE (SERVICE, "Shutting down existing service");
  
  error = NULL;
  
  connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
			       NULL /* Cancellable */,
			       &error);
  
  if (error != NULL)
    {
      g_error ("Error fetching session bus to shut down existing service: %s", error->message);
      
      g_error_free (error);
      
      return;
    }
  
  service_proxy = unity_webapps_gen_service_proxy_new_sync (connection,
							    G_DBUS_PROXY_FLAGS_NONE,
							    "com.canonical.Unity.Webapps.Service",
							    "/com/canonical/Unity/Webapps/Service",
							    NULL /* Cancellable */,
							    &error);
  
  if (error != NULL)
    {
      g_critical ("Couldn't create proxy for existing service: %s", error->message);
      g_error_free (error);

      return;
    }
  
  unity_webapps_gen_service_call_shutdown_sync (service_proxy,
						NULL /* Cancellable */,
						&error);
  
  if (error != NULL)
    {
      g_critical ("Failed to call shutdown method for service: %s", error->message);
      g_error_free (error);
    }
  
  g_object_unref (G_OBJECT (service_proxy));

  return;  
}

static gboolean
indexer_spawn_callback (gpointer user_data)
{
  unity_webapps_service_spawn_index_updater ();
  return TRUE;
}

static gint
unity_webapps_service_get_index_updater_timeout ()
{
  GSettings *settings;
  gint time;
  
  settings = g_settings_new (UNITY_WEBAPPS_SCHEMA_NAME);
  
  time = g_settings_get_int (settings, INDEX_UPDATE_TIME_KEY);
  
  g_object_unref (G_OBJECT (settings));
  
  if (time < MINIMUM_UPDATE_TIME)
    {
      time = MINIMUM_UPDATE_TIME;
    }
  
  return time;
}

gint 
main (gint argc, gchar **argv)
{
  GOptionContext *context;
  UnityWebappsService *service;
  GError *error;
  guint owner_id;
  
  g_type_init();
  
#ifdef UNITY_WEBAPPS_ENABLE_DEBUG
  unity_webapps_debug_initialize_flags ();
#endif
  
  context = g_option_context_new ("- Unity Webapps Service Daemon");
  
  g_option_context_add_main_entries (context, option_entries, NULL);
  
  error = NULL;
  if (!g_option_context_parse (context, &argc, &argv, &error))
    {
      printf("Failed to parse arguments: %s\n", error->message);
      g_error_free (error);
      
      exit (1);
    }
  
  if (replace_service == TRUE)
    replace_existing_service ();
  
  UNITY_WEBAPPS_NOTE (SERVICE, "Starting Unity Webapps Service");
  
  unity_webapps_service_spawn_index_updater ();
  
  service = unity_webapps_service_new ();
  
  owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, UNITY_WEBAPPS_SERVICE_NAME,
			     G_BUS_NAME_OWNER_FLAGS_NONE,
			     on_bus_acquired,
			     on_name_acquired,
			     on_name_lost,
			     service /* userdata */,
			     NULL /* userdata free*/);
			     
  g_timeout_add_seconds (unity_webapps_service_get_index_updater_timeout (), 
			 indexer_spawn_callback, NULL);
  
  mainloop = g_main_loop_new(NULL, FALSE);
  g_main_loop_run(mainloop);
  
  g_bus_unown_name(owner_id);

  unity_webapps_service_destroy (service);

  return 0;
}
