/*
 * unity-webapps-url-repository.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 <json-glib/json-glib.h>

#include "unity-webapps-application-repository.h"
#include "unity-webapps-local-url-index.h"
#include "unity-webapps-dpkg-url-index.h"
#include "unity-webapps-dpkg-available-application.h"
#include "unity-webapps-local-available-application.h"
#include "unity-webapps-script-loader.h"

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

struct _UnityWebappsApplicationRepositoryPrivate {
  UnityWebappsUrlIndex *available_index;
  UnityWebappsUrlIndex *local_index;
  
  UnityWebappsScriptLoader *script_loader;
  
  GHashTable *applications_by_name;
};

enum {
  PROP_0,
  PROP_AVAILABLE_INDEX,
  PROP_LOCAL_INDEX
};

G_DEFINE_TYPE(UnityWebappsApplicationRepository, unity_webapps_application_repository, G_TYPE_OBJECT)

#define UNITY_WEBAPPS_APPLICATION_REPOSITORY_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), UNITY_WEBAPPS_TYPE_APPLICATION_REPOSITORY, UnityWebappsApplicationRepositoryPrivate))

static void
unity_webapps_application_repository_get_property (GObject *object,
						 guint prop_id,
						 GValue *value,
						 GParamSpec *pspec)
{
  UnityWebappsApplicationRepository *self;
  
  self = UNITY_WEBAPPS_APPLICATION_REPOSITORY (object);
  
  switch (prop_id)
    {
    case PROP_LOCAL_INDEX:
      g_value_set_object (value, self->priv->local_index);
      break;
    case PROP_AVAILABLE_INDEX:
      g_value_set_object (value, self->priv->available_index);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
unity_webapps_application_repository_set_property (GObject *object,
						 guint prop_id,
						 const GValue *value,
						 GParamSpec *pspec)
{
  UnityWebappsApplicationRepository *self;
  
  self = UNITY_WEBAPPS_APPLICATION_REPOSITORY (object);
  
  switch (prop_id)
    {
    case PROP_AVAILABLE_INDEX:
      g_assert (self->priv->available_index == NULL);
      self->priv->available_index = g_value_get_object (value);
      break;
    case PROP_LOCAL_INDEX:
      g_assert (self->priv->local_index == NULL);
      self->priv->local_index = g_value_get_object (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}


static void
unity_webapps_application_repository_finalize (GObject *object)
{
  UnityWebappsApplicationRepository *repository;

  repository = UNITY_WEBAPPS_APPLICATION_REPOSITORY (object);
  

  g_hash_table_destroy (repository->priv->applications_by_name);
}

static void
unity_webapps_application_repository_dispose (GObject *object)
{
  UnityWebappsApplicationRepository *repository;

  repository = UNITY_WEBAPPS_APPLICATION_REPOSITORY (object);
  
  if (repository->priv->available_index)
    {
      g_object_unref (G_OBJECT (repository->priv->available_index));
      repository->priv->available_index = NULL;
    }
  if (repository->priv->local_index)
    {
      g_object_unref (G_OBJECT (repository->priv->local_index));
      repository->priv->local_index = NULL;
    }
  if (repository->priv->script_loader)
    {
      g_object_unref (G_OBJECT (repository->priv->script_loader));
      repository->priv->script_loader = NULL;
    }
}

static void
unity_webapps_application_repository_class_init (UnityWebappsApplicationRepositoryClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  
  object_class->finalize = unity_webapps_application_repository_finalize;
  object_class->dispose = unity_webapps_application_repository_dispose;
  object_class->get_property = unity_webapps_application_repository_get_property;
  object_class->set_property = unity_webapps_application_repository_set_property;
  
  g_type_class_add_private (object_class, sizeof(UnityWebappsApplicationRepositoryPrivate));
  
  g_object_class_install_property (object_class, PROP_LOCAL_INDEX,
				   g_param_spec_object ("local-index",
							"Local Index",
							"Index for Locally Available Applications",
							UNITY_WEBAPPS_TYPE_LOCAL_URL_INDEX,
							G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (object_class, PROP_AVAILABLE_INDEX,
				   g_param_spec_object ("available-index",
							"Available Index",
							"Index for remotely available applications",
							UNITY_WEBAPPS_TYPE_DPKG_URL_INDEX,
							G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}


static void
unity_webapps_application_repository_init (UnityWebappsApplicationRepository *repository)
{
  repository->priv = UNITY_WEBAPPS_APPLICATION_REPOSITORY_GET_PRIVATE (repository);
  
  repository->priv->script_loader = unity_webapps_script_loader_new ();
  
  repository->priv->applications_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
}

UnityWebappsApplicationRepository *
unity_webapps_application_repository_new_default ()
{
  UnityWebappsUrlIndex *available_index, *local_index;

  available_index = unity_webapps_dpkg_url_index_new_default ();
  local_index = unity_webapps_local_url_index_new_default ();
  

  return g_object_new (UNITY_WEBAPPS_TYPE_APPLICATION_REPOSITORY, "available-index", available_index, "local-index", local_index, NULL);
}

gboolean
unity_webapps_application_repository_prepare (UnityWebappsApplicationRepository *repository)
{
  return unity_webapps_local_url_index_load_applications (UNITY_WEBAPPS_LOCAL_URL_INDEX (repository->priv->local_index));
}

static GList *
unity_webapps_application_repository_search_index (UnityWebappsApplicationRepository *repository, UnityWebappsUrlIndex *index, const gchar *url)
{
  GList *available_apps, *apps, *cur_available_app;

  available_apps = unity_webapps_url_index_lookup_url (index, url);

  if (available_apps == NULL)
    {
      return NULL;
    }

  apps = NULL;
  for (cur_available_app = available_apps;
       cur_available_app != NULL;
       cur_available_app = cur_available_app->next)
    {
      apps = g_list_append(apps, UNITY_WEBAPPS_AVAILABLE_APPLICATION (cur_available_app->data));
    }
  g_list_free (available_apps);

  return apps;
}

GList *
unity_webapps_application_repository_resolve_url (UnityWebappsApplicationRepository *repository, const gchar *url)
{
  GList *app, *apps, *names;

  apps = unity_webapps_application_repository_search_index (repository,
                                                            repository->priv->local_index,
                                                            url);
  if (apps == NULL)
    {
      apps = unity_webapps_application_repository_search_index (repository,
                                                                repository->priv->available_index,
                                                                url);
    }
  if (apps == NULL)
    {
      return NULL;
    }

  names = NULL;
  for (app = apps; app != NULL; app = app->next)
    {
      UnityWebappsAvailableApplication *available_app;
      const gchar * name;

      available_app = (UnityWebappsAvailableApplication *) app->data;
      name = unity_webapps_available_application_get_name (available_app);

      if (!g_hash_table_contains(repository->priv->applications_by_name, name))
        {
          g_hash_table_insert (repository->priv->applications_by_name,
                               g_strdup (name),
                               g_object_ref (available_app));
        }
      names = g_list_append(names, (gpointer) g_strdup (name));
    }
  g_list_free (apps);

  return names;
}

gchar *
unity_webapps_application_repository_resolve_url_as_json (UnityWebappsApplicationRepository *repository, const gchar *url)
{
  JsonArray *array;
  JsonGenerator *generator;
  JsonNode *root;
  gchar *names_as_json;
  GList *app, *apps;

  apps = unity_webapps_application_repository_resolve_url (repository, url);

  root = json_node_new (JSON_NODE_ARRAY);
  array = json_array_new ();
  json_node_set_array (root, array);

  for (app = apps; app != NULL; app = app->next)
    {
      json_array_add_string_element (array, (gchar *) app->data);
    }

  generator = json_generator_new ();
  json_generator_set_root (generator, root);

  names_as_json = json_generator_to_data (generator, NULL);

  json_array_unref (array);
  json_node_free (root);
  g_object_unref (generator);
  g_list_free_full (apps, g_free);

  return names_as_json;
}

UnityWebappsApplicationStatus
unity_webapps_application_repository_get_resolved_application_status (UnityWebappsApplicationRepository *repository, const gchar *application)
{
  UnityWebappsAvailableApplication *app;
  
  app = g_hash_table_lookup (repository->priv->applications_by_name, application);
  
  if (app == NULL)
    {
      return UNITY_WEBAPPS_APPLICATION_STATUS_UNRESOLVED;
    }
  else if (UNITY_WEBAPPS_IS_DPKG_AVAILABLE_APPLICATION (app))
    {
      return UNITY_WEBAPPS_APPLICATION_STATUS_AVAILABLE;
    }
  else if (UNITY_WEBAPPS_IS_LOCAL_AVAILABLE_APPLICATION (app))
    {
      return UNITY_WEBAPPS_APPLICATION_STATUS_INSTALLED;
    }
  else
    {
      g_assert_not_reached ();
      return UNITY_WEBAPPS_APPLICATION_STATUS_UNRESOLVED;
    }
 
}

typedef struct _webapp_repository_install_data {
  UnityWebappsApplicationRepository *repository;
  UnityWebappsApplicationRepositoryInstallCallback callback;
  gpointer user_data;
} webapp_repository_install_data;

static const gchar*
unity_webapps_application_repository_create_application_desktop_name(const gchar* const desktop_name)
{
  g_return_val_if_fail(desktop_name != NULL, NULL);
  return g_strdup_printf("application://%s.desktop", desktop_name);
}

static void
unity_webapps_application_repository_add_application_to_favorites(UnityWebappsLocalAvailableApplication *app)
{
#define VALIDATE_APPLICATION_FIELD(field_name) \
  if(NULL == field_name || 0 == strlen(field_name)) \
    { \
      g_warning("Could not add installed webapp to" \
                " launcher favorites: invalid '" #field_name "' field in" \
                " manifest (or wrongly parsed)"); \
      return; \
    }

  gchar *domain, *name, *desktop_basename, *desktop_favorite_name;

  g_return_if_fail(NULL != app);
  g_return_if_fail(UNITY_WEBAPPS_IS_LOCAL_AVAILABLE_APPLICATION(app));

  name = unity_webapps_available_application_get_application_name(UNITY_WEBAPPS_AVAILABLE_APPLICATION(app));
  domain = unity_webapps_available_application_get_application_domain(UNITY_WEBAPPS_AVAILABLE_APPLICATION(app));

  VALIDATE_APPLICATION_FIELD(name);
  VALIDATE_APPLICATION_FIELD(domain);

  desktop_basename =
    unity_webapps_desktop_infos_build_desktop_basename(name,
                                                       domain);
  desktop_favorite_name =
    unity_webapps_application_repository_create_application_desktop_name(desktop_basename);

  unity_webapps_application_repository_add_desktop_to_launcher(desktop_favorite_name);

  g_free(desktop_basename);
  g_free(desktop_favorite_name);

#undef VALIDATE_APPLICATION_FIELD
}

static void
unity_webapps_application_repository_install_complete (UnityWebappsAvailableApplication *app,
						       gboolean installed,
						       gpointer user_data)
{
  webapp_repository_install_data *data;
  const gchar *name;
  
  data = (webapp_repository_install_data *)user_data;
  
  name = unity_webapps_available_application_get_name (UNITY_WEBAPPS_AVAILABLE_APPLICATION (app));
  
  if (installed)
    {
      UnityWebappsLocalAvailableApplication *app;
      UnityWebappsLocalUrlIndex *index;
      
      index = (UnityWebappsLocalUrlIndex *)data->repository->priv->local_index;

      unity_webapps_local_url_index_load_applications (index);
      app = unity_webapps_local_url_index_get_application_by_name (index, name);
      g_hash_table_replace (data->repository->priv->applications_by_name,
                            g_strdup (name),
                            g_object_ref (app));

      unity_webapps_application_repository_add_application_to_favorites(app);
    }
  data->callback (data->repository, name, installed, data->user_data);
  g_free (data);
}

void
unity_webapps_application_repository_add_desktop_to_launcher (const gchar *const desktop_name)
{
  const gchar *UNITY_LAUNCHER_DBUS_IF_NAME = "com.canonical.Unity.Launcher";
  const gchar *UNITY_LAUNCHER_DBUS_PATH = "/com/canonical/Unity/Launcher";

  GDBusConnection *connection;
  g_return_if_fail(NULL != desktop_name || 0 != strlen(desktop_name));

  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
  if (NULL == connection)
    {
      g_warning("Could not get a connection to DBUS session bus");
      return;
    }

  GError *error = NULL;
  g_dbus_connection_call (connection,
    UNITY_LAUNCHER_DBUS_IF_NAME,
    UNITY_LAUNCHER_DBUS_PATH,
    UNITY_LAUNCHER_DBUS_IF_NAME,
    "UpdateLauncherIconFavoriteState",
    g_variant_new ("(sb)", desktop_name, TRUE, NULL),
    NULL,
    G_DBUS_CALL_FLAGS_NONE,
    -1,
    NULL,
    NULL,
    &error);
  if (error)
    {
      g_error("Could not complete DBUS call to UpdateLauncherIconFavoriteState: %s",
              error->message);
      g_error_free (error);
    }
  g_dbus_connection_flush_sync (connection, NULL, NULL);
}

void
unity_webapps_application_repository_install_application (UnityWebappsApplicationRepository *repository, const gchar *name,
							  UnityWebappsApplicationRepositoryInstallCallback callback, gpointer user_data)
{
  webapp_repository_install_data *data;
  UnityWebappsAvailableApplication *app;
  
  app = g_hash_table_lookup (repository->priv->applications_by_name, name);
  
  if (app == NULL)
    {
      callback (repository, name, FALSE, user_data);
      return;
    }
  if (UNITY_WEBAPPS_IS_LOCAL_AVAILABLE_APPLICATION (app))
    {
      callback (repository, name, TRUE, user_data);
      return;
    }
  else if (UNITY_WEBAPPS_IS_DPKG_AVAILABLE_APPLICATION (app) == FALSE)
    {
      g_assert_not_reached ();
    }
  
  data = g_malloc0 (sizeof (webapp_repository_install_data));
  data->repository = repository;
  data->callback = callback;
  data->user_data = user_data;
  
  unity_webapps_dpkg_available_application_install (UNITY_WEBAPPS_DPKG_AVAILABLE_APPLICATION (app), unity_webapps_application_repository_install_complete, data);
}

gchar *
unity_webapps_application_repository_get_userscript_contents (UnityWebappsApplicationRepository *repository, const gchar *name)
{
  UnityWebappsAvailableApplication *app;
  
  app = g_hash_table_lookup (repository->priv->applications_by_name, name);
  
  if (app == NULL)
    {
      return NULL;
    }
  if (UNITY_WEBAPPS_IS_LOCAL_AVAILABLE_APPLICATION (app))
    {
      UnityWebappsLocalAvailableApplication *local_app;
      
      local_app = (UnityWebappsLocalAvailableApplication *)app;
      
      return unity_webapps_script_loader_get_userscript_contents (repository->priv->script_loader, local_app);
    }
  else if (UNITY_WEBAPPS_IS_DPKG_AVAILABLE_APPLICATION (app))
    {
      return NULL;
    }
  else
    {
      g_assert_not_reached ();
    }
  return NULL;
}


const gchar *
unity_webapps_application_repository_get_resolved_application_name (UnityWebappsApplicationRepository *repository,
								    const gchar *application)
{
  UnityWebappsAvailableApplication *app;
  
  app = g_hash_table_lookup (repository->priv->applications_by_name, application);
  
  if (app == NULL)
    {
      return NULL;
    }
  
  return unity_webapps_available_application_get_application_name (app);
}


const gchar *
unity_webapps_application_repository_get_resolved_application_domain (UnityWebappsApplicationRepository *repository,
								      const gchar *application)
{
  UnityWebappsAvailableApplication *app;
  
  app = g_hash_table_lookup (repository->priv->applications_by_name, application);
  
  if (app == NULL)
    {
      return NULL;
    }
  
  return unity_webapps_available_application_get_application_domain (app);
}
