/*
 * unity-webapps-interest-manager.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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib/gstdio.h>
#include <gio/gio.h>

#include "unity-webapps-interest-manager.h"
#include "unity-webapps-context-daemon.h"

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

#include "../unity-webapps-debug.h"

struct _UnityWebappsInterestManagerPrivate {
  GHashTable *interests_by_id;

  gint interest_id_count;
  gint active_interest_count;
};

enum {
  INTEREST_ADDED,
  INTEREST_REMOVED,
  BECAME_LONELY,
  ACTIVE_CHANGED,
  LOCATION_CHANGED,
  WINDOW_CHANGED,
  INTEREST_READY,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static UnityWebappsInterest *unity_webapps_interest_new (UnityWebappsInterestManager *manager,
							 const gchar *sender, gint id);
static void unity_webapps_interest_free (UnityWebappsInterest *interest);

G_DEFINE_TYPE(UnityWebappsInterestManager, unity_webapps_interest_manager, G_TYPE_OBJECT)

#define UNITY_WEBAPPS_INTEREST_MANAGER_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), UNITY_WEBAPPS_TYPE_INTEREST_MANAGER, UnityWebappsInterestManagerPrivate))

static void
unity_webapps_interest_manager_finalize (GObject *object)
{
  UnityWebappsInterestManager *manager;
  
  manager = UNITY_WEBAPPS_INTEREST_MANAGER (object);
  
  g_hash_table_destroy (manager->priv->interests_by_id);
}

static void
unity_webapps_interest_manager_class_init (UnityWebappsInterestManagerClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  
  object_class->finalize = unity_webapps_interest_manager_finalize;
  
  g_type_class_add_private (object_class, sizeof(UnityWebappsInterestManagerPrivate));
  
  signals[INTEREST_ADDED] = g_signal_new ("interest-added",
					  UNITY_WEBAPPS_TYPE_INTEREST_MANAGER,
					  G_SIGNAL_RUN_LAST,
					  0,
					  NULL,
					  NULL,
					  NULL,
					  G_TYPE_NONE,
					  1,
					  G_TYPE_POINTER);
  signals[INTEREST_REMOVED] = g_signal_new ("interest-removed",
					    UNITY_WEBAPPS_TYPE_INTEREST_MANAGER,
					    G_SIGNAL_RUN_LAST,
					    0,
					    NULL,
					    NULL,
					    NULL,
					    G_TYPE_NONE,
					    1,
					    G_TYPE_POINTER);
  
  signals[BECAME_LONELY] = g_signal_new ("became-lonely",
					 UNITY_WEBAPPS_TYPE_INTEREST_MANAGER,
					 G_SIGNAL_RUN_LAST,
					 0,
					 NULL,
					 NULL,
					 NULL,
					 G_TYPE_NONE,
           1,
           G_TYPE_BOOLEAN);
  
  signals[ACTIVE_CHANGED] = g_signal_new ("active-changed",
					  UNITY_WEBAPPS_TYPE_INTEREST_MANAGER,
					  G_SIGNAL_RUN_LAST,
					  0,
					  NULL,
					  NULL,
					  NULL,
					  G_TYPE_NONE,
					  2,
					  G_TYPE_INT,
					  G_TYPE_BOOLEAN);

  signals[LOCATION_CHANGED] = g_signal_new ("location-changed",
					    UNITY_WEBAPPS_TYPE_INTEREST_MANAGER,
					    G_SIGNAL_RUN_LAST,
					    0,
					    NULL,
					    NULL,
					    NULL,
					    G_TYPE_NONE,
					    2,
					    G_TYPE_INT,
					    G_TYPE_STRING);

  signals[WINDOW_CHANGED] = g_signal_new ("window-changed",
					    UNITY_WEBAPPS_TYPE_INTEREST_MANAGER,
					    G_SIGNAL_RUN_LAST,
					    0,
					    NULL,
					    NULL,
					    NULL,
					    G_TYPE_NONE,
					    2,
					    G_TYPE_INT,
					    G_TYPE_UINT64);

  signals[INTEREST_READY] = g_signal_new ("interest-ready",
					  UNITY_WEBAPPS_TYPE_INTEREST_MANAGER,
					  G_SIGNAL_RUN_LAST,
					  0,
					  NULL,
					  NULL,
					  NULL,
					  G_TYPE_NONE,
					  1,
					  G_TYPE_POINTER);
}

static void
unity_webapps_interest_manager_init (UnityWebappsInterestManager *manager)
{
  manager->priv = UNITY_WEBAPPS_INTEREST_MANAGER_GET_PRIVATE (manager);
  
  manager->priv->active_interest_count = 0;
  manager->priv->interest_id_count = 1;
  manager->priv->interests_by_id = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)unity_webapps_interest_free);
}

UnityWebappsInterestManager *
unity_webapps_interest_manager_new ()
{
  return g_object_new (UNITY_WEBAPPS_TYPE_INTEREST_MANAGER, NULL);
}

static UnityWebappsInterest *
unity_webapps_interest_new (UnityWebappsInterestManager *manager, const gchar *sender, gint id)
{
  UnityWebappsInterest *interest;
  
  interest = g_malloc0 (sizeof (UnityWebappsInterest));
  
  interest->sender = g_strdup (sender);
  interest->active = FALSE;
  interest->location = NULL;
  interest->window = 0;
  
  interest->manager = manager;
  
  interest->id = id;
  interest->pending_invocations = NULL;
  
  interest->ready = FALSE;
  
  return interest;
}

static void
unity_webapps_interest_free (UnityWebappsInterest *interest)
{
  if (interest->pending_invocations)
    {
      unity_webapps_interest_manager_clear_preview_requests (interest->manager,
							     interest->id,
							     "");
      g_list_free (interest->pending_invocations);
    }

  if (interest->sender)
    {
      g_free (interest->sender);
    }

  if (interest->location)
    {
      g_free (interest->location);
    }
  

  
  g_free (interest);
}

static void
remove_interests_by_name (UnityWebappsInterestManager *manager,
			 const gchar *name)
{
  GList *found, *walk;
  GHashTableIter iter;
  gpointer key, value;
  
  g_hash_table_iter_init (&iter, manager->priv->interests_by_id);
  
  found = NULL;
  
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      UnityWebappsInterest *interest;
      
      interest = (UnityWebappsInterest *)value;
      
      if (g_strcmp0(interest->sender, name) == 0)
	{
	  UNITY_WEBAPPS_NOTE (INTEREST, "Removing interest %d because owner vanished", GPOINTER_TO_INT (key));
	  found = g_list_append (found, key);
	}
    }
  
  for (walk = found; walk != NULL; walk = walk->next)
    {
      gint interest_id;
      
      interest_id = GPOINTER_TO_INT (walk->data);
      unity_webapps_interest_manager_remove_interest (manager, interest_id, TRUE);
    }
  
  if (found != NULL)
    {
      g_list_free (found);
    }

}

static gboolean
remove_bus_watch (gpointer user_data)
{
  guint watch_id = GPOINTER_TO_INT(user_data);
  UNITY_WEBAPPS_NOTE(INTEREST, "REMOVING BUS WATCH FOR %d", watch_id);
  g_bus_unwatch_name (watch_id);
  return FALSE;
}

#ifndef UNITY_WEBAPPS_TEST_BUILD
static void
interest_name_vanished (GDBusConnection *connection,
			const gchar *name,
			gpointer user_data)
{
  UnityWebappsInterestManager *manager;
  UnityWebappsInterest *interest;
  gchar *interest_name;
  
  manager = (UnityWebappsInterestManager *)user_data;
  
  interest = (UnityWebappsInterest *)user_data;
  
  if (interest->watcher_id != 0)
    {
      g_bus_unwatch_name (interest->watcher_id);
    }
  
  interest_name = g_strdup (name);
  
  interest->watcher_id = 0;
  
  UNITY_WEBAPPS_NOTE (INTEREST, "Client vanished: %s, removing interests", interest_name);
  
  remove_interests_by_name (manager, interest_name);
  if (manager->priv->active_interest_count == 0) {
    g_signal_emit (manager, signals[BECAME_LONELY], 0, TRUE);
  }
}
#endif

gint
unity_webapps_interest_manager_add_interest (UnityWebappsInterestManager *manager,
					     const gchar *interest_name)

{
  UnityWebappsInterest *interest;
  
  interest = unity_webapps_interest_new (manager, interest_name, manager->priv->interest_id_count);
  
  g_hash_table_insert (manager->priv->interests_by_id,
		       GINT_TO_POINTER (manager->priv->interest_id_count),
		       interest);

  UNITY_WEBAPPS_NOTE (INTEREST, "Added interest %d", manager->priv->interest_id_count);
  
  manager->priv->interest_id_count++;
  manager->priv->active_interest_count++;

#ifndef UNITY_WEBAPPS_TEST_BUILD
  interest->watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
				 interest_name,
				 G_BUS_NAME_WATCHER_FLAGS_NONE,
				 NULL,
				 interest_name_vanished,
				 manager,
				 NULL /* User data free */);
#else
  interest->watcher_id = 0;
#endif
  
  g_signal_emit (manager, signals[INTEREST_ADDED], 0, interest);  
  return interest->id;
}

void
unity_webapps_interest_manager_remove_interest (UnityWebappsInterestManager *manager,
						gint interest_id,
            gboolean user_abandoned)
{
  UnityWebappsInterest *interest;
  guint watch_id;

  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest != NULL)
    {
      UNITY_WEBAPPS_NOTE (INTEREST, "Removing interest %d from %s", interest_id,
                          interest->sender);
      
      watch_id = interest->watcher_id;
      
      if (watch_id != 0)
        {
#ifndef UNITY_WEBAPPS_TEST_BUILDF
          g_timeout_add_seconds(15, remove_bus_watch, GINT_TO_POINTER(watch_id));
#endif
        }
      
      manager->priv->active_interest_count--;
      g_signal_emit (manager, signals[INTEREST_REMOVED], 0, interest);
      
      g_hash_table_remove (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
    }
  
  if (unity_webapps_interest_manager_get_num_interests (manager) == 0)
    {
      UNITY_WEBAPPS_NOTE (INTEREST, "Interest manager became lonely");
      g_signal_emit (manager, signals[BECAME_LONELY], 0, user_abandoned);
    }
}

guint
unity_webapps_interest_manager_get_num_interests (UnityWebappsInterestManager *manager)
{
  return g_hash_table_size (manager->priv->interests_by_id);
}

GVariant *
unity_webapps_interest_manager_list_interests (UnityWebappsInterestManager *manager)
{
  GVariant *interests_list;
  GVariantBuilder *builder;
  GHashTableIter iter;
  gpointer key, value;

  if (g_hash_table_size (manager->priv->interests_by_id) == 0)
    {
      return NULL;
    }
  
  builder = g_variant_builder_new (G_VARIANT_TYPE ("ai"));
  
  g_hash_table_iter_init (&iter, manager->priv->interests_by_id);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      UnityWebappsInterest *interest;
      interest = (UnityWebappsInterest *)value;

      if (interest->ready)
        {
          g_variant_builder_add (builder, "i", GPOINTER_TO_INT (key));
        }
    }

  interests_list = g_variant_builder_end (builder);

  g_variant_builder_unref (builder);
  
  return interests_list;
}

const gchar *
unity_webapps_interest_manager_get_interest_owner (UnityWebappsInterestManager *manager,
						   gint interest_id)
{
  UnityWebappsInterest *interest;

  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return NULL;
    }
  
  return interest->sender;
}

void
unity_webapps_interest_manager_set_interest_is_active (UnityWebappsInterestManager *manager,
						       gint interest_id,
						       gboolean is_active)
{
  UnityWebappsInterest *interest;
  
  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return;
    }
  
  interest->active = is_active;
  
  g_signal_emit (manager, signals[ACTIVE_CHANGED], 0, interest_id, is_active);
  
  //  if (is_active == TRUE)
  //    unity_webapps_context_daemon_set_most_recent_interest (interest_id);
}

gboolean
unity_webapps_interest_manager_get_interest_is_active (UnityWebappsInterestManager *manager,
						       gint interest_id)
{
  UnityWebappsInterest *interest;
  
  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return FALSE;
    }
  
  return interest->active;
}


void
unity_webapps_interest_manager_set_interest_location (UnityWebappsInterestManager *manager,
						      gint interest_id,
						      const gchar *location)
{
  UnityWebappsInterest *interest;
  
  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return;
    }
  
  if (interest->location != NULL)
    {
      if (g_strcmp0 (interest->location, location) == 0)
	{
	  return;
	}
      g_free (interest->location);
    }
  interest->location = g_strdup (location);
  
  g_signal_emit (manager, signals[LOCATION_CHANGED], 0, interest_id, location);
}

const gchar *
unity_webapps_interest_manager_get_interest_location (UnityWebappsInterestManager *manager,
						      gint interest_id)
{
  UnityWebappsInterest *interest;
  
  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return NULL;
    }
  
  return interest->location;
}

void
unity_webapps_interest_manager_set_interest_window (UnityWebappsInterestManager *manager,
						    gint interest_id,
						    guint64 window)
{
  UnityWebappsInterest *interest;
  gboolean made_ready;
  
  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return;
    }
  
  made_ready = FALSE;
  if ((interest->window == 0) && (window != 0))
    {
      made_ready = TRUE;
      interest->ready = TRUE;
    }
  
  interest->window = window;

  if (made_ready == TRUE)
    {
      g_signal_emit (manager, signals[INTEREST_READY], 0, interest);
    }
  g_signal_emit (manager, signals[WINDOW_CHANGED], 0, interest_id, window);
}

guint64
unity_webapps_interest_manager_get_interest_window (UnityWebappsInterestManager *manager,
						    gint interest_id)
{
  UnityWebappsInterest *interest;
  
  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return FALSE;
    }
  
  return interest->window;
}

void
unity_webapps_interest_manager_clear_preview_requests (UnityWebappsInterestManager *manager,
						       gint interest_id,
						       const gchar *preview_data)
{
  UnityWebappsInterest *interest;
  GList *walk;
  
  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return;
    }
  
  for (walk = interest->pending_invocations; walk != NULL; walk = walk->next)
    {
      GDBusMethodInvocation *invocation;
      
      invocation = (GDBusMethodInvocation *)walk->data;
      
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", preview_data, NULL));
    }
  
  g_list_free (interest->pending_invocations);
  interest->pending_invocations = NULL;
}

void
unity_webapps_interest_manager_add_preview_request (UnityWebappsInterestManager *manager,
						    gint interest_id,
						    GDBusMethodInvocation *invocation)
{
  UnityWebappsInterest *interest;
  
  interest = g_hash_table_lookup (manager->priv->interests_by_id, GINT_TO_POINTER (interest_id));
  
  if (interest == NULL)
    {
      return;
    }
  
  interest->pending_invocations = g_list_append (interest->pending_invocations, (gpointer) invocation);
}


gint
unity_webapps_interest_manager_get_active_interest_at_window (UnityWebappsInterestManager *manager,
							      guint64 window_id)
{
  GList *interests, *walk;
  gint active_interest;

  interests = g_hash_table_get_values (manager->priv->interests_by_id);
  active_interest = -1;
  
  for (walk = interests; walk != NULL; walk = walk->next)
    {
      UnityWebappsInterest *interest;
      
      interest = (UnityWebappsInterest *)walk->data;
      
      if (interest->active && (interest->window == window_id))
	{
#ifdef UNITY_WEBAPPS_ENABLE_DEBUG
	  if (active_interest != -1)
	    {
	      g_warning ("Found two active interests in one window (interests %d and %d), this should not happen",
			 interest->id, active_interest);
	    }
#endif
	  active_interest = interest->id;
#ifndef UNITY_WEBAPPS_ENABLE_DEBUG
	  goto out;
#endif
	}
    }
#ifndef UNITY_WEBAPPS_ENABLE_DEBUG
 out:
#endif
  
  g_list_free (interests);
  
  return active_interest;
}

void
unity_webapps_interest_manager_remove_interests_with_name (UnityWebappsInterestManager *manager,
							   const gchar *name)
{
  g_return_if_fail (UNITY_WEBAPPS_IS_INTEREST_MANAGER (manager));
  
  remove_interests_by_name (manager, name);
}
