/*
     This file is part of GNUnet.
     Copyright (C) 2010-2013 GNUnet e.V.

     GNUnet is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published
     by the Free Software Foundation; either version 3, or (at your
     option) any later version.

     GNUnet 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
     General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with GNUnet; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

/**
 * @file src/fs/gnunet-fs-gtk.c
 * @brief Main function of gnunet-fs-gtk
 * @author Christian Grothoff
 */
#include "gnunet-fs-gtk.h"
#include "gnunet-fs-gtk_common.h"
#include "gnunet-fs-gtk_event-handler.h"
#include "gnunet-fs-gtk_open-uri.h"
#include "gnunet/gnunet_namestore_service.h"
#include <gmodule.h>

#if HAVE_LIBUNIQUE
#include <unique/unique.h>
#endif

#include <gnunet/gnunet_arm_service.h>

/**
 * How many block requests can we have outstanding in parallel at a time by
 * default?
 */
#define DEFAULT_MAX_PARALLEL_REQUESTS 100000

/**
 * How many downloads can we have outstanding in parallel at a time by default?
 */
#define DEFAULT_MAX_PARALLEL_DOWNLOADS 128


/**
 * The GNUNET_GTK_file_sharing_downloads_tree_store with the
 * information about active (or completed) downloads.
 */
GtkTreeStore *downloads_treestore;

/**
 * Handle to our main loop.
 */
static struct GNUNET_GTK_MainLoop *ml;

/**
 * Handle for file-sharing operations.
 */
static struct GNUNET_FS_Handle *fs;

/**
 * Handle for ARM monitoring.
 */
static struct GNUNET_ARM_MonitorHandle *armon;

/**
 * Handle for ARM controlling.
 */
static struct GNUNET_ARM_Handle *arm;

/**
 * Ongoing identity operation.
 */
static struct GNUNET_IDENTITY_Operation *iop;

/**
 * Context for main window.
 */
static struct GNUNET_GTK_MainWindowContext main_context;

/**
 * Identity combo box in the main window.
 */
static GtkComboBox *id_combo_box;

/**
 * Currently selected entry in #id_liststore.
 */
static GtkTreeIter id_iter;

/**
 * List of all known egos.
 */
static GtkListStore *id_liststore;

/**
 * Status label in main window.
 */
static GtkLabel *status_label;

/**
 * Are we shutting down?
 */
static int in_shutdown = 0;

/**
 * Columns in the id list store.
 */
enum ID_COLUMNS
{

  /**
   * A gchararray
   */
  ID_LS_NAME = 0,


  /**
   * A `struct GNUNET_IDENTITY_Ego`
   */
  ID_LS_EGO = 1
};


#if HAVE_LIBUNIQUE
static UniqueApp *unique_app;
#endif


struct GNUNET_GTK_MainWindowContext *
GNUNET_FS_GTK_get_main_context ()
{
  return &main_context;
}


/**
 * Return handle for file-sharing operations.
 *
 * @return NULL on error
 */
struct GNUNET_FS_Handle *
GNUNET_FS_GTK_get_fs_handle ()
{
  return fs;
}

/**
 * Get an object from the main window.
 *
 * @param name name of the object
 * @return NULL on error, otherwise the object
 */
static GObject *
get_object (const char *name)
{
  if (NULL == ml)
    return NULL;
  return GNUNET_GTK_main_loop_get_object (ml, name);
}



/**
 * Remember FS handle if we don't have one yet.
 *
 * @param fsh file sharing handle to use
 */
void
GNUNET_FS_GTK_set_fs_handle (struct GNUNET_FS_Handle *fsh)
{
  if (NULL == fs)
    fs = fsh;
}


/**
 * Get our configuration.
 *
 * @return configuration handle
 */
const struct GNUNET_CONFIGURATION_Handle *
GNUNET_FS_GTK_get_configuration ()
{
  return GNUNET_GTK_main_loop_get_configuration (ml);
}


/**
 * Get an object from the main window.
 *
 * @param name name of the object
 * @return NULL on error
 */
GObject *
GNUNET_FS_GTK_get_main_window_object (const char *name)
{
  return GNUNET_GTK_main_loop_get_object (ml, name);
}


/**
 * Return the list store with anonymity levels.
 *
 * @return the list store
 */
GtkTreeModel *
GNUNET_FS_GTK_get_anonymity_level_list_store ()
{
  return GTK_TREE_MODEL (
    GNUNET_FS_GTK_get_main_window_object ("anonymity_level_liststore"));
}


/**
 * Obtains main window position and size before it's destroyed
 * and saves these into user's config file.
 *
 * @param main_window main window widget
 */
static void
main_window_save_position (GtkWidget *main_window)
{
  GdkWindow *main_window_gdk;
  gint window_x;
  gint window_y;
  gint window_width;
  gint window_height;
  int maximized;
  GdkWindowState window_state;
  struct GNUNET_CONFIGURATION_Handle *cfg;
  struct GNUNET_CONFIGURATION_Handle *cfgDefault;

  cfg = (struct GNUNET_CONFIGURATION_Handle *)
        GNUNET_GTK_main_loop_get_configuration (ml);
  main_window_gdk = gtk_widget_get_window (main_window);
  maximized = GNUNET_YES;
  if (NULL != main_window_gdk)
  {
    window_state = gdk_window_get_state (main_window_gdk);
    if (! (window_state & GDK_WINDOW_STATE_MAXIMIZED))
      maximized = GNUNET_NO;
  }

  gtk_window_get_position (GTK_WINDOW (main_window), &window_x, &window_y);
  gtk_window_get_size (GTK_WINDOW (main_window), &window_width, &window_height);

  GNUNET_CONFIGURATION_set_value_number (cfg,
                                         "gnunet-fs-gtk",
                                         "MAIN_WINDOW_X",
                                         window_x);
  GNUNET_CONFIGURATION_set_value_number (cfg,
                                         "gnunet-fs-gtk",
                                         "MAIN_WINDOW_Y",
                                         window_y);
  GNUNET_CONFIGURATION_set_value_number (cfg,
                                         "gnunet-fs-gtk",
                                         "MAIN_WINDOW_WIDTH",
                                         window_width);
  GNUNET_CONFIGURATION_set_value_number (cfg,
                                         "gnunet-fs-gtk",
                                         "MAIN_WINDOW_HEIGHT",
                                         window_height);
  GNUNET_CONFIGURATION_set_value_string (cfg,
                                         "gnunet-fs-gtk",
                                         "MAIN_WINDOW_MAXIMIZED",
                                         (maximized == GNUNET_YES) ? "YES"
                                                                   : "NO");

  cfgDefault = GNUNET_CONFIGURATION_create ();
  (void) GNUNET_CONFIGURATION_load (cfgDefault, NULL); /* load defaults only */
  GNUNET_CONFIGURATION_write_diffs (cfgDefault,
                                    cfg,
                                    GNUNET_GTK_main_loop_get_configuration_file (
                                      ml));
  GNUNET_CONFIGURATION_destroy (cfgDefault);
}


/**
 * Obtains main window position and size before it's destroyed
 * and saves these into user's config file.
 *
 * @param main_window main window widget
 * @param event the event that we are handling
 * @param user_data main window context
 */
gboolean
GNUNET_GTK_main_window_configure_event_cb (GtkWidget *main_window,
                                           GdkEventConfigure *event,
                                           gpointer user_data)
{
  struct GNUNET_GTK_MainWindowContext *main_context = user_data;

  main_window_save_position (main_context->main_window);
  return FALSE;
}


/**
 * Task run on shutdown.
 *
 * @param cls NULL
 */
static void
shutdown_task (void *cls)
{
  struct SearchLookup *sl;
  struct PseuLookupContext *lctx;
  struct SearchResult *sr;

  in_shutdown = 1;
  while (NULL != (sl = main_context.sl_head))
    abort_search_lookup (sl);
  while (NULL != (lctx = main_context.lctx_head))
    abort_pseu_lookup (lctx);
  while (NULL != (sr = pl_head))
  {
    GNUNET_FS_probe_stop (sr->probe);
    sr->probe = NULL;
    GNUNET_CONTAINER_DLL_remove (pl_head, pl_tail, sr);
  }
  if (NULL != fs)
  {
    GNUNET_FS_stop (fs);
    fs = NULL;
  }
  if (NULL != iop)
  {
    GNUNET_IDENTITY_cancel (iop);
    iop = NULL;
  }
  if (NULL != main_context.identity)
  {
    GNUNET_IDENTITY_disconnect (main_context.identity);
    main_context.identity = NULL;
  }
  if (NULL != main_context.zm)
  {
    GNUNET_NAMESTORE_zone_monitor_stop (main_context.zm);
    main_context.zm = NULL;
  }
  if (NULL != main_context.gns)
  {
    GNUNET_GNS_disconnect (main_context.gns);
    main_context.gns = NULL;
  }
  if (NULL != armon)
  {
    GNUNET_ARM_monitor_stop (armon);
    armon = NULL;
  }
  if (NULL != arm)
  {
    GNUNET_ARM_disconnect (arm);
    arm = NULL;
  }
  GNUNET_FS_GTK_close_uri_tab_ ();
  if (NULL != ml)
  {
    GNUNET_GTK_main_loop_quit (ml);
    ml = NULL;
  }
}


/**
 * Callback invoked if the application is supposed to exit.
 *
 * @param object origin of the quit event, unused
 * @param user_data global builder instance, unused
 */
void
GNUNET_FS_GTK_menu_quit_activate_cb (GtkMenuItem *object, gpointer user_data)
{
  GNUNET_SCHEDULER_shutdown ();
}


/**
 * Callback invoked if the application is supposed to exit.
 *
 * @param object origin of the quit event, unused
 * @param event the delete event
 * @param user_data global builder instance, unused
 */
void
GNUNET_FS_GTK_delete_event_cb (GtkWidget *object,
                               GdkEvent *event,
                               gpointer user_data)
{
  /* GNUNET_FS_GTK_delete_event_cb will eventually be called if we shut down
   * the scheduler, because shutting it down will make GTK delete the main
   * window. On the other hand, deleting the main window first (clicking on X
   * button) will not trigger scheduler shutdown, unlike
   * GNUNET_FS_GTK_menu_quit_activate_cb(). So we shut it down here again,
   * just to be sure it is dead (if it isn't, application will hang up).
   */
  GNUNET_SCHEDULER_shutdown ();
}


/**
 * Text was pasted into the main window.  Check if it is a URL
 * and perform the appropriate action.
 *
 * @param cb source clipboard
 * @param text pasted text
 * @param data NULL
 */
static void
process_paste (GtkClipboard *cb, const gchar *text, gpointer data)
{
  struct GNUNET_FS_Uri *kskuri;
  char *emsg;

  if (strlen (text) == 0)
    return;
  if (GNUNET_OK == GNUNET_FS_GTK_handle_uri_string (text, 1))
    return;
  emsg = NULL;
  kskuri = GNUNET_FS_uri_ksk_create (text, &emsg);
  if (NULL == kskuri)
  {
    GNUNET_free (emsg);
    return;
  }
  GNUNET_FS_GTK_handle_uri (kskuri, 1);
  GNUNET_FS_uri_destroy (kskuri);
}


/**
 * We got an event in the main window.  Check for clipboard action.
 *
 * @param widget the main window
 * @param event the event, we only care about button events
 * @param user_data the 'struct SearchTab' the widget is in
 * @return FALSE if no menu could be popped up,
 *         TRUE if there is now a pop-up menu
 */
gboolean
GNUNET_FS_GTK_main_window_button_press_event (GtkWidget *widget,
                                              GdkEvent *event,
                                              gpointer user_data)
{
  GdkEventButton *event_button = (GdkEventButton *) event;
  GtkClipboard *cb;

  if ((GDK_BUTTON_PRESS != event->type) || (2 != event_button->button))
    return FALSE;
  cb = gtk_clipboard_get (gdk_atom_intern ("PRIMARY", FALSE));
  gtk_clipboard_request_text (cb, &process_paste, NULL);
  return FALSE;
}


#if HAVE_LIBUNIQUE
/**
 * Function called whenever a second gnunet-fs-gtk process is started
 * with additional arguments for us.
 *
 * @param app unique handle
 * @param command command that was given
 * @param message_data command line message data
 * @param time_ timestamp of the event
 * @param user_data our 'main context'
 * @return response code for original process
 */
static UniqueResponse
unique_app_message_cb (UniqueApp *app,
                       gint command,
                       UniqueMessageData *message_data,
                       guint time_,
                       gpointer user_data)
{
  struct GNUNET_GTK_MainWindowContext *main_context = user_data;

  /* raise the window */
  if (GTK_IS_WINDOW (main_context->main_window))
    gtk_window_present_with_time (GTK_WINDOW (main_context->main_window),
                                  time_);

  switch (command)
  {
  case UNIQUE_NEW:
    /* this is unexpected... */
    GNUNET_break (0);
    break;
  case UNIQUE_OPEN: {
      gchar **uris;
      gint n_uris;
      gint i;

      uris = unique_message_data_get_uris (message_data);
      n_uris = g_strv_length (uris);
      for (i = 0; i < n_uris; i++)
      {
        if (GNUNET_OK !=
            GNUNET_FS_GTK_handle_uri_string (uris[i], 1 /* anonymity level */))
          return UNIQUE_RESPONSE_PASSTHROUGH;
      }
      g_strfreev (uris);
    }
    break;
  case UNIQUE_ACTIVATE:
    break;
  default:
    break;
  }
  return UNIQUE_RESPONSE_OK;
}


#endif


static char *
format_service_list (unsigned int count,
                     const struct GNUNET_ARM_ServiceInfo *list)
{
  size_t len = 0;
  int i;
  const char *header = _ ("GNUnet node appears to be on.");
  const char *listheader = _ ("Currently running services:\n");
  char *result;
  char *p;
  int r;

  len = strlen (header) + 1;
  if (list)
  {
    len += strlen (listheader);
    for (i = 0; i < count; i++)
      len += strlen (list[i].name) + 1;
  }
  len += 1;
  result = p = GNUNET_malloc (len * sizeof (char));
  r = GNUNET_snprintf (p, (size_t) (result + len - p), "%s\n", header);
  if (r < 0)
  {
    GNUNET_free (result);
    return NULL;
  }
  p += r;
  if (list)
  {
    r = GNUNET_snprintf (p, (size_t) (result + len - p), "%s", listheader);
    if (r < 0)
    {
      GNUNET_free (result);
      return NULL;
    }
    p += r;
    for (i = 0; i < count; i++)
    {
      size_t l = strlen (list[i].name);
      memcpy (p, list[i].name, l);
      p += l;
      if (i + 1 < count)
      {
        p[0] = '\n';
        p += 1;
      }
    }
  }
  p[0] = '\0';
  return result;
}


static void
service_list_callback (void *cls,
                       enum GNUNET_ARM_RequestStatus rs,
                       unsigned int count,
                       const struct GNUNET_ARM_ServiceInfo *list)
{
  char *service_list;

  if ((GNUNET_ARM_REQUEST_SENT_OK != rs) || (NULL == list))
    return;
  service_list = format_service_list (count, list);
  GNUNET_FS_GTK_update_connection_indicator (cls, TRUE, service_list);
  GNUNET_free (service_list);
}


static void
arm_connection_state_change (void *cls, int connected)
{
  char *service_list;

  if (connected)
  {
    service_list = format_service_list (0, NULL);
    GNUNET_FS_GTK_update_connection_indicator (cls, TRUE, service_list);
    GNUNET_free (service_list);
    GNUNET_ARM_request_service_list (arm, &service_list_callback, cls);
  }
  else
    GNUNET_FS_GTK_update_connection_indicator (
      cls,
      FALSE,
      _ ("Can't connect to the Automatic Restart Manager service."));
}


static void
service_status_change (void *cls,
                       const char *service,
                       enum GNUNET_ARM_ServiceMonitorStatus status)
{
  /* Very crude, we can probably do better.
   * Maybe keep a list of running services, and modify it in response
   * to service status changes, then update the indicator,
   * without requesting a list from ARM every goddamned time?
   */
  GNUNET_ARM_request_service_list (arm, &service_list_callback, cls);
}


static void
monitor_zone_error (void *cls)
{
  GtkListStore *ls;

  ls = GTK_LIST_STORE (
    GNUNET_FS_GTK_get_main_window_object ("namespace_label_liststore"));
  gtk_list_store_clear (ls);
  gtk_widget_hide (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object (
                                 "main_window_search_namespace_combobox")));
}


static void
monitor_zone_sync (void *cls)
{
  gtk_widget_show (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object (
                                 "main_window_search_namespace_combobox")));
}


/**
 * Process a record that was stored in the namestore in the
 * "sks_zone".  Adds (or removes) the respective label to the
 * drop-down menu for the SKS search by manipulating the
 * "namespace_label_liststore".
 *
 * @param cls closure
 * @param zone private key of the zone; NULL on disconnect
 * @param label label of the records; NULL on disconnect
 * @param rd_count number of entries in @a rd array; 0 on removal
 * @param rd array of records for the label
 */
static void
monitor_zone_records (void *cls,
                      const struct GNUNET_IDENTITY_PrivateKey *zone,
                      const char *label,
                      unsigned int rd_count,
                      const struct GNUNET_GNSRECORD_Data *rd)
{
  GtkListStore *ls;
  GtkTreeModel *tm;
  GtkTreeIter iter;
  gchar *id;
  gchar *label_gnu;

  GNUNET_NAMESTORE_zone_monitor_next (main_context.zm, 1);
  ls = GTK_LIST_STORE (
    GNUNET_FS_GTK_get_main_window_object ("namespace_label_liststore"));
  label_gnu = g_strdup_printf ("%s.%s", label, "gnu");
  if (0 == rd_count)
  {
    tm = GTK_TREE_MODEL (ls);
    if (gtk_tree_model_get_iter_first (tm, &iter))
      do
      {
        gtk_tree_model_get (tm, &iter, 0, &id, -1);
        if (0 == strcmp (id, label_gnu))
        {
          GNUNET_assert (gtk_list_store_remove (ls, &iter));
          g_free (id);
          g_free (label_gnu);
          return;
        }
        g_free (id);
      } while (gtk_tree_model_iter_next (tm, &iter));
    GNUNET_break (0);
    return;
  }
  gtk_list_store_insert_with_values (ls, &iter, -1, 0, label_gnu, -1);
  g_free (label_gnu);
}


/**
 * The identity service has given us the ego we should use for resolving
 * namepace names.
 *
 * @param cls closure
 * @param ego ego handle, NULL for none
 * @param ctx context for application to store data for this ego
 *                 (during the lifetime of this process, initially NULL)
 * @param name name assigned by the user for this ego,
 *                   NULL if the user just deleted the ego and it
 *                   must thus no longer be used
 */
static void
handle_sks_zone_identity (void *cls,
                          struct GNUNET_IDENTITY_Ego *ego)
{
  main_context.id_op = NULL;

  if (NULL == ego)
  {
    GNUNET_log (
      GNUNET_ERROR_TYPE_WARNING,
      _ (
        "No default ego specified for `fs-sks` service, will not enable namespace search.\n"));
    return;
  }
  main_context.sks_zone = ego;
  gtk_widget_show (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object (
                                 "main_window_search_namespace_label")));
  gtk_widget_show (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object (
                                 "main_window_search_namespace_combobox")));
  if (NULL != main_context.zm)
  {
    GNUNET_NAMESTORE_zone_monitor_stop(main_context.zm);
    main_context.zm = NULL;
  }
  main_context.zm =
    GNUNET_NAMESTORE_zone_monitor_start (main_context.cfg,
                                         GNUNET_IDENTITY_ego_get_private_key (
                                           main_context.sks_zone),
                                         GNUNET_YES,
                                         &monitor_zone_error,
                                         NULL,
                                         &monitor_zone_records,
                                         NULL,
                                         &monitor_zone_sync,
                                         NULL);
}


/**
 * The user selected another identity in the combobox.  Load it.
 *
 * @param widget the combo box where the selection was changed
 * @param user_data the builder, unused
 */
void
gnunet_fs_gtk_id_combobox_changed_cb (GtkComboBox *widget,
                                      gpointer user_data)
{
  GtkTreeIter iter;
  struct GNUNET_IDENTITY_Ego *ego;
  char *name;

  (void) user_data;
  if (! gtk_combo_box_get_active_iter (id_combo_box, &iter))
  {
    return;
  }
  id_iter = iter;
  /* clang-format off */
  gtk_tree_model_get (GTK_TREE_MODEL (id_liststore),
                      &iter,
                      ID_LS_NAME, &name,
                      ID_LS_EGO, &ego,
                      -1);
  /* clang-format on */
  handle_sks_zone_identity (name, ego);
}

/**
 * Method called to inform about the egos of this peer.  Updates the
 * `zone_liststore`.
 *
 * When used with #GNUNET_IDENTITY_connect, this function is
 * initially called for all egos and then again whenever a
 * ego's name changes or if it is deleted.  At the end of
 * the initial pass over all egos, the function is once called
 * with 'NULL' for @a ego. That does NOT mean that the callback won't
 * be invoked in the future or that there was an error.
 *
 * If @a ego is non-NULL and if '*ctx' is set in those callbacks, the
 * value WILL be passed to a subsequent call to the identity callback
 * of #GNUNET_IDENTITY_connect (if that one was not NULL).
 *
 * When an identity is renamed, this function is called with the
 * (known) @a ego but the NEW @a name.
 *
 * When an identity is deleted, this function is called with the
 * (known) ego and "NULL" for the @a name.  In this case,
 * the @a ego is henceforth invalid (and the @a ctx should also be
 * cleaned up).
 *
 * @param cls closure
 * @param ego ego handle
 * @param ctx context for application to store data for this ego
 *                 (during the lifetime of this process, initially NULL)
 * @param name name assigned by the user for this ego,
 *                   NULL if the user just deleted the ego and it
 *                   must thus no longer be used
 */
static void
identity_cb (void *cls,
             struct GNUNET_IDENTITY_Ego *ego,
             void **ctx,
             const char *name)
{
  GtkTreeRowReference *rr;
  GtkTreeIter iter;
  GtkTreePath *path;

  if (in_shutdown)
    return;
  if ((NULL == ego) && (NULL == name) && (NULL == ctx))
  {
    /* end of initial iteration, trigger loading selected zone */
    gnunet_fs_gtk_id_combobox_changed_cb (id_combo_box, ml);
    return;
  }
  rr = *ctx;
  if (NULL == rr)
  {
    /* clang-format off */
    gtk_list_store_insert_with_values (id_liststore,
                                       &iter, 0,
                                       ID_LS_NAME, name,
                                       ID_LS_EGO, ego,
                                       -1);
    /* clang-format on */
    gtk_combo_box_set_active_iter (id_combo_box, &iter);
    gtk_widget_set_sensitive (GTK_WIDGET (id_combo_box), TRUE);
    path = gtk_tree_model_get_path (GTK_TREE_MODEL (id_liststore), &iter);
    rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (id_liststore), path);
    *ctx = rr;
    gtk_tree_path_free (path);
    return;
  }
  path = gtk_tree_row_reference_get_path (rr);
  GNUNET_break (
    gtk_tree_model_get_iter (GTK_TREE_MODEL (id_liststore), &iter, path));
  gtk_tree_path_free (path);
  if (NULL == name)
  {
    GtkTreeIter act_iter;

    /* identity was removed, remove from list */
    gtk_list_store_remove (id_liststore, &iter);
    if (! gtk_combo_box_get_active_iter (id_combo_box, &act_iter))
    {
      if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (id_liststore),
                                         &act_iter))
      {
        /* make sure combo box remains selected if possible */
        gtk_combo_box_set_active (id_combo_box, 0);
      }
      else
      {
        /* make combo box insensitive if nothing can be selected */
        gtk_widget_set_sensitive (GTK_WIDGET (id_combo_box), FALSE);
      }
    }
    return;
  }
  /* identity was renamed, rename in model */
  gtk_list_store_set (id_liststore, &iter, ID_LS_NAME, name, -1);
}

/**
 * Actual main function run right after GNUnet's scheduler
 * is initialized.  Initializes up GTK and Glade.
 *
 * @param cls handle to the main loop (`struct GNUNET_GTK_MainLoop`)
 */
static void
run (void *cls)
{
  unsigned long long dl_parallel;
  unsigned long long req_parallel;
  unsigned long long window_x;
  unsigned long long window_y;
  unsigned long long window_width;
  unsigned long long window_height;
  int maximized;

  ml = cls;
  /* setup main context */
  if (GNUNET_OK != GNUNET_GTK_main_loop_build_window (ml, &main_context))
    return;
  main_context.builder = GNUNET_GTK_main_loop_get_builder (ml);
  main_context.cfg = GNUNET_GTK_main_loop_get_configuration (ml);
  main_context.search_ns_treestore =
    GTK_TREE_STORE (GNUNET_FS_GTK_get_main_window_object (
                      "main_window_search_namespace_treestore"));
  main_context.main_window = GTK_WIDGET (
    GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window"));
  main_context.ns_selector_treeview = GTK_TREE_VIEW (
    GNUNET_FS_GTK_get_main_window_object ("namespace_selector_treeview"));
  main_context.ns_selector_window = GTK_WIDGET (
    GNUNET_FS_GTK_get_main_window_object ("namespace_selector_window"));
  main_context.ns_dropdown_button =
    GTK_TOGGLE_BUTTON (GNUNET_FS_GTK_get_main_window_object (
                         "main_window_search_namespace_dropdown_button"));
  main_context.search_ns_label =
    GTK_LABEL (GNUNET_FS_GTK_get_main_window_object (
                 "main_window_search_selected_namespace_label"));

  main_context.search_entry = GTK_ENTRY (
    GNUNET_FS_GTK_get_main_window_object ("main_window_search_entry"));

  downloads_treestore = GTK_TREE_STORE (GNUNET_FS_GTK_get_main_window_object (
                                          "GNUNET_GTK_file_sharing_downloads_tree_store"));
  main_context.anonymity_combo =
    GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object (
                     "main_window_search_anonymity_combobox"));
  main_context.anonymity_level_liststore = GTK_LIST_STORE (
    GNUNET_FS_GTK_get_main_window_object ("anonymity_level_liststore"));

  main_context.preview_image = GTK_IMAGE (GNUNET_FS_GTK_get_main_window_object (
                                            "GNUNET_GTK_main_window_preview_image"));
  main_context.md_liststore = GTK_LIST_STORE (
    GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_meta_data_list_store"));
  main_context.md_treeview =
    GTK_TREE_VIEW (GNUNET_FS_GTK_get_main_window_object (
                     "GNUNET_GTK_main_window_metadata_treeview"));
  main_context.ns_discovery_handle = NULL;

  main_context.download_location_chooser =
    GTK_FILE_CHOOSER (GNUNET_FS_GTK_get_main_window_object (
                        "GNUNET_GTK_search_frame_download_location_chooser"));
  main_context.download_name_entry =
    GTK_ENTRY (GNUNET_FS_GTK_get_main_window_object (
                 "GNUNET_GTK_search_frame_download_filename_entry"));
  main_context.download_anonymity_combo =
    GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object (
                     "main_window_download_anonymity_combobox"));
  main_context.download_recursive_checkbutton =
    GTK_CHECK_BUTTON (GNUNET_FS_GTK_get_main_window_object (
                        "GNUNET_GTK_search_frame_download_recursive_checkbox"));
  main_context.download_download_button =
    GTK_BUTTON (GNUNET_FS_GTK_get_main_window_object (
                  "GNUNET_GTK_search_frame_download_download_button"));
  main_context.download_panel = GTK_BOX (GNUNET_FS_GTK_get_main_window_object (
                                           "GNUNET_GTK_search_frame_download_vbox"));

  main_context.notebook = GTK_NOTEBOOK (
    GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook"));

  main_context.connection_indicator =
    GTK_IMAGE (GNUNET_FS_GTK_get_main_window_object (
                 "GNUNET_FS_GTK_main_window_connection_indicator"));

  status_label = GTK_LABEL (get_object (
                              "gnunet_fs_gtk_status_label"));
  id_combo_box =
    GTK_COMBO_BOX (get_object ("gnunet_fs_gtk_id_combobox"));
  id_liststore = GTK_LIST_STORE (get_object ("id_liststore"));
  GNUNET_GTK_set_icon_search_path ();
  GNUNET_GTK_setup_nls ();

  /* Make sure button class is realized */
  g_type_class_unref (g_type_class_ref (GTK_TYPE_BUTTON));
  /* GNUnet main window assumes that images on buttons are visible,
   * override the theme's gtkrc setting
   */
  g_object_set (gtk_settings_get_default (), "gtk-button-images", TRUE, NULL);

  /* setup main window */
  maximized = GNUNET_CONFIGURATION_get_value_yesno (main_context.cfg,
                                                    "gnunet-fs-gtk",
                                                    "MAIN_WINDOW_MAXIMIZED");
  if (GNUNET_SYSERR == maximized)
    maximized = GNUNET_YES;
  if ((GNUNET_NO == maximized) &&
      (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (main_context.cfg,
                                                           "gnunet-fs-gtk",
                                                           "MAIN_WINDOW_X",
                                                           &window_x)) &&
      (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (main_context.cfg,
                                                           "gnunet-fs-gtk",
                                                           "MAIN_WINDOW_Y",
                                                           &window_y)) &&
      (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (main_context.cfg,
                                                           "gnunet-fs-gtk",
                                                           "MAIN_WINDOW_WIDTH",
                                                           &window_width)) &&
      (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (main_context.cfg,
                                                           "gnunet-fs-gtk",
                                                           "MAIN_WINDOW_HEIGHT",
                                                           &window_height)))
  {
    gtk_window_move (GTK_WINDOW (main_context.main_window), window_x, window_y);
    gtk_window_resize (GTK_WINDOW (main_context.main_window),
                       window_width,
                       window_height);
  }
  else
  {
    /* If anything is wrong - play safe and show it maximized */
    gtk_window_maximize (GTK_WINDOW (main_context.main_window));
  }

  /* Allow multiple selection in metadata view; */
  /* FIXME-GTK3: this can be done within (modern versions of) glade
     (However, this needs GTK >= 3.2, we currently target 3.0) */
  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (
                                 main_context.md_treeview),
                               GTK_SELECTION_MULTIPLE);

  /* FIXME: should these '1's be here? Maybe better to put them into
   * default config files?
   */
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_number (main_context.cfg,
                                             "gnunet-fs-gtk",
                                             "MAX_PARALLEL_DOWNLOADS",
                                             &dl_parallel))
    dl_parallel = DEFAULT_MAX_PARALLEL_DOWNLOADS;
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_number (main_context.cfg,
                                             "gnunet-fs-gtk",
                                             "MAX_PARALLEL_REQUESTS",
                                             &req_parallel))
    req_parallel = DEFAULT_MAX_PARALLEL_REQUESTS;
  /* initialize file-sharing */
  fs = GNUNET_FS_start (main_context.cfg,
                        "gnunet-fs-gtk",
                        &GNUNET_GTK_fs_event_handler,
                        NULL,
                        GNUNET_FS_FLAGS_PERSISTENCE | GNUNET_FS_FLAGS_DO_PROBES,
                        GNUNET_FS_OPTIONS_DOWNLOAD_PARALLELISM,
                        (unsigned int) dl_parallel,
                        GNUNET_FS_OPTIONS_REQUEST_PARALLELISM,
                        (unsigned int) req_parallel,
                        GNUNET_FS_OPTIONS_END);
  if (NULL == fs)
  {
    GNUNET_GTK_main_loop_quit (cls);
    return;
  }
  arm = GNUNET_ARM_connect (main_context.cfg,
                            &arm_connection_state_change,
                            &main_context);
  armon = GNUNET_ARM_monitor_start (main_context.cfg,
                                    &service_status_change,
                                    &main_context);
  main_context.gns = GNUNET_GNS_connect (main_context.cfg);
  main_context.identity = GNUNET_IDENTITY_connect (main_context.cfg,
                                                   &identity_cb, NULL);
#if HAVE_LIBUNIQUE
  unique_app_watch_window (unique_app, GTK_WINDOW (main_context.main_window));
  g_signal_connect (unique_app,
                    "message-received",
                    G_CALLBACK (unique_app_message_cb),
                    &main_context);
#endif
  /* make GUI visible */
  gtk_widget_show (main_context.main_window);
  gtk_window_present (GTK_WINDOW (main_context.main_window));

  {
    char *const *argv;
    int argc;
    int i;

    GNUNET_GTK_main_loop_get_args (ml, &argc, &argv);

    for (i = 0; i < argc; i++)
    {
      if (GNUNET_OK !=
          GNUNET_FS_GTK_handle_uri_string (argv[i], 1 /* anonymity level */))
        fprintf (stderr, "Invalid URI `%s'\n", argv[i]);
    }
  }
  GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
}


int
main (int argc, char **argv)
{
  static struct GNUNET_GETOPT_CommandLineOption options[] = {
    GNUNET_GETOPT_OPTION_END
  };
#if HAVE_LIBUNIQUE
  int arge;

  gtk_init (&argc, &argv);
  unique_app = unique_app_new ("org.gnunet.gnunet-fs-gtk", NULL);
  if (unique_app_is_running (unique_app))
  {
    UniqueResponse response;

    arge = GNUNET_GETOPT_run ("gnunet-fs-gtk", options, argc, argv);
    response = unique_app_send_message (unique_app, UNIQUE_ACTIVATE, NULL);
    while (arge < argc)
    {
      UniqueMessageData *msg;

      msg = unique_message_data_new ();
      unique_message_data_set_text (msg, argv[arge], strlen (argv[arge]) + 1);
      if (UNIQUE_RESPONSE_OK == response)
        response = unique_app_send_message (unique_app, UNIQUE_OPEN, msg);
      unique_message_data_free (msg);
      arge++;
    }
    g_object_unref (unique_app);

    return (UNIQUE_RESPONSE_OK == response) ? 0 : 1;
  }

#endif
  if (GNUNET_OK !=
      GNUNET_GTK_main_loop_start ("gnunet-fs-gtk",
                                  "GTK GUI for GNUnet",
                                  argc,
                                  argv,
                                  options,
                                  "gnunet_fs_gtk_main_window.glade",
                                  &run))
  {
#if HAVE_LIBUNIQUE
    g_object_unref (unique_app);
#endif
    return 1;
  }
#if HAVE_LIBUNIQUE
  g_object_unref (unique_app);
#endif
  return 0;
}


/* end of gnunet-fs-gtk.c */
