/*
 * unity-webapps-indicator-model.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.h>
#include <glib-object.h>

#include "unity-webapps-indicator-model.h"

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

typedef struct _UnityWebappsIndicatorModelIndicatorValue {
  GVariant *value;
  
  gint interest_id;
} UnityWebappsIndicatorModelIndicatorValue;

typedef struct _UnityWebappsIndicatorModelIndicator {
  gchar *name;

  gint ref_count;
  
  GHashTable *properties_by_name;
} UnityWebappsIndicatorModelIndicator;

struct _UnityWebappsIndicatorModelPrivate {
  GHashTable *indicators_for_interest;
  GHashTable *indicators_by_name;
  gpointer fill;
};


enum {
  INDICATOR_ADDED,
  INDICATOR_REMOVED,
  INDICATOR_PROPERTY_CHANGED,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE(UnityWebappsIndicatorModel, unity_webapps_indicator_model, G_TYPE_OBJECT);

#define UNITY_WEBAPPS_INDICATOR_MODEL_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), UNITY_WEBAPPS_TYPE_INDICATOR_MODEL, UnityWebappsIndicatorModelPrivate))

UnityWebappsIndicatorModelIndicator *unity_webapps_indicator_model_indicator_new (const gchar *name);
void unity_webapps_indicator_model_indicator_free (UnityWebappsIndicatorModelIndicator *indicator);

UnityWebappsIndicatorModelIndicatorValue *unity_webapps_indicator_model_indicator_value_new (GVariant *variant_value, gint interest_id);
void unity_webapps_indicator_model_indicator_value_free (UnityWebappsIndicatorModelIndicatorValue *indicator_value);

void unity_webapps_indicator_model_ref_indicator_for_interest (UnityWebappsIndicatorModel *model,
							       UnityWebappsIndicatorModelIndicator *indicator,
							       gint interest_id);
void unity_webapps_indicator_model_unref_indicator_for_interest (UnityWebappsIndicatorModel *model,
								 UnityWebappsIndicatorModelIndicator *indicator,
								 gint interest_id);

static gint
unity_webapps_indicator_model_compare_indicators (gconstpointer a, gconstpointer b)
{
  UnityWebappsIndicatorModelIndicator *indicator_a, *indicator_b;
  
  indicator_a = (UnityWebappsIndicatorModelIndicator *)a;
  indicator_b = (UnityWebappsIndicatorModelIndicator *)b;
  
  return g_strcmp0 (indicator_a->name, indicator_b->name);
}

static void
unity_webapps_indicator_model_free_property_queue (gpointer data)
{
  GQueue *queue;
  
  queue = (GQueue *)data;
  
  g_queue_free_full (queue, (GDestroyNotify) unity_webapps_indicator_model_indicator_value_free);
  
}

UnityWebappsIndicatorModelIndicatorValue *
unity_webapps_indicator_model_indicator_value_new (GVariant *variant_value, gint interest_id)
{
  UnityWebappsIndicatorModelIndicatorValue *indicator_value;
  
  indicator_value = (UnityWebappsIndicatorModelIndicatorValue *)g_malloc0 (sizeof (UnityWebappsIndicatorModelIndicatorValue));

  indicator_value->interest_id = interest_id;
  indicator_value->value = g_variant_ref (variant_value);
  
  return indicator_value;
}

void 
unity_webapps_indicator_model_indicator_value_free (UnityWebappsIndicatorModelIndicatorValue *indicator_value)
{
  g_variant_unref (indicator_value->value);
  
  g_free (indicator_value);
}

UnityWebappsIndicatorModelIndicator *
unity_webapps_indicator_model_indicator_new (const gchar *name)
{
  UnityWebappsIndicatorModelIndicator *indicator;
  
  indicator = g_malloc0 (sizeof (UnityWebappsIndicatorModelIndicator));
  
  indicator->ref_count = 0;
  indicator->name = g_strdup (name);
  
  indicator->properties_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
							 g_free, unity_webapps_indicator_model_free_property_queue);
  
  return indicator;
}

void 
unity_webapps_indicator_model_indicator_free (UnityWebappsIndicatorModelIndicator *indicator)
{
  g_free (indicator->name);
  
  g_hash_table_destroy (indicator->properties_by_name);
  
  g_free (indicator);
}

gboolean
unity_webapps_indicator_model_indicator_push_property (UnityWebappsIndicatorModel *model,
						       UnityWebappsIndicatorModelIndicator *indicator,
						       const gchar *name,
						       GVariant *value,
						       gint interest_id)
{
  GQueue *property_queue;
  UnityWebappsIndicatorModelIndicatorValue *property_value;
  
  property_queue = (GQueue *)g_hash_table_lookup (indicator->properties_by_name,
						  name);
  
  property_value = NULL;
  if (property_queue == NULL)
    {
      property_queue = g_queue_new ();
      g_hash_table_insert (indicator->properties_by_name,
			   g_strdup (name), property_queue);
    }
  else
    {
      property_value = g_queue_peek_tail (property_queue);
    }
  
  if (property_value == NULL || (g_variant_compare (property_value->value, value) != 0))
    {
      property_value = unity_webapps_indicator_model_indicator_value_new (value, interest_id);
      
      g_queue_push_tail (property_queue, property_value);
      
      return TRUE;
    }
  
  return FALSE;
}

GVariant *
unity_webapps_indicator_model_indicator_value_queue_clear_interest (UnityWebappsIndicatorModelIndicator *indicator,
								    const gchar *property_name,
								    GQueue *value_queue, 
								    gint interest_id)
{
  GList *to_remove, *walk;
  GVariant *current_value_variant, *new_value_variant;
  UnityWebappsIndicatorModelIndicatorValue *indicator_value;
  gint i, len;
  GVariant *ret;
  
  indicator_value = (UnityWebappsIndicatorModelIndicatorValue *)g_queue_peek_tail (value_queue);
  if (indicator_value == NULL)
    {
      return NULL;
    }

  current_value_variant = g_variant_ref (indicator_value->value);
  
  len = g_queue_get_length (value_queue);
  
  to_remove = NULL;
  
  for (i = 0; i < len; i++)
    {
      indicator_value = (UnityWebappsIndicatorModelIndicatorValue *)g_queue_peek_nth (value_queue, i);
      
      if (indicator_value->interest_id == interest_id)
	{
	  to_remove = g_list_append (to_remove, indicator_value);
	}
    }
  
  for (walk = to_remove; walk != NULL; walk = walk->next)
    {
      indicator_value = (UnityWebappsIndicatorModelIndicatorValue *)walk->data;
      
      g_queue_remove (value_queue, indicator_value);
    }

  indicator_value = (UnityWebappsIndicatorModelIndicatorValue *)g_queue_peek_tail (value_queue);
  if (indicator_value == NULL)
    {
      g_hash_table_remove (indicator->properties_by_name, property_name);

      ret = NULL;
      goto out;
    }

  new_value_variant = g_variant_ref (indicator_value->value);
  if (g_variant_compare (new_value_variant, current_value_variant) != 0)
    {
      ret = new_value_variant;
    }
  else
    {
      ret = NULL;
    }
 out:
  g_variant_unref (current_value_variant);
  g_list_free (to_remove);
  
  return ret;
}

void
unity_webapps_indicator_model_remove_indicator_properties_for_interest (UnityWebappsIndicatorModel *model,
									UnityWebappsIndicatorModelIndicator *indicator,
									gint interest_id)
{
  GList *property_names, *walk;
  
  property_names = g_hash_table_get_keys (indicator->properties_by_name);
  
  for (walk = property_names; walk != NULL; walk = walk->next)
    {
      GQueue *value_queue;
      GVariant *new_value;
      const gchar *name;
      
      name = (gchar *)walk->data;
      
      value_queue = g_hash_table_lookup (indicator->properties_by_name, name);
      
      new_value = unity_webapps_indicator_model_indicator_value_queue_clear_interest (indicator, name,
										      value_queue,
										      interest_id);
      
      if (new_value != NULL)
	{
	  g_signal_emit (model, signals[INDICATOR_PROPERTY_CHANGED], 0, indicator->name,
			 name, new_value, TRUE);
	}
    }
  
  g_list_free (property_names);
}

void 
unity_webapps_indicator_model_ref_indicator_for_interest (UnityWebappsIndicatorModel *model,
							  UnityWebappsIndicatorModelIndicator *indicator,
							  gint interest_id)
{
  GList *indicators_for_interest, *indicator_link;
  
  indicators_for_interest = (GList *)g_hash_table_lookup (model->priv->indicators_for_interest,
							  GINT_TO_POINTER (interest_id));
  
  indicator_link = g_list_find (indicators_for_interest, indicator);
  
  if (indicator_link != NULL)
    {
      return;
    }
  
  indicators_for_interest = g_list_append (indicators_for_interest, (gpointer)indicator);
  g_hash_table_insert (model->priv->indicators_for_interest, GINT_TO_POINTER (interest_id), indicators_for_interest);
  
  indicator->ref_count++;
  
}

void 
unity_webapps_indicator_model_unref_indicator_for_interest (UnityWebappsIndicatorModel *model,
							    UnityWebappsIndicatorModelIndicator *indicator,
							    gint interest_id)
{
  GList *indicators_for_interest, *indicator_link;
  
  indicators_for_interest = (GList *)g_hash_table_lookup (model->priv->indicators_for_interest,
							  GINT_TO_POINTER (interest_id));
  
  indicator_link = g_list_find (indicators_for_interest, indicator);
  
  if (indicator_link == NULL)
    {
      return;
    }
  
  indicators_for_interest = g_list_remove (indicators_for_interest, (gpointer)indicator);
  g_hash_table_insert (model->priv->indicators_for_interest, GINT_TO_POINTER (interest_id), indicators_for_interest);
  
  indicator->ref_count--;
  
  if (indicator->ref_count == 0)
    {
      g_signal_emit (model, signals[INDICATOR_REMOVED], 0, indicator->name);
      g_hash_table_remove (model->priv->indicators_by_name, indicator->name);
      
      return;
    }
  
  unity_webapps_indicator_model_remove_indicator_properties_for_interest (model, indicator, interest_id);  
}


static void
unity_webapps_indicator_model_finalize (GObject *object)
{
  UnityWebappsIndicatorModel *model;
  
  model = UNITY_WEBAPPS_INDICATOR_MODEL (object);
  
  g_hash_table_destroy (model->priv->indicators_by_name);

  // TODO: Free contents of indicators_for_interest (the GList)
  g_hash_table_destroy (model->priv->indicators_for_interest);
}

static void
unity_webapps_indicator_model_class_init (UnityWebappsIndicatorModelClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  
  object_class->finalize = unity_webapps_indicator_model_finalize;
  
  g_type_class_add_private (object_class, sizeof(UnityWebappsIndicatorModelPrivate));
  
  signals[INDICATOR_ADDED] = g_signal_new ("indicator-added",
					   UNITY_WEBAPPS_TYPE_INDICATOR_MODEL,
					   G_SIGNAL_RUN_LAST,
					   0,
					   NULL,
					   NULL,
					   NULL,
					   G_TYPE_NONE,
					   1,
					   G_TYPE_STRING);
  signals[INDICATOR_REMOVED] = g_signal_new ("indicator-removed",
					     UNITY_WEBAPPS_TYPE_INDICATOR_MODEL,
					     G_SIGNAL_RUN_LAST,
					     0,
					     NULL,
					     NULL,
					     NULL,
					     G_TYPE_NONE,
					     1,
					     G_TYPE_STRING);
  
  signals[INDICATOR_PROPERTY_CHANGED] = g_signal_new ("indicator-property-changed",
						      UNITY_WEBAPPS_TYPE_INDICATOR_MODEL,
						      G_SIGNAL_RUN_LAST,
						      0,
						      NULL,
						      NULL,
						      NULL,
						      G_TYPE_NONE,
						      4,
						      G_TYPE_STRING,
						      G_TYPE_STRING,
						      G_TYPE_VARIANT,
						      G_TYPE_BOOLEAN);
}

static void
unity_webapps_indicator_model_init (UnityWebappsIndicatorModel *manager)
{
  manager->priv = UNITY_WEBAPPS_INDICATOR_MODEL_GET_PRIVATE (manager);
  
  manager->priv->indicators_for_interest = g_hash_table_new_full (g_direct_hash, g_direct_equal,
								  NULL, NULL);
  manager->priv->indicators_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
							     g_free, (GDestroyNotify)unity_webapps_indicator_model_indicator_free);

}

UnityWebappsIndicatorModel *
unity_webapps_indicator_model_new ()
{
  return g_object_new (UNITY_WEBAPPS_TYPE_INDICATOR_MODEL, NULL);
}

void
unity_webapps_indicator_model_show_indicator_for_interest (UnityWebappsIndicatorModel *model,
							   const gchar *name,
							   gint interest_id) 
{
  UnityWebappsIndicatorModelIndicator *indicator;
  
  indicator = g_hash_table_lookup (model->priv->indicators_by_name, name);
  
  if (indicator == NULL)
    {
      indicator = unity_webapps_indicator_model_indicator_new (name);
      g_hash_table_insert (model->priv->indicators_by_name, g_strdup (name), indicator);
      
      g_signal_emit (model, signals[INDICATOR_ADDED], 0, indicator->name);
    }

  unity_webapps_indicator_model_ref_indicator_for_interest (model, indicator, interest_id);

  return;
}

void 
unity_webapps_indicator_model_clear_indicator_for_interest (UnityWebappsIndicatorModel *model,
							    const gchar *name,
							    gint interest_id)
{
  UnityWebappsIndicatorModelIndicator *indicator;
  
  indicator = g_hash_table_lookup (model->priv->indicators_by_name, name);
  
  if (indicator == NULL)
    {
      return;
    }
  
  unity_webapps_indicator_model_unref_indicator_for_interest (model, indicator, interest_id);
}

void
unity_webapps_indicator_model_clear_indicators_for_interest (UnityWebappsIndicatorModel *model,
							     gint interest_id)
{
  GList *indicators_for_interest, *walk;
  
  indicators_for_interest = (GList *) g_hash_table_lookup (model->priv->indicators_for_interest, 
							   GINT_TO_POINTER (interest_id));
  
  indicators_for_interest = g_list_copy (indicators_for_interest);
  
  for (walk = indicators_for_interest; walk != NULL; walk = walk->next)
    {
      UnityWebappsIndicatorModelIndicator *indicator;
      
      indicator = (UnityWebappsIndicatorModelIndicator *)walk->data;

      unity_webapps_indicator_model_unref_indicator_for_interest (model, indicator, interest_id);
    }
  
  g_list_free (indicators_for_interest);
}

static GVariant *
unity_webapps_indicator_model_serialize_indicator_properties (UnityWebappsIndicatorModel *model,
							      UnityWebappsIndicatorModelIndicator *indicator)
{
  GList *properties, *walk;
  GVariantBuilder b;
  
  properties = g_hash_table_get_keys (indicator->properties_by_name);
  
  g_variant_builder_init (&b, G_VARIANT_TYPE("a{sv}"));
  //  g_variant_builder_open (&b, G_VARIANT_TYPE_ARRAY);
  
  for (walk = properties; walk != NULL; walk = walk->next)
    {
      GVariant *property_value;
      const gchar *property_name;
      
      property_name = (const gchar *)walk->data;
      property_value = unity_webapps_indicator_model_get_indicator_property (model, indicator->name,
									     property_name);
      
      if (property_value == NULL)
	{
	  // TODO: FIXME
	  g_warning ("TODO: When does this happen?");
	  continue;
	}
      
      //  g_variant_builder_open (&b, G_VARIANT_TYPE("{sv}"));
      g_variant_builder_add (&b, "{sv}", property_name,
			     property_value);
      //      g_variant_builder_close (&b);
    }
  
  //  g_variant_builder_close (&b);
  
  g_list_free (properties);
  
  return g_variant_builder_end (&b);
}

static GVariant *
unity_webapps_indicator_model_serialize_indicator (UnityWebappsIndicatorModel *model,
						   UnityWebappsIndicatorModelIndicator *indicator)
{
  GVariant *tuple_values[3];
  
  tuple_values[0] = g_variant_new_string (indicator->name);
  tuple_values[1] = g_variant_new_int32 (indicator->ref_count);
  tuple_values[2] = unity_webapps_indicator_model_serialize_indicator_properties (model, indicator);

  return g_variant_new_tuple (tuple_values, 3);
}

GVariant *
unity_webapps_indicator_model_serialize (UnityWebappsIndicatorModel *model)
{
  GList *indicators, *walk;
  GVariantBuilder b;
  
  g_return_val_if_fail (UNITY_WEBAPPS_IS_INDICATOR_MODEL (model), NULL);
  
  indicators = g_hash_table_get_values (model->priv->indicators_by_name);
  indicators = g_list_sort (indicators, unity_webapps_indicator_model_compare_indicators);
  
  g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(sia{sv}))"));
  g_variant_builder_open (&b, G_VARIANT_TYPE ("a(sia{sv})"));
  
  for (walk = indicators; walk != NULL; walk = walk->next)
    {
      UnityWebappsIndicatorModelIndicator *indicator;
      GVariant *indicator_variant;
      
      indicator = (UnityWebappsIndicatorModelIndicator *)walk->data;
      indicator_variant = unity_webapps_indicator_model_serialize_indicator (model, indicator);
      
      g_variant_builder_add_value (&b, indicator_variant);
    }
  
  g_list_free (indicators);
  
  g_variant_builder_close (&b);
  return g_variant_builder_end (&b);  
}

static gboolean
unity_webapps_indicator_model_property_should_draw_attention (const gchar *property_name, 
							      GVariant *value)
{
  if (g_strcmp0(property_name, "count") == 0)
    {
      
      if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
	{
	  const gchar *count_val;
	  
	  count_val = g_variant_get_string (value, NULL);
	  if (g_strcmp0 (count_val, "0") == 0)
	    {
	      return FALSE;
	    }
	}
      if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT32))
	{
	  gint count_val;
	  
	  count_val = g_variant_get_int32 (value);
	  
	  if (count_val == 0)
	    {
	      return FALSE;
	    }
	}
    }
  return TRUE;
}

void
unity_webapps_indicator_model_set_indicator_property_for_interest (UnityWebappsIndicatorModel *model,
								   const gchar *indicator_name,
								   const gchar *property_name,
								   GVariant *value,
								   gint interest_id)
{
  UnityWebappsIndicatorModelIndicator *indicator;
  GList *indicators_for_interest, *indicator_link;
  gboolean changed_property;

  g_return_if_fail (UNITY_WEBAPPS_IS_INDICATOR_MODEL (model));

  g_variant_ref_sink (value);

  indicator = g_hash_table_lookup (model->priv->indicators_by_name, indicator_name);
  
  if (indicator == NULL)
    {
      goto out;
    }
  
  indicators_for_interest = (GList *)g_hash_table_lookup (model->priv->indicators_for_interest,
							  GINT_TO_POINTER (interest_id));
  
  indicator_link = g_list_find (indicators_for_interest, indicator);
  
  if (indicator_link == NULL)
    {
      goto out;
    }
  
  changed_property = 
    unity_webapps_indicator_model_indicator_push_property (model, indicator, property_name, value, interest_id);
  
  if (changed_property == TRUE)
    {
      gboolean draw_attention;
      
      draw_attention = unity_webapps_indicator_model_property_should_draw_attention (property_name, value);
      g_signal_emit (model, signals[INDICATOR_PROPERTY_CHANGED], 0, indicator_name,
		     property_name, value, draw_attention);
    }

 out:
  g_variant_unref (value);
}

GVariant *
unity_webapps_indicator_model_get_indicator_property (UnityWebappsIndicatorModel *model,
						      const gchar *indicator_name,
						      const gchar *property_name)
{
  UnityWebappsIndicatorModelIndicator *indicator;
  UnityWebappsIndicatorModelIndicatorValue *indicator_value;
  GQueue *value_queue;
  
  g_return_val_if_fail (UNITY_WEBAPPS_IS_INDICATOR_MODEL (model), NULL);
  
  indicator = g_hash_table_lookup (model->priv->indicators_by_name, indicator_name);
  
  if (indicator == NULL)
    {
      return NULL;
    }
  
  value_queue = g_hash_table_lookup (indicator->properties_by_name, property_name);
  
  if (value_queue == NULL)
    return NULL;
  
  indicator_value = g_queue_peek_tail (value_queue);
  
  if (indicator_value == NULL)
    return NULL;
  
  // TODO: Floating? FIXME:
  return g_variant_ref (indicator_value->value);
}
