/*
     This file is part of GNUnet.
     Copyright (C) 2010-2014 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/peerinfo/gnunet-peerinfo-gtk.c
 * @brief Main function of gnunet-peerinfo-gtk
 * @author Christian Grothoff
 */
#include "gnunet_gtk.h"
#include <gnunet/gnunet_peerinfo_service.h>
#include <gnunet/gnunet_transport_service.h>
#include <gnunet/gnunet_ats_service.h>
#include <gnunet/gnunet_core_service.h>
#include <gnunet/gnunet_friends_lib.h>
#include "gnunet-peerinfo-gtk-flags.h"

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

#define UPDATE_FREQUENCY \
  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)

/**
 * Should we show peers that have no connections and
 * no known (valid, non-expired) addresses?
 */
static int show_inactive;


/**
 * Columns in the peerinfo model.
 */
enum PEERINFO_ModelColumns
{
  /**
   * A gchararray
   */
  PEERINFO_MC_PEER_IDENTITY_STRING = 0,

  /**
   * A gchararray
   */
  PEERINFO_MC_COUNTRY_NAME = 1,

  /**
   * A GdkPixbuf
   */
  PEERINFO_MC_COUNTRY_FLAG = 2,

  /**
   * A guint
   */
  PEERINFO_MC_BANDWIDTH_IN = 3,

  /**
   * A guint
   */
  PEERINFO_MC_BANDWIDTH_OUT = 4,

  /**
   * A GdkPixbuf
   */
  PEERINFO_MC_ATS_CONNECTIVITY_LED = 5,

  /**
   * A gboolean
   */
  PEERINFO_MC_ATS_SELECTED_STATUS = 6,

  /**
   * A GdkPixbuf
   */
  PEERINFO_MC_CORE_CONNECTIVITY_LED = 7,

  /**
   * A guint for sorting by connectivity
   */
  PEERINFO_MC_CORE_CONNECTED_STATUS = 8,

  /**
   * A gboolean
   */
  PEERINFO_MC_IS_FRIEND = 9,

  /**
   * A `struct PeerInfo *`
   */
  PEERINFO_MC_PEERINFO = 10,

  /**
   * A gchararray
   */
  PEERINFO_MC_PLUGIN_NAME = 11,

  /**
   * A gchararray
   */
  PEERINFO_MC_ADDRESS_AS_STRING = 12,

  /**
   * A GdkPixbuf
   */
  PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_LED = 13,

  /**
   * A gboolean
   */
  PEERINFO_MC_NEIGHBOUR_CONNECTED_STATUS = 14,

  /**
   * A gchararray
   */
  PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_TIMEOUT_AS_STRING = 15,

  /**
   * A gchararray
   */
  PEERINFO_MC_NEIGHBOUR_STATE_AS_STRING = 16,

  /**
   * A gchararray
   */
  PEERINFO_MC_NEIGHBOUR_STATE_TIMEOUT_AS_STRING = 17,

  /**
   * A GdkPixbuf
   */
  PEERINFO_MC_PLUGIN_CONNECTIVITY_LED = 18,

  /**
   * A gboolean
   */
  PEERINFO_MC_PLUGIN_CONNECTIVITY_STATUS = 19,

  /**
   * A gchararray
   */
  PEERINFO_MC_PLUGIN_CONNECTIVITY_TIMEOUT_AS_STRING = 20,

  /**
   * A gboolean
   */
  PEERINFO_MC_SHOW_FRIEND = 21,

  /**
   * A gchararray
   */
  PEERINFO_MC_PEERINFO_ADDRESS_EXPIRATION = 22,

  /**
   * Last "column" (does not actually exist).
   */
  PEERINFO_MC_END = 23

};


/**
 * Information we track for each peer outside of the model.
 */
struct PeerInfo;

/**
 * Information about an address of the peer.
 */
struct PeerAddress
{

  /**
   * DLL.
   */
  struct PeerAddress *next;

  /**
   * DLL.
   */
  struct PeerAddress *prev;

  /**
   * Peer this address belongs to.
   */
  struct PeerInfo *pi;

  /**
   * Reference to the peer in the view.
   */
  GtkTreeRowReference *rr;

  /**
   * Handle for address to string conversion.
   */
  struct GNUNET_TRANSPORT_AddressToStringContext *tos;

  /**
   * Binary address, allocated at the end of the struct.
   */
  const void *addr;

  /**
   * Name of the plugin.
   */
  char *plugin;

  /**
   * Address in string format.
   */
  char *address_as_string;

  /**
   * ccTLD.
   */
  char *country_name;

  /**
   * Number of bytes in @e addr.
   */
  size_t addr_len;

  /**
   * ATS bandwidth in for this address.
   */
  guint ats_in;

  /**
   * ATS bandwidth out for this address.
   */
  guint ats_out;

  /**
   * Is this address active in ATS?
   */
  gboolean address_ats_active;
};


/**
 * Information we track for each peer outside of the model.
 */
struct PeerInfo
{
  /**
   * Reference to the peer in the view.
   */
  GtkTreeRowReference *rr;

  /**
   * Handle to an active lookup for addresses of this peer, or NULL.
   */
  struct GNUNET_TRANSPORT_PeerIterateContext *palc;

  /**
   * Identity of the peer for this entry.
   */
  struct GNUNET_PeerIdentity pid;

  /**
   * Head of DLL with addresses of this peer.
   */
  struct PeerAddress *pa_head;

  /**
   * Tail of DLL with addresses of this peer.
   */
  struct PeerAddress *pa_tail;

  /**
   * Timeout for the current state in the state machine.
   */
  struct GNUNET_TIME_Absolute state_timeout;

  /**
   * State of this address in the transport state machine.
   */
  enum GNUNET_TRANSPORT_PeerState state;
};


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

/**
 * Handle for our notifications from peerinfo about new peers.
 */
static struct GNUNET_PEERINFO_NotifyContext *pnc;

/**
 * Handle to ATS service.
 */
static struct GNUNET_ATS_PerformanceHandle *ats;

/**
 * Map of peer identities to the respective `struct PeerInfo` for our view.
 */
static struct GNUNET_CONTAINER_MultiPeerMap *peer2info;

/**
 * Monitoring transport neighbours
 */
static struct GNUNET_TRANSPORT_PeerMonitoringContext *pmc;

/**
 * Monitoring transport plugin sessions.
 */
struct GNUNET_TRANSPORT_PluginMonitor *pm;

/**
 * Monitoring core connectivity.
 */
static struct GNUNET_CORE_MonitorHandle *cm;

/**
 * Green status led (connected)
 */
static GdkPixbuf *led_green;

/**
 * Red status led (disconnected)
 */
static GdkPixbuf *led_red;

/**
 * Black status led (error)
 */
static GdkPixbuf *led_black;

/**
 * Yellow status led (connecting)
 */
static GdkPixbuf *led_yellow;

/**
 * Blue status led (reconnecting/rekeying/special operation)
 */
static GdkPixbuf *led_blue;

/**
 * Main window tree store.
 */
static GtkTreeStore *ts;

/**
 * Map of all of our friends.
 */
static struct GNUNET_CONTAINER_MultiPeerMap *friends;

/**
 * Tree view column for CORE connecitivity.
 */
static GtkTreeViewColumn *tvc_core_connectivity;

/**
 * Tree view column for TRANSPORT connecitivity.
 */
static GtkTreeViewColumn *tvc_transport_connectivity;

/**
 * Tree view column for ATS connecitivity.
 */
static GtkTreeViewColumn *tvc_ats_connectivity;

/**
 * Tree view column for plugin connecitivity.
 */
static GtkTreeViewColumn *tvc_plugin_connectivity;

/**
 * The main tree view.
 */
static GtkTreeView *tv;

/**
 * Task to actually update the tree view.
 */
static struct GNUNET_SCHEDULER_Task *update_task;


#if HAVE_LIBUNIQUE
static UniqueApp *unique_app;
#endif


/**
 * Periodically unfreezes the tree view to allow updates to show.
 *
 * @param cls NULL
 */
static void
do_update (void *cls)
{
  static int phase;

  (void) cls;
  phase++;
  if (0 == (phase % 2))
  {
    gtk_widget_thaw_child_notify (GTK_WIDGET (tv));
    update_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MILLISECONDS,
                                                &do_update,
                                                NULL);
  }
  else
  {
    gtk_widget_freeze_child_notify (GTK_WIDGET (tv));
    update_task =
      GNUNET_SCHEDULER_add_delayed (UPDATE_FREQUENCY, &do_update, NULL);
  }
}


/**
 * Get cfg.
 */
static const struct GNUNET_CONFIGURATION_Handle *
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
 */
static GObject *
get_object (const char *name)
{
  return GNUNET_GTK_main_loop_get_object (ml, name);
}


/**
 * Free the given peer address which must belong to the
 * given peer.
 *
 * @param info peer the address belongs to
 * @param pa address to free
 */
static void
free_peer_address (struct PeerInfo *info, struct PeerAddress *pa)
{
  GNUNET_CONTAINER_DLL_remove (info->pa_head, info->pa_tail, pa);
  if (NULL != pa->tos)
  {
    GNUNET_TRANSPORT_address_to_string_cancel (pa->tos);
    pa->tos = NULL;
  }
  gtk_tree_row_reference_free (pa->rr);
  GNUNET_free (pa->plugin);
  GNUNET_free (pa->address_as_string);
  GNUNET_free (pa->country_name);
  GNUNET_free (pa);
}


/**
 * Function called on each entry in the #peer2info map
 * to free the associated path.
 *
 * @param cts unused
 * @param key peer identity
 * @param value the `struct PeerInfo`
 * @return #GNUNET_OK (continue to iterate)
 */
static int
free_paths (void *cts, const struct GNUNET_PeerIdentity *key, void *value)
{
  struct PeerInfo *info = value;
  struct PeerAddress *pa;

  while (NULL != (pa = info->pa_head))
    free_peer_address (info, pa);
  gtk_tree_row_reference_free (info->rr);
  GNUNET_free (info);
  return GNUNET_OK;
}


/**
 * Task run on shutdown.
 *
 * @param cts unused
 */
static void
shutdown_task (void *cts)
{
  if (NULL != update_task)
  {
    GNUNET_SCHEDULER_cancel (update_task);
    update_task = NULL;
  }
  if (NULL == ml)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_GTK_main_loop_quit (ml);
  if (NULL != pnc)
  {
    GNUNET_PEERINFO_notify_cancel (pnc);
    pnc = NULL;
  }
  if (NULL != ats)
  {
    GNUNET_ATS_performance_done (ats);
    ats = NULL;
  }
  if (NULL != pmc)
  {
    GNUNET_TRANSPORT_monitor_peers_cancel (pmc);
    pmc = NULL;
  }
  if (NULL != pm)
  {
    GNUNET_TRANSPORT_monitor_plugins_cancel (pm);
    pm = NULL;
  }
  if (NULL != cm)
  {
    GNUNET_CORE_monitor_stop (cm);
    cm = NULL;
  }
  GNUNET_CONTAINER_multipeermap_iterate (peer2info, &free_paths, NULL);
  GNUNET_CONTAINER_multipeermap_destroy (peer2info);
  peer2info = NULL;
  GNUNET_PEERINFO_GTK_flags_shutdown ();
}


/**
 * Convert a row reference to an iter.
 *
 * @param rr reference to a row (in our #ts)
 * @param iter set to the iter corresponding to @a rr
 */
static void
get_iter_from_rr (GtkTreeRowReference *rr, GtkTreeIter *iter)
{
  GtkTreePath *path;

  path = gtk_tree_row_reference_get_path (rr);
  GNUNET_assert (NULL != path);
  GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ts), iter, path));
  gtk_tree_path_free (path);
}


/**
 * Function to call with the text format of an address
 *
 * @param cts the `struct PeerAddress` for the address
 * @param address address as a string, NULL on last call
 * @param res result of the address to string conversion:
 *        if #GNUNET_OK: address contains address as string
 *        if #GNUNET_SYSERR: address is invalid
 */
static void
peer_address_string_cb (void *cts, const char *address, int res)
{
  struct PeerAddress *pa = cts;
  GtkTreeIter iter;
  char *country;
  const char *colon;
  const char *dot;

  if (NULL == address)
  {
    pa->tos = NULL;
    return;
  }
  if (GNUNET_SYSERR == res)
    return; /* don't care */
  get_iter_from_rr (pa->rr, &iter);
  country = NULL;
  colon = strstr (address, ":");
  if (NULL != colon)
  {
    for (dot = colon - 1; dot != address; dot--)
      if ('.' == *dot)
        break;
    if ('.' == *dot)
      country = GNUNET_strndup (&dot[1], (colon - dot) - 1);
  }
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_COUNTRY_NAME,
                      country,
                      PEERINFO_MC_COUNTRY_FLAG,
                      GNUNET_PEERINFO_GTK_get_flag (country),
                      PEERINFO_MC_ADDRESS_AS_STRING,
                      address,
                      -1);
  if (pa->address_ats_active)
  {
    get_iter_from_rr (pa->pi->rr, &iter);
    gtk_tree_store_set (ts,
                        &iter,
                        PEERINFO_MC_COUNTRY_NAME,
                        country,
                        PEERINFO_MC_COUNTRY_FLAG,
                        GNUNET_PEERINFO_GTK_get_flag (country),
                        PEERINFO_MC_ADDRESS_AS_STRING,
                        address,
                        PEERINFO_MC_PLUGIN_NAME,
                        pa->plugin,
                        -1);
  }
  pa->address_as_string = GNUNET_strdup (address);
  pa->country_name = country;
}


/**
 * Obtain the address entry for the given address at the given
 * peer.  If the address entry does not yet exist, create it.
 *
 * @param pi peer info of the peer
 * @param addr peer's address
 * @return address entry for the given address
 */
static struct PeerAddress *
get_address (struct PeerInfo *pi, const struct GNUNET_HELLO_Address *addr)
{
  static struct PeerAddress *pa;
  GtkTreeIter iter;
  GtkTreeIter aiter;
  GtkTreePath *path;

  for (pa = pi->pa_head; NULL != pa; pa = pa->next)
    if ((addr->address_length == pa->addr_len) &&
        (0 == memcmp (addr->address, pa->addr, addr->address_length)) &&
        (0 == strcmp (addr->transport_name, pa->plugin)))
      return pa;
  pa = GNUNET_malloc (sizeof (struct PeerAddress) + addr->address_length);
  pa->plugin = GNUNET_strdup (addr->transport_name);
  pa->addr = &pa[1];
  pa->pi = pi;
  memcpy (&pa[1], addr->address, addr->address_length);
  pa->addr_len = addr->address_length;
  GNUNET_CONTAINER_DLL_insert (pi->pa_head, pi->pa_tail, pa);
  path = gtk_tree_row_reference_get_path (pi->rr);
  GNUNET_assert (NULL != path);
  GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ts), &iter, path));
  gtk_tree_path_free (path);
  gtk_tree_store_insert_with_values (ts,
                                     &aiter,
                                     &iter,
                                     -1 /* append */,
                                     PEERINFO_MC_PLUGIN_NAME,
                                     addr->transport_name,
                                     -1);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (ts), &aiter);
  pa->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (ts), path);
  GNUNET_assert (NULL != pa->rr);
  gtk_tree_path_free (path);
  pa->tos = GNUNET_TRANSPORT_address_to_string (get_configuration (),
                                                addr,
                                                GNUNET_NO,
                                                GNUNET_TIME_UNIT_FOREVER_REL,
                                                &peer_address_string_cb,
                                                pa);

  return pa;
}


/**
 * Function to call with a binary format of an address
 *
 * @param cts the `struct PeerInfo` for which this is a valid address
 * @param address an address of the peer
 * @param expiration expiration time for that address
 * @return #GNUNET_OK (keep iterating)
 */
static int
peer_address_cb (void *cts,
                 const struct GNUNET_HELLO_Address *address,
                 struct GNUNET_TIME_Absolute expiration)
{
  struct PeerInfo *info = cts;
  struct PeerAddress *pa;
  GtkTreeIter iter;
  GtkTreePath *path;

  path = gtk_tree_row_reference_get_path (info->rr);
  GNUNET_assert (NULL != path);
  GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ts), &iter, path));
  gtk_tree_path_free (path);
  pa = get_address (info, address);
  get_iter_from_rr (pa->rr, &iter);
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_PEERINFO_ADDRESS_EXPIRATION,
                      GNUNET_STRINGS_absolute_time_to_string (expiration),
                      -1);
  return GNUNET_OK;
}


/**
 * Obtain the `struct PeerInfo` for the given peer; if it does
 * not yet exist, add it.
 *
 * @param peer peer identity to get the `struct PeerInfo` for
 * @return the corresponding struct
 */
static struct PeerInfo *
get_peer_info (const struct GNUNET_PeerIdentity *peer)
{
  GtkTreeIter iter;
  GtkTreePath *path;
  struct PeerInfo *info;

  info = GNUNET_CONTAINER_multipeermap_get (peer2info, peer);
  if (NULL != info)
    return info;
  info = GNUNET_new (struct PeerInfo);
  info->pid = *peer;
  gtk_tree_store_append (ts, &iter, NULL);

  if (NULL != friends)
  {
    gtk_tree_store_set (ts,
                        &iter,
                        PEERINFO_MC_IS_FRIEND,
                        GNUNET_CONTAINER_multipeermap_contains (friends, peer),
                        -1);
  }
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_PEER_IDENTITY_STRING,
                      GNUNET_i2s (peer),
                      PEERINFO_MC_PEERINFO,
                      info,
                      PEERINFO_MC_SHOW_FRIEND,
                      TRUE,
                      -1);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (ts), &iter);
  info->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (ts), path);
  GNUNET_assert (NULL != info->rr);
  gtk_tree_path_free (path);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CONTAINER_multipeermap_put (
                   peer2info,
                   peer,
                   info,
                   GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  return info;
}


/**
 * Function called for peers that we know about.
 *
 * @param cts closure
 * @param peer id of the peer, NULL for last call
 * @param hello hello message for the peer (can be NULL)
 * @param err_msg NULL if successful, otherwise contains error message
 */
static void
peerinfo_processor (void *cts,
                    const struct GNUNET_PeerIdentity *peer,
                    const struct GNUNET_HELLO_Message *hello,
                    const char *err_msg)
{
  struct PeerInfo *info;

  if ((NULL == hello) ||
      ((! show_inactive) && (0 == GNUNET_TIME_absolute_get_remaining (
                                    GNUNET_HELLO_get_last_expiration (hello))
                                    .rel_value_us)))
    return;
  info = get_peer_info (peer);
  GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &peer_address_cb, info);
}


/**
 * ATS disconnected, remove ATS status information for all peers.
 *
 * @param cls NULL
 * @param pid peer identity to reset
 * @param value the `struct PeerInfo` for the peer
 * @return #GNUNET_OK
 */
static int
reset_ats_status (void *cls, const struct GNUNET_PeerIdentity *pid, void *value)
{
  struct PeerInfo *info = value;
  GtkTreeIter iter;

  get_iter_from_rr (info->rr, &iter);
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_ATS_CONNECTIVITY_LED,
                      NULL,
                      PEERINFO_MC_ATS_SELECTED_STATUS,
                      FALSE,
                      PEERINFO_MC_BANDWIDTH_IN,
                      0,
                      PEERINFO_MC_BANDWIDTH_OUT,
                      0,
                      -1);
  return GNUNET_OK;
}


/**
 * Method called whenever a given peer has a QoS status change.
 *
 * @param cts closure
 * @param address the address, or NULL if service disconnected
 * @param address_active #GNUNET_YES if this address is actively used
 *        to maintain a connection to a peer;
 *        #GNUNET_NO if the address is not actively used;
 *        #GNUNET_SYSERR if this address is no longer available for ATS
 * @param bandwidth_in available amount of inbound bandwidth
 * @param bandwidth_out available amount of outbound bandwidth
 * @param perf performance data for the address (as far as known)
 */
static void
ats_status_cb (void *cts,
               const struct GNUNET_HELLO_Address *address,
               int address_active,
               struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in,
               struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
               const struct GNUNET_ATS_Properties *prop)
{
  struct PeerInfo *info;
  struct PeerAddress *pa;
  struct PeerAddress *act;
  GtkTreeIter iter;

  if (NULL == address)
  {
    /* state 'reset' / disconnect from service, reset everything */
    GNUNET_CONTAINER_multipeermap_iterate (peer2info, &reset_ats_status, NULL);
    return;
  }

  info = get_peer_info (&address->peer);
  pa = get_address (info, address);
  pa->address_ats_active = address_active;
  pa->ats_in = (guint) ntohl (bandwidth_in.value__);
  pa->ats_out = (guint) ntohl (bandwidth_out.value__);
  get_iter_from_rr (pa->rr, &iter);
  if (GNUNET_SYSERR == address_active)
  {
    gtk_tree_store_remove (ts, &iter);
    free_peer_address (info, pa);
    return;
  }
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_ATS_CONNECTIVITY_LED,
                      (pa->address_ats_active) ? led_green : led_red,
                      PEERINFO_MC_ATS_SELECTED_STATUS,
                      pa->address_ats_active,
                      PEERINFO_MC_BANDWIDTH_IN,
                      pa->ats_in,
                      PEERINFO_MC_BANDWIDTH_OUT,
                      pa->ats_out,
                      -1);
  act = (GNUNET_YES == address_active) ? pa : NULL;
  for (pa = info->pa_head; NULL != pa; pa = pa->next)
  {
    if (GNUNET_YES == pa->address_ats_active)
    {
      if (NULL == act)
      {
        /* first active address found, remember it! */
        act = pa;
      }
      else if (GNUNET_YES == address_active)
      {
        /* second address became active, set BW for first to zero */
        get_iter_from_rr (pa->rr, &iter);
        gtk_tree_store_set (ts,
                            &iter,
                            PEERINFO_MC_BANDWIDTH_IN,
                            (guint) 0,
                            PEERINFO_MC_BANDWIDTH_OUT,
                            (guint) 0,
                            -1);
      }
    }
  }
  get_iter_from_rr (info->rr, &iter);
  if (NULL == act)
  {
    /* no active address, make sure per-peer state is on 'off' */
    gtk_tree_store_set (ts,
                        &iter,
                        PEERINFO_MC_ATS_CONNECTIVITY_LED,
                        led_red,
                        PEERINFO_MC_ATS_SELECTED_STATUS,
                        FALSE,
                        PEERINFO_MC_BANDWIDTH_IN,
                        (guint) 0,
                        PEERINFO_MC_BANDWIDTH_OUT,
                        (guint) 0,
                        PEERINFO_MC_COUNTRY_NAME,
                        NULL,
                        PEERINFO_MC_COUNTRY_FLAG,
                        NULL,
                        PEERINFO_MC_ADDRESS_AS_STRING,
                        NULL,
                        PEERINFO_MC_PLUGIN_NAME,
                        NULL,
                        -1);
  }
  else
  {
    /* update per-peer state to that of active address */
    gtk_tree_store_set (ts,
                        &iter,
                        PEERINFO_MC_ATS_CONNECTIVITY_LED,
                        led_green,
                        PEERINFO_MC_ATS_SELECTED_STATUS,
                        TRUE,
                        PEERINFO_MC_BANDWIDTH_IN,
                        act->ats_in,
                        PEERINFO_MC_BANDWIDTH_OUT,
                        act->ats_out,
                        PEERINFO_MC_COUNTRY_NAME,
                        act->country_name,
                        PEERINFO_MC_COUNTRY_FLAG,
                        GNUNET_PEERINFO_GTK_get_flag (act->country_name),
                        PEERINFO_MC_ADDRESS_AS_STRING,
                        act->address_as_string,
                        PEERINFO_MC_PLUGIN_NAME,
                        act->plugin,
                        -1);
  }
}


/**
 * Transport peer monitor disconnected, reset
 * transport peer status information for all peers.
 *
 * @param cls NULL
 * @param pid peer identity to reset
 * @param value the `struct PeerInfo` for the peer
 * @return #GNUNET_OK
 */
static int
reset_transport_peer_status (void *cls,
                             const struct GNUNET_PeerIdentity *pid,
                             void *value)
{
  struct PeerInfo *info = value;
  GtkTreeIter iter;
  struct PeerAddress *pa;

  get_iter_from_rr (info->rr, &iter);
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_NEIGHBOUR_CONNECTED_STATUS,
                      NULL,
                      PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_TIMEOUT_AS_STRING,
                      NULL,
                      PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_LED,
                      NULL,
                      PEERINFO_MC_NEIGHBOUR_STATE_AS_STRING,
                      NULL,
                      PEERINFO_MC_NEIGHBOUR_STATE_TIMEOUT_AS_STRING,
                      NULL,
                      -1);
  for (pa = info->pa_head; NULL != pa; pa = pa->next)
  {
    get_iter_from_rr (pa->rr, &iter);
    gtk_tree_store_set (ts,
                        &iter,
                        PEERINFO_MC_NEIGHBOUR_CONNECTED_STATUS,
                        FALSE,
                        PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_TIMEOUT_AS_STRING,
                        NULL,
                        PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_LED,
                        NULL,
                        PEERINFO_MC_NEIGHBOUR_STATE_AS_STRING,
                        NULL,
                        PEERINFO_MC_NEIGHBOUR_STATE_TIMEOUT_AS_STRING,
                        NULL,
                        -1);
  }
  return GNUNET_OK;
}


/**
 * Function to call with information about a peer
 *
 * @param cts closure
 * @param peer peer this update is about,
 *      NULL if this is the final last callback for a iteration operation
 * @param address address, NULL for disconnect notification in monitor mode
 * @param state current state this peer is in
 * @param state_timeout timeout for the current state of the peer
 */
static void
transport_peer_cb (void *cts,
                   const struct GNUNET_PeerIdentity *peer,
                   const struct GNUNET_HELLO_Address *address,
                   enum GNUNET_TRANSPORT_PeerState state,
                   struct GNUNET_TIME_Absolute state_timeout)
{
  struct PeerInfo *info;
  GtkTreeIter iter;
  gboolean con;
  const char *tos;
  struct PeerAddress *pa;
  GdkPixbuf *led;

  if (NULL == peer)
  {
    /* disconnect notification, reset all state */
    GNUNET_CONTAINER_multipeermap_iterate (peer2info,
                                           &reset_transport_peer_status,
                                           NULL);
    return;
  }
  switch (state)
  {
  case GNUNET_TRANSPORT_PS_NOT_CONNECTED:
    led = led_red;
    break;
  case GNUNET_TRANSPORT_PS_INIT_ATS:
    led = led_yellow;
    break;
  case GNUNET_TRANSPORT_PS_SYN_SENT:
    led = led_yellow;
    break;
  case GNUNET_TRANSPORT_PS_SYN_RECV_ATS:
    led = led_yellow;
    break;
  case GNUNET_TRANSPORT_PS_SYN_RECV_ACK:
    led = led_yellow;
    break;
  case GNUNET_TRANSPORT_PS_CONNECTED:
    led = led_green;
    break;
  case GNUNET_TRANSPORT_PS_RECONNECT_ATS:
    led = led_blue;
    break;
  case GNUNET_TRANSPORT_PS_RECONNECT_SENT:
    led = led_blue;
    break;
  case GNUNET_TRANSPORT_PS_SWITCH_SYN_SENT:
    led = led_green;
    break;
  case GNUNET_TRANSPORT_PS_DISCONNECT:
    led = led_red;
    break;
  case GNUNET_TRANSPORT_PS_DISCONNECT_FINISHED:
    led = NULL;
    break;
  default:
    GNUNET_break (0);
    led = NULL;
    break;
  }
  info = get_peer_info (peer);
  info->state = state;
  info->state_timeout = state_timeout;
  con = (GNUNET_YES == GNUNET_TRANSPORT_is_connected (state));
  get_iter_from_rr (info->rr, &iter);
  tos = GNUNET_STRINGS_absolute_time_to_string (state_timeout);
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_NEIGHBOUR_CONNECTED_STATUS,
                      con,
                      PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_TIMEOUT_AS_STRING,
                      con ? tos : NULL,
                      PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_LED,
                      led,
                      PEERINFO_MC_NEIGHBOUR_STATE_AS_STRING,
                      GNUNET_TRANSPORT_ps2s (state),
                      PEERINFO_MC_NEIGHBOUR_STATE_TIMEOUT_AS_STRING,
                      tos,
                      -1);
  for (pa = info->pa_head; NULL != pa; pa = pa->next)
  {
    get_iter_from_rr (pa->rr, &iter);
    gtk_tree_store_set (ts,
                        &iter,
                        PEERINFO_MC_NEIGHBOUR_CONNECTED_STATUS,
                        FALSE,
                        PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_TIMEOUT_AS_STRING,
                        NULL,
                        PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_LED,
                        NULL,
                        PEERINFO_MC_NEIGHBOUR_STATE_AS_STRING,
                        NULL,
                        PEERINFO_MC_NEIGHBOUR_STATE_TIMEOUT_AS_STRING,
                        NULL,
                        -1);
  }
  if (NULL == address)
    return;
  pa = get_address (info, address);
  get_iter_from_rr (pa->rr, &iter);
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_NEIGHBOUR_CONNECTED_STATUS,
                      con,
                      PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_TIMEOUT_AS_STRING,
                      con ? tos : NULL,
                      PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_LED,
                      led,
                      PEERINFO_MC_NEIGHBOUR_STATE_AS_STRING,
                      GNUNET_TRANSPORT_ps2s (state),
                      PEERINFO_MC_NEIGHBOUR_STATE_TIMEOUT_AS_STRING,
                      tos,
                      -1);
}


/**
 * Function called by the plugin with information about the
 * current sessions managed by the plugin (for monitoring).
 *
 * @param cls closure
 * @param session session handle this information is about,
 *        NULL to indicate that we are "in sync" (initial
 *        iteration complete)
 * @param session_ctx storage location where the application
 *        can store data; will point to NULL on #GNUNET_TRANSPORT_SS_INIT,
 *        and must be reset to NULL on #GNUNET_TRANSPORT_SS_DONE
 * @param sinfo information about the state of the session,
 *        NULL if @a session is also NULL and we are
 *        merely signalling that the initial iteration is over;
 *        NULL with @a session being non-NULL if the monitor
 *        was being cancelled while sessions were active
 */
static void
transport_plugin_cb (void *cls,
                     struct GNUNET_TRANSPORT_PluginSession *session,
                     void **session_ctx,
                     const struct GNUNET_TRANSPORT_SessionInfo *sinfo)
{
  struct PeerInfo *info;
  GtkTreeIter iter;
  const char *tos;
  struct PeerAddress *pa;
  GdkPixbuf *led;

  if ((NULL == session) && (NULL == sinfo))
  {
    /* initial iteration completed, ignore */
    return;
  }
  if (NULL == sinfo)
  {
    /* in shutdown, ignore */
    return;
  }
  switch (sinfo->state)
  {
  case GNUNET_TRANSPORT_SS_INIT:
    led = led_red;
    break;
  case GNUNET_TRANSPORT_SS_HANDSHAKE:
    led = led_yellow;
    break;
  case GNUNET_TRANSPORT_SS_UP:
    led = led_green;
    break;
  case GNUNET_TRANSPORT_SS_UPDATE:
    led = led_green;
    break;
  case GNUNET_TRANSPORT_SS_DONE:
    led = NULL;
    break;
  default:
    GNUNET_break (0);
    led = NULL;
    break;
  }
  if (NULL == sinfo->address)
  {
    GNUNET_break (0); // is this OK?
    return;
  }
  info = get_peer_info (&sinfo->address->peer);
  tos = GNUNET_STRINGS_absolute_time_to_string (sinfo->session_timeout);
  if (NULL == led)
    tos = NULL;
  pa = get_address (info, sinfo->address);
  get_iter_from_rr (pa->rr, &iter);
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_PLUGIN_CONNECTIVITY_STATUS,
                      (led_green == led),
                      PEERINFO_MC_PLUGIN_CONNECTIVITY_LED,
                      led,
                      PEERINFO_MC_PLUGIN_CONNECTIVITY_TIMEOUT_AS_STRING,
                      tos,
                      -1);
}


/**
 * Core disconnected, remove CORE KX status information for all peers.
 *
 * @param cls NULL
 * @param pid peer identity to reset
 * @param value the `struct PeerInfo` for the peer
 * @return #GNUNET_OK
 */
static int
reset_core_status (void *cls,
                   const struct GNUNET_PeerIdentity *pid,
                   void *value)
{
  struct PeerInfo *info = value;
  GtkTreeIter iter;

  get_iter_from_rr (info->rr, &iter);
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_CORE_CONNECTIVITY_LED,
                      NULL,
                      PEERINFO_MC_CORE_CONNECTED_STATUS,
                      (guint) 0,
                      -1);
  return GNUNET_OK;
}


/**
 * Method called whenever CORE KX state changes for a peer.
 *
 * @param cts closure
 * @param peer peer identity this notification is about
 * @param state current key exchange state of the peer
 * @param timeout when does the current state expire
 */
static void
handle_core_monitor_event (void *cts,
                           const struct GNUNET_PeerIdentity *peer,
                           enum GNUNET_CORE_KxState state,
                           struct GNUNET_TIME_Absolute timeout)
{
  struct PeerInfo *info;
  GtkTreeIter iter;
  GdkPixbuf *led;
  guint status;

  switch (state)
  {
  case GNUNET_CORE_KX_STATE_DOWN:
    led = led_black;
    status = 1;
    break;
  case GNUNET_CORE_KX_STATE_KEY_SENT:
    led = led_yellow;
    status = 2;
    break;
  case GNUNET_CORE_KX_STATE_KEY_RECEIVED:
    led = led_yellow;
    status = 2;
    break;
  case GNUNET_CORE_KX_STATE_UP:
    led = led_green;
    status = 4;
    break;
  case GNUNET_CORE_KX_STATE_REKEY_SENT:
    led = led_blue;
    status = 4;
    break;
  case GNUNET_CORE_KX_PEER_DISCONNECT:
    led = NULL;
    status = 1;
    break;
  case GNUNET_CORE_KX_ITERATION_FINISHED:
    return;
  case GNUNET_CORE_KX_CORE_DISCONNECT:
    GNUNET_CONTAINER_multipeermap_iterate (peer2info, &reset_core_status, NULL);
    return;
  default:
    GNUNET_break (0);
    led = NULL;
    status = -1;
    break;
  }
  info = get_peer_info (peer);
  get_iter_from_rr (info->rr, &iter);
  gtk_tree_store_set (ts,
                      &iter,
                      PEERINFO_MC_CORE_CONNECTIVITY_LED,
                      led,
                      PEERINFO_MC_CORE_CONNECTED_STATUS,
                      status,
                      -1);
}


/**
 * Write a friend to the friends file.
 *
 * @param cts the `struct GNUNET_FRIENDS_Writer`
 * @param friend friend to write to file
 * @param value unused
 * @return #GNUNET_OK if the writing succeeded
 */
static int
write_friend (void *cts, const struct GNUNET_PeerIdentity *friend, void *value)
{
  struct GNUNET_FRIENDS_Writer *w = cts;

  return GNUNET_FRIENDS_write (w, friend);
}


/**
 * Write an updated friends file out to disk.
 */
static void
write_friends ()
{
  struct GNUNET_FRIENDS_Writer *w;

  w = GNUNET_FRIENDS_write_start (get_configuration ());
  if (NULL == w)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_CONTAINER_multipeermap_iterate (friends, &write_friend, w);
  if (GNUNET_OK != GNUNET_FRIENDS_write_stop (w))
  {
    GNUNET_break (0);
    return;
  }
}


/**
 * The user has toggled the 'is friend' column for one of the peers.
 * Update everything.
 *
 * @param cell_renderer the cell renderer that issued the toggle signal
 * @param path which cell was toggled
 * @param user_data our main window builder
 */
void
GNUNET_PEERINFO_GTK_main_window_friends_cellrenderertoggle_toggled_cb (
  GtkCellRendererToggle *cell_renderer,
  gchar *path,
  gpointer user_data)
{
  GtkTreeIter old;
  struct PeerInfo *info;
  gboolean oldvalue;

  if (NULL == friends)
  {
    GNUNET_break (0);
    return;
  }
  if (! gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (ts), &old, path))
  {
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (GTK_TREE_MODEL (ts),
                      &old,
                      PEERINFO_MC_PEERINFO,
                      &info,
                      PEERINFO_MC_IS_FRIEND,
                      &oldvalue,
                      -1);
  gtk_tree_store_set (ts, &old, PEERINFO_MC_IS_FRIEND, ! oldvalue, -1);
  if (oldvalue)
  {
    GNUNET_break (
      1 == GNUNET_CONTAINER_multipeermap_remove_all (friends, &info->pid));
  }
  else
  {
    GNUNET_break (GNUNET_YES ==
                  GNUNET_CONTAINER_multipeermap_put (
                    friends,
                    &info->pid,
                    "true",
                    GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  }
  write_friends ();
}


/**
 * Callback invoked if the application is supposed to exit.
 *
 * @param object
 * @param user_data unused
 */
void
GNUNET_PEERINFO_GTK_quit_cb (GObject *object, gpointer user_data)
{
  GNUNET_SCHEDULER_shutdown ();
}


/**
 * Load LED image from resource file.
 *
 * @param color color of the LED to load
 * @return the image as a GdkPixbuf
 */
static GdkPixbuf *
load_led (const char *color)
{
  GdkPixbuf *pixbuf;
  char *dir;
  char *fn;

  dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
  GNUNET_asprintf (&fn, "%s%s.png", dir, color);
  GNUNET_free (dir);
  pixbuf = gdk_pixbuf_new_from_file (fn, NULL);
  GNUNET_free (fn);
  return pixbuf;
}


/**
 * Add a friend to our friends peer map.
 *
 * @param cts NULL
 * @param friend the friend to add
 */
static void
add_friend (void *cts, const struct GNUNET_PeerIdentity *friend)
{
  if (NULL == friends)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_break (GNUNET_YES ==
                GNUNET_CONTAINER_multipeermap_put (
                  friends,
                  friend,
                  "true",
                  GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
}


/**
 * Return the tooltip for the given coordinate.
 *
 * @param widget our tree view
 * @param x x-coordinate
 * @param y y-coordinate
 * @param keyboard_tip triggered by keyboard?
 * @param tooltip tooltip object to fill
 * @param data NULL
 * @return TRUE if a tooltip was provided
 */
static gboolean
query_tooltip_cb (GtkWidget *widget,
                  gint x,
                  gint y,
                  gboolean keyboard_tip,
                  GtkTooltip *tooltip,
                  gpointer data)
{
  GtkTreeView *tv = GTK_TREE_VIEW (widget);
  GtkTreeModel *model = GTK_TREE_MODEL (ts);
  GtkTreeIter iter;
  GtkTreePath *path;
  GtkTreeViewColumn *column;
  int model_column = -1;
  gchar *tmp;

  if (! gtk_tree_view_get_tooltip_context (tv,
                                           &x,
                                           &y,
                                           keyboard_tip,
                                           &model,
                                           &path,
                                           &iter))
    return FALSE;
  if (! gtk_tree_view_get_path_at_pos (tv, x, y, NULL, &column, NULL, NULL))
    return FALSE;

  if (tvc_core_connectivity == column)
    model_column = -1;
  else if (tvc_plugin_connectivity == column)
    model_column = PEERINFO_MC_PLUGIN_CONNECTIVITY_TIMEOUT_AS_STRING;
  else if (tvc_ats_connectivity == column)
    model_column = PEERINFO_MC_PEERINFO_ADDRESS_EXPIRATION;
  else if (tvc_transport_connectivity == column)
    model_column = PEERINFO_MC_NEIGHBOUR_CONNECTIVITY_TIMEOUT_AS_STRING;
  if (-1 == model_column)
    return FALSE;

  gtk_tree_model_get (model, &iter, model_column, &tmp, -1);
  if (NULL == tmp)
    return FALSE;
  gtk_tooltip_set_text (tooltip, tmp);
  gtk_tree_view_set_tooltip_cell (tv, tooltip, path, column, NULL);
  g_free (tmp);
  gtk_tree_path_free (path);
  return TRUE;
}


/**
 * Actual main function run right after GNUnet's scheduler
 * is initialized.  Initializes up GTK and Glade.
 *
 * @param cts the main loop context
 */
static void
run (void *cts)
{
  GtkWidget *main_window;
  const struct GNUNET_CONFIGURATION_Handle *cfg;
  int can_edit_friends;

  ml = cts;
  if (GNUNET_OK != GNUNET_GTK_main_loop_build_window (ml, NULL))
    return;
  cfg = get_configuration ();
  led_green = load_led ("green");
  led_red = load_led ("red");
  led_black = load_led ("black");
  led_yellow = load_led ("yellow");
  led_blue = load_led ("blue");
  GNUNET_GTK_set_icon_search_path ();
  GNUNET_GTK_setup_nls ();
  can_edit_friends =
    (GNUNET_NO ==
     GNUNET_CONFIGURATION_have_value (cfg, "ARM", "SYSTEM_ONLY")) ||
    (GNUNET_YES ==
     GNUNET_CONFIGURATION_get_value_yesno (cfg, "ARM", "SYSTEM_ONLY"));
  if (can_edit_friends)
  {
    friends = GNUNET_CONTAINER_multipeermap_create (128, GNUNET_NO);
    if (GNUNET_OK != GNUNET_FRIENDS_parse (cfg, &add_friend, NULL))
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  _ ("Failed to parse list of friends\n"));
  }
  else
  {
    gtk_tree_view_column_set_visible (
      GTK_TREE_VIEW_COLUMN (
        get_object ("GNUNET_PEERINFO_GTK_main_window_friends_treeviewcolumn")),
      FALSE);
  }
  peer2info = GNUNET_CONTAINER_multipeermap_create (256, GNUNET_NO);
  cm = GNUNET_CORE_monitor_start (cfg, &handle_core_monitor_event, NULL);
  pnc = GNUNET_PEERINFO_notify (cfg, GNUNET_NO, &peerinfo_processor, NULL);
  pmc = GNUNET_TRANSPORT_monitor_peers (cfg,
                                        NULL,
                                        GNUNET_NO,
                                        &transport_peer_cb,
                                        NULL);
  pm = GNUNET_TRANSPORT_monitor_plugins (cfg, &transport_plugin_cb, NULL);
  ats = GNUNET_ATS_performance_init (cfg, &ats_status_cb, NULL);
  tvc_core_connectivity = GTK_TREE_VIEW_COLUMN (get_object (
    "GNUNET_PEERINFO_GTK_main_window_core_connectivity_treeviewcolumn"));
  tvc_ats_connectivity = GTK_TREE_VIEW_COLUMN (get_object (
    "GNUNET_PEERINFO_GTK_main_window_ats_connectivity_treeviewcolumn"));
  tvc_plugin_connectivity = GTK_TREE_VIEW_COLUMN (get_object (
    "GNUNET_PEERINFO_GTK_main_window_plugin_connectivity_treeviewcolumn"));
  tvc_transport_connectivity = GTK_TREE_VIEW_COLUMN (get_object (
    "GNUNET_PEERINFO_GTK_main_window_transport_connectivity_treeviewcolumn"));
  /* setup main window */
  main_window = GTK_WIDGET (get_object ("GNUNET_PEERINFO_GTK_main_window"));
  ts = GTK_TREE_STORE (get_object ("GNUNET_PEERINFO_GTK_tree_store"));
  GNUNET_assert (NULL != ts);
  gtk_window_maximize (GTK_WINDOW (main_window));
  tv = GTK_TREE_VIEW (get_object ("GNUNET_PEERINFO_GTK_main_window_treeview"));
  g_object_set (tv, "has-tooltip", TRUE, NULL);
  g_signal_connect (tv, "query-tooltip", G_CALLBACK (query_tooltip_cb), NULL);
#if HAVE_LIBUNIQUE
  unique_app_watch_window (unique_app, GTK_WINDOW (main_window));
#endif
  /* make GUI visible */
  gtk_widget_show (main_window);
  gtk_window_present (GTK_WINDOW (main_window));
  update_task =
    GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &do_update, NULL);
  GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
}


/**
 * Main function of gnunet-peerinfo-gtk.
 *
 * @param argc number of arguments
 * @param argv arguments
 * @return 0 on success
 */
int
main (int argc, char **argv)
{
  struct GNUNET_GETOPT_CommandLineOption options[] =
    {GNUNET_GETOPT_option_flag (
       's',
       "show-inactive",
       gettext_noop (
         "show peers even if the are inactive and we know nothing except their public key"),
       &show_inactive),
     GNUNET_GETOPT_OPTION_END};

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

    response = unique_app_send_message (unique_app, UNIQUE_ACTIVATE, NULL);
    g_object_unref (unique_app);
    return (UNIQUE_RESPONSE_OK == response) ? 0 : 1;
  }
#endif

  if (GNUNET_OK !=
      GNUNET_GTK_main_loop_start ("gnunet-peerinfo-gtk",
                                  "GTK GUI for inspecting GNUnet Peers",
                                  argc,
                                  argv,
                                  options,
                                  "gnunet_peerinfo_gtk_main_window.glade",
                                  &run))
    return 1;

#if HAVE_LIBUNIQUE
  g_object_unref (unique_app);
#endif
  return 0;
}


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