/*
     This file is part of GNUnet.
     Copyright (C) 2010-2014, 2018 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/conversation/gnunet-conversation-gtk_contacts.c
 * @brief display the address book with the list of known contacts
 *        and launch calls if user activates entry in address book
 * @author yids
 * @author hark
 * @author Christian Grothoff
 */
#include "gnunet-conversation-gtk.h"
#include "gnunet-conversation-gtk_contacts.h"
#include "gnunet-conversation-gtk_get_label.h"
#include "gnunet-conversation-gtk_phone.h"
#include "gnunet-conversation-gtk_zones.h"


/**
 * Columns in the #contacts_liststore.
 */
enum ContactsListstoreValues
{
  /**
   * Human-readable name of the label in the zone.
   */
  CONTACTS_LS_NAME = 0,

  /**
   * Type of the label (as a `const char *`)
   */
  CONTACTS_LS_TYPE = 1,

  /**
   * Value stored under the label (as a `const char *`)
   */
  CONTACTS_LS_VALUE = 2,

  /**
   * Is the record public (as a `gboolean`)
   */
  CONTACTS_LS_IS_PUBLIC = 3,

  /**
   * Is the 'public' flag editable (once implemented, negation
   * of #CONTACT_IS_PUBLIC), a `gboolean`.
   */
  CONTACTS_LS_IS_EDITABLE = 4
};


/**
 * List of contacts (records).
 */
static GtkListStore *contacts_liststore;

/**
 * Tree model of the contacts treeview, same objects as #contacts_liststore.
 */
static GtkTreeModel *contacts_treemodel;

/**
 * Monitor to view information in our current zone.
 */
static struct GNUNET_NAMESTORE_ZoneMonitor *zone_mon;

/**
 * The tree view widget.
 */
static GtkWidget *contacts_treeview;

/**
 * The "paste" button.
 */
static GtkWidget *b_paste;


/**
 * A row was activated in the contacts list.  Initiate call.
 *
 * @param tree_view view where the row was activated
 * @param path path to the activated element
 * @param column column that was activated
 * @param user_data builder context (unused)
 */
void
gnunet_conversation_gtk_contact_list_treeview_row_activated_cb (
  GtkTreeView *tree_view,
  GtkTreePath *path,
  GtkTreeViewColumn *column,
  gpointer user_data)
{
  char *address;
  gchar *type;
  GtkTreeIter iter;
  gchar *name;
  GtkEntry *address_entry;
  const char *tld;

  GNUNET_assert (NULL != GCG_ZONES_get_selected_zone (&tld));
  if (! gtk_tree_model_get_iter (contacts_treemodel, &iter, path))
  {
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (contacts_treemodel,
                      &iter,
                      CONTACTS_LS_NAME,
                      &name,
                      CONTACTS_LS_TYPE,
                      &type,
                      -1);
  if (0 == strcmp (type, "PKEY"))
  {
    GNUNET_asprintf (&address, "phone.%s.%s", name, tld);
  }
  else
  {
    GNUNET_asprintf (&address, "%s.%s", name, tld);
  }
  g_free (name);
  g_free (type);
  address_entry = GTK_ENTRY (
    GCG_get_main_window_object ("gnunet_conversation_gtk_address_entry"));
  gtk_entry_set_text (address_entry, address);
  GNUNET_free (address);
}


/**
 * Process a record that was stored or modified the namestore by
 * adding/modifying/removing it in the liststore.
 *
 * @param cls closure
 * @param zone_key private key of the zone; NULL on disconnect
 * @param rname label of the records; NULL on disconnect
 * @param rd_len number of entries in @a rd array, 0 if label was deleted
 * @param rd array of records with data to store
 */
static void
display_record (void *cls,
                const struct GNUNET_IDENTITY_PrivateKey *zone_key,
                const char *rname,
                unsigned int rd_len,
                const struct GNUNET_GNSRECORD_Data *rd)
{
  GtkTreeIter iter;
  gboolean do_display;
  const char *type;
  gchar *lname;
  gboolean update;
  gboolean is_public;
  char *value;

  GNUNET_NAMESTORE_zone_monitor_next (zone_mon, 1);
  do_display = FALSE;
  value = NULL;
  for (unsigned int i = 0; i < rd_len; i++)
  {
    switch (rd[i].record_type)
    {
    case GNUNET_GNSRECORD_TYPE_PKEY:
      type = "PKEY";
      do_display = TRUE;
      break;
    case GNUNET_DNSPARSER_TYPE_CNAME:
      type = "CNAME";
      do_display = TRUE;
      break;
    case GNUNET_GNSRECORD_TYPE_PHONE:
      type = "PHONE";
      do_display = TRUE;
      break;
    default:
      /* ignore, not useful for conversation */
      type = NULL; /* make compiler happy */
      break;
    }
    if (do_display)
    {
      value = GNUNET_GNSRECORD_value_to_string (rd[i].record_type,
                                                rd[i].data,
                                                rd[i].data_size);
      if (NULL == value)
      {
        GNUNET_break (0);
        return;
      }
      is_public = (0 == (rd[i].flags & GNUNET_GNSRECORD_RF_PRIVATE));
      break;
    }
    else
    {
      /* make compiler happy */
      is_public = FALSE;
    }
  }

  /* check if exists, if so, update or remove */
  update = FALSE;
  if (gtk_tree_model_get_iter_first (contacts_treemodel, &iter))
  {
    do
    {
      gtk_tree_model_get (contacts_treemodel,
                          &iter,
                          CONTACTS_LS_NAME,
                          &lname,
                          -1);
      if (0 == strcmp (lname, rname))
      {
        if (! do_display)
        {
          /* remove */
          gtk_list_store_remove (contacts_liststore, &iter);
          g_free (lname);
          GNUNET_free (value);
          return;
        }
        /* update */
        update = TRUE;
        break;
      }
      g_free (lname);
    } while (gtk_tree_model_iter_next (contacts_treemodel, &iter));
  }

  /* insert new record */
  if (! do_display)
  {
    GNUNET_assert (NULL == value);
    return;
  }
  if (! update)
    gtk_list_store_append (contacts_liststore, &iter);
  gtk_list_store_set (contacts_liststore,
                      &iter,
                      CONTACTS_LS_NAME,
                      rname,
                      CONTACTS_LS_TYPE,
                      type,
                      CONTACTS_LS_VALUE,
                      value,
                      CONTACTS_LS_IS_PUBLIC,
                      is_public,
                      CONTACTS_LS_IS_EDITABLE,
                      FALSE /* FIXME: editing to be implemented */,
                      -1);
  GNUNET_free (value);
}


/**
 * Function called once the monitor has caught up with the current
 * state of the database.  Will be called AGAIN after each disconnect
 * (record monitor called with 'NULL' for zone_key) once we're again
 * in sync.
 *
 * Could be used to optimize visuals if we block GTK updates while the
 * list is not in sync.
 *
 * @param cls closure
 */
static void
unfreeze_view (void *cls)
{
  gtk_widget_show (contacts_treeview);
}


/**
 * Function called once the monitor has disconnected from the
 * database.
 *
 * @param cls closure
 */
static void
freeze_view (void *cls)
{
  gtk_list_store_clear (contacts_liststore);
  gtk_widget_hide (contacts_treeview);
}


/**
 * A different zone was selected in the zone toggle bar.  Load the
 * appropriate zone.
 *
 * @param widget combobox that was changed, unused
 * @param user_data builder, unused
 */
void
gnunet_conversation_gtk_contacts_zone_combobox_changed_cb (GtkComboBox *widget,
                                                           gpointer user_data)
{
  struct GNUNET_IDENTITY_Ego *ego;
  const struct GNUNET_IDENTITY_PrivateKey *temp_zone_pkey;
  const char *tld;

  if (NULL != zone_mon)
  {
    GNUNET_NAMESTORE_zone_monitor_stop (zone_mon);
    zone_mon = NULL;
  }
  gtk_list_store_clear (contacts_liststore);
  ego = GCG_ZONES_get_selected_zone (&tld);
  if (NULL == ego)
  {
    /* ego deselected (likely shutdown) */
    gtk_widget_set_sensitive (b_paste, FALSE);
    return;
  }
  gtk_widget_hide (contacts_treeview);
  gtk_widget_set_sensitive (b_paste, TRUE);
  temp_zone_pkey = GNUNET_IDENTITY_ego_get_private_key (ego);
  zone_mon = GNUNET_NAMESTORE_zone_monitor_start (GCG_get_configuration (),
                                                  temp_zone_pkey,
                                                  GNUNET_YES,
                                                  &freeze_view,
                                                  NULL,
                                                  &display_record,
                                                  NULL,
                                                  &unfreeze_view,
                                                  NULL);
}


/**
 * User clicked the "paste" button.  Handle data from the clipboard,
 * if it is a "gnunet://gns/"-URI, import it into our current zone /
 * address book.
 *
 * @param clipboard the clipboard
 * @param text the text from the board
 * @param data NULL
 */
static void
handle_paste_data (GtkClipboard *clipboard, const gchar *text, gpointer data)
{
  char *name;
  char *slash;
  const char *label;

  if (NULL == text)
    return; /* clipboard empty */
  if (0 != strncasecmp ("gnunet://gns/", text, strlen ("gnunet://gns/")))
  {
    GCG_log (_ ("Invalid URI `%s'\n"), text);
    return;
  }
  name = GNUNET_strdup (&text[strlen ("gnunet://gns/")]);
  slash = strchr (name, '/');
  if ((NULL != slash) && ('\0' != slash[1]))
  {
    *slash = '\0';
    label = &slash[1];
    if (GNUNET_OK != GNUNET_DNSPARSER_check_label (label))
    {
      GCG_log (_ ("Invalid label `%s' in URI `%s'\n"), label, text);
      GNUNET_free (name);
      return;
    }
    else
    {
      /* got a label already, try to use it */
      GSC_get_label_for_name (name, label);
      GNUNET_free (name);
      return;
    }
  }
  if (NULL != slash)
    *slash = '\0';
  /* don't have a label yet, need to prompt! */
  GSC_get_label_for_name (name, NULL);
  GNUNET_free (name);
}


/**
 * User clicked the "paste" button.  Copy address information
 * from the clipboard into our current zone / address book.
 *
 * @param button the button
 * @param user_data builder (unused)
 */
void
gnunet_conversation_gtk_contacts_paste_button_clicked_cb (GtkButton *button,
                                                          gpointer user_data)
{
  GtkClipboard *cb;

  cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
  gtk_clipboard_request_text (cb, &handle_paste_data, NULL);
}


/**
 * Initialize the contact list
 */
void
GCG_CONTACTS_init ()
{
  contacts_treeview = GTK_WIDGET (GCG_get_main_window_object (
    "gnunet_conversation_gtk_contact_list_treeview"));
#if GTK_CHECK_VERSION(3, 10, 0)
  gtk_tree_view_set_activate_on_single_click (GTK_TREE_VIEW (contacts_treeview),
                                              TRUE);
#endif
  contacts_liststore = GTK_LIST_STORE (
    GCG_get_main_window_object ("gnunet_conversation_gtk_contacts_liststore"));
  contacts_treemodel = GTK_TREE_MODEL (contacts_liststore);
  b_paste = GTK_WIDGET (GCG_get_main_window_object (
    "gnunet_conversation_gtk_contacts_paste_button"));
}


/**
 * Shutdown the contact list
 */
void
GCG_CONTACTS_shutdown ()
{
  if (NULL != zone_mon)
  {
    GNUNET_NAMESTORE_zone_monitor_stop (zone_mon);
    zone_mon = NULL;
  }
  gtk_list_store_clear (contacts_liststore);
  contacts_liststore = NULL;
  contacts_treemodel = NULL;
}

/* end of gnunet-conversation-gtk_contacts.c */
