/*
     This file is part of GNUnet
     Copyright (C) 2011, 2012, 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_main-window-search.c
 * @author Christian Grothoff
 * @brief event handlers for the search function in the main window
 */
#include "gnunet-fs-gtk_common.h"
#include "gnunet-fs-gtk.h"
#include "gnunet-fs-gtk_anonymity-widgets.h"
#include <gnunet/gnunet_gnsrecord_lib.h>


/**
 * How long until we decide a SKS namespace GNS lookup has failed?
 */
#define LOOKUP_TIMEOUT \
  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)


/**
 * Emits a beep using the display of the screen of the main file sharing window.
 */
static void
beep ()
{
  gdk_display_beep (gdk_screen_get_display (
                      gdk_window_get_screen (gtk_widget_get_parent_window (
                                               GTK_WIDGET (
                                                 GNUNET_FS_GTK_get_main_window_object (
                                                   "GNUNET_GTK_main_window"))))));
}


/**
 * Start a search.
 *
 * @param uri uri to search for, freed in this function
 * @param anonymity_level degree of anonymity to apply for the search
 */
static void
search_for_uri (struct GNUNET_FS_Uri *uri, guint anonymity_level)
{
  GNUNET_FS_search_start (GNUNET_FS_GTK_get_fs_handle (),
                          uri,
                          anonymity_level,
                          GNUNET_FS_SEARCH_OPTION_NONE,
                          NULL);
  GNUNET_FS_uri_destroy (uri);
}


/**
 * Abort the given search lookup.
 *
 * @param sl lookup to abort.
 */
void
abort_search_lookup (struct SearchLookup *sl)
{
  struct GNUNET_GTK_MainWindowContext *main_ctx =
    GNUNET_FS_GTK_get_main_context ();

  GNUNET_CONTAINER_DLL_remove (main_ctx->sl_head, main_ctx->sl_tail, sl);
  if (NULL != sl->timeout_task)
  {
    GNUNET_SCHEDULER_cancel (sl->timeout_task);
    sl->timeout_task = NULL;
  }
  if (NULL != sl->gns)
  {
    GNUNET_GNS_lookup_cancel (sl->gns);
    sl->gns = NULL;
  }
  g_free (sl->keywords);
  GNUNET_free (sl);
}


/**
 * Task run when the GNS timeout during the resolution of
 * the GNS namespace times out.
 *
 * @param cls the 'struct SearchLookup'
 */
static void
timeout_search_lookup (void *cls)
{
  struct SearchLookup *sl = cls;

  sl->timeout_task = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              _ ("Failed to resolve namespace in time\n"));
  abort_search_lookup (sl);
}


/**
 * Iterator called on obtained result for a GNS lookup for
 * the public key of a namespace identifier.
 *
 * @param cls closure
 * @param rd_count number of records in @a rd
 * @param rd the records in reply
 */
static void
handle_gns_result (void *cls,
                   uint32_t rd_count,
                   const struct GNUNET_GNSRECORD_Data *rd)
{
  struct SearchLookup *sl = cls;
  unsigned int i;
  struct GNUNET_FS_Uri *uri;

  sl->gns = NULL;
  for (i = 0; i < rd_count; i++)
  {
    if (GNUNET_GNSRECORD_TYPE_PKEY != rd[i].record_type)
      continue;
    uri = GNUNET_FS_uri_sks_create (rd[i].data, sl->keywords);
    search_for_uri (uri, sl->anonymity_level);
    abort_search_lookup (sl);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              _ ("Failed to resolve namespace in time\n"));
  abort_search_lookup (sl);
}


/**
 * Start a search.
 *
 * @param main_ctx the main window context
 */
static void
start_search (struct GNUNET_GTK_MainWindowContext *main_ctx)
{
  guint anonymity_level;
  gchar *keywords;
  gchar *mime_keyword;
  const char *nsid;
  struct GNUNET_FS_Uri *uri;
  struct GNUNET_IDENTITY_PublicKey pub_sks_zone;
  struct SearchLookup *sl;
  char *emsg;

  /* get anonymity level */
  if (! GNUNET_GTK_get_selected_anonymity_level (
        main_ctx->builder,
        "main_window_search_anonymity_combobox",
        &anonymity_level))
  {
    GNUNET_break (0);
    return;
  }

  /* get selected mime type */
  {
    GtkComboBox *mime_combo;
    GtkTreeModel *mime_model;
    GtkTreeIter iter;

    mime_combo = GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object (
                                  "main_window_search_mime_combobox"));
    mime_model = gtk_combo_box_get_model (mime_combo);
    mime_keyword = NULL;
    if ((NULL != mime_model) &&
        gtk_combo_box_get_active_iter (mime_combo, &iter))
      gtk_tree_model_get (mime_model,
                          &iter,
                          GNUNET_GTK_FS_MAIN_WINDOW_SEARCH_MIME_MC_MIME,
                          &mime_keyword,
                          -1);
    if ((NULL != mime_keyword) && (0 == strcmp (mime_keyword, " ")))
    {
      g_free (mime_keyword);
      mime_keyword = NULL;
    }
  }
  {
    GtkComboBox *namespace_box;

    namespace_box = GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object (
                                     "main_window_search_namespace_combobox"));
    nsid = gtk_entry_get_text (
      GTK_ENTRY (gtk_bin_get_child (GTK_BIN (namespace_box))));
    if ((NULL != nsid) && ((0 == strcasecmp (nsid, "<none>")) ||
                           (0 == strcasecmp (nsid, _ ("<none>")))))
      nsid = NULL;
  }

  /* get keywords and compose keyword string */
  {
    const char *entry_keywords;

    entry_keywords = gtk_entry_get_text (main_ctx->search_entry);
    if (NULL != mime_keyword)
    {
      keywords = g_strdup_printf ("%s +%s", entry_keywords, mime_keyword);
      g_free (mime_keyword);
    }
    else
    {
      keywords = g_strdup (entry_keywords);
    }
  }

  /* build KSK/SKS URI */
  if ((NULL != nsid) && (0 < strlen (nsid)))
  {
    sl = GNUNET_new (struct SearchLookup);
    sl->keywords = keywords;
    sl->anonymity_level = anonymity_level;
    sl->timeout_task =
      GNUNET_SCHEDULER_add_delayed (LOOKUP_TIMEOUT, &timeout_search_lookup, sl);
    GNUNET_IDENTITY_ego_get_public_key (main_ctx->sks_zone, &pub_sks_zone);
    sl->gns = GNUNET_GNS_lookup (main_ctx->gns,
                                 nsid,
                                 &pub_sks_zone,
                                 GNUNET_GNSRECORD_TYPE_PKEY,
                                 GNUNET_NO,
                                 &handle_gns_result,
                                 sl);
    GNUNET_CONTAINER_DLL_insert (main_ctx->sl_head, main_ctx->sl_tail, sl);
    return;
  }

  emsg = NULL;
  uri = GNUNET_FS_uri_ksk_create (keywords, &emsg);
  if (NULL == uri)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Invalid keyword string `%s': %s"),
                keywords,
                emsg);
    g_free (keywords);
    GNUNET_free (emsg);
    return;
  }
  g_free (keywords);
  search_for_uri (uri, anonymity_level);
}


/**
 * User clicked on the 'search' button in the main window.
 *
 * @param button the search button
 * @param user_data the main window context builder
 */
void
main_window_search_button_clicked_cb (GtkButton *button, gpointer user_data)
{
  struct GNUNET_GTK_MainWindowContext *main_ctx = user_data;

  start_search (main_ctx);
}


/**
 * User pushed a key (possibly ENTER) in the search entry widget.
 * Start the search if it was ENTER.
 *
 * @param widget the entry widget
 * @param event the key stroke
 * @param user_data the main window context
 * @return FALSE if this was not ENTER, TRUE if it was
 */
gboolean
main_window_search_entry_key_press_event_cb (GtkWidget *widget,
                                             GdkEventKey *event,
                                             gpointer user_data)
{
  struct GNUNET_GTK_MainWindowContext *main_ctx = user_data;

  if (event->keyval == GDK_KEY_Return)
  {
    start_search (main_ctx);
    return TRUE;
  }
  return FALSE;
}


/**
 * Asynchronously clean up PseuLookupContext.
 *
 * @param cls the `struct PseuLookupContext` to clean up.
 */
static void
end_pseu_lookup (void *cls)
{
  struct PseuLookupContext *lctx = cls;

  if (NULL != lctx->progress_dialog_builder)
  {
    gtk_widget_destroy (lctx->progress_dialog);
    g_object_unref (G_OBJECT (lctx->progress_dialog_builder));
    lctx->progress_dialog_builder = NULL;
  }
  if (NULL != lctx->nick_dialog_builder)
  {
    gtk_widget_destroy (lctx->nick_dialog);
    g_object_unref (G_OBJECT (lctx->nick_dialog_builder));
    lctx->nick_dialog_builder = NULL;
  }

  if (NULL != lctx->lr)
  {
    GNUNET_GNS_lookup_cancel (lctx->lr);
    lctx->lr = NULL;
  }
  if (NULL != lctx->qe)
  {
    GNUNET_NAMESTORE_cancel (lctx->qe);
    lctx->qe = NULL;
  }
  if (NULL != lctx->namestore)
  {
    GNUNET_NAMESTORE_disconnect (lctx->namestore);
    lctx->namestore = NULL;
  }
  GNUNET_free (lctx->nick);
  GNUNET_free (lctx);
}


/**
 * Abort the given PSEU lookup.
 *
 * @param lctx lookup to abort.
 */
void
abort_pseu_lookup (struct PseuLookupContext *lctx)
{
  struct GNUNET_GTK_MainWindowContext *main_ctx = lctx->main_ctx;

  GNUNET_CONTAINER_DLL_remove (main_ctx->lctx_head, main_ctx->lctx_tail, lctx);
  (void) GNUNET_SCHEDULER_add_now (&end_pseu_lookup, lctx);
}


/**
 * Continuation called to notify client about result of the
 * operation.
 *
 * @param cls closure
 * @param success the error code
 */
static void
store_continuation (void *cls, enum GNUNET_ErrorCode success)
{
  struct PseuLookupContext *lctx = cls;

  lctx->qe = NULL;
  if (GNUNET_EC_NONE != success)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Failed to save record: %s\n"),
                GNUNET_ErrorCode_get_hint (success));
    beep ();
  }
  abort_pseu_lookup (lctx);
}


/**
 * Save the namespace under the given nickname.
 *
 * @param lctx namespace request we are processing
 * @param nick nickname to store the namespace under
 */
static void
save_pseudonym_with_nick (struct PseuLookupContext *lctx, const char *nick)
{
  struct GNUNET_GNSRECORD_Data rd;
  struct GNUNET_GTK_MainWindowContext *main_ctx;

  GNUNET_break (NULL == lctx->nick);
  lctx->nick = GNUNET_strdup (nick);
  /* again, show progress indicator, this should be fast though... */
  lctx->progress_dialog_builder =
    GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_pseu_progress_dialog.glade",
                                lctx);
  lctx->progress_dialog =
    GTK_WIDGET (gtk_builder_get_object (lctx->progress_dialog_builder,
                                        "GNUNET_FS_GTK_pseu_progress_dialog"));
  /* show the window */
  gtk_window_present (GTK_WINDOW (lctx->progress_dialog));
  memset (&rd, 0, sizeof (rd));
  rd.data_size = GNUNET_IDENTITY_public_key_get_length (&lctx->pkey);
  rd.data = &lctx->pkey;
  rd.flags = GNUNET_GNSRECORD_RF_PRIVATE;
  rd.expiration_time = GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us;
  rd.record_type = GNUNET_GNSRECORD_TYPE_PKEY;
  main_ctx = GNUNET_FS_GTK_get_main_context ();
  lctx->namestore = GNUNET_NAMESTORE_connect (main_ctx->cfg);
  GNUNET_assert (NULL != lctx->namestore);
  lctx->qe =
    GNUNET_NAMESTORE_records_store (lctx->namestore,
                                    GNUNET_IDENTITY_ego_get_private_key (
                                      main_ctx->sks_zone),
                                    nick,
                                    1,
                                    &rd,
                                    &store_continuation,
                                    lctx);
}


/**
 * User clicked on the 'execute' button in nickname dialog.
 * Store the selected namespace in the "sks-fs" zone under
 * the given nickname.
 *
 * @param button the "execute" button
 * @param user_data the `struct PseuLookupContext`
 */
void
GNUNET_GTK_enter_nick_window_execute_button_clicked_cb (GtkButton *button,
                                                        gpointer user_data)
{
  struct PseuLookupContext *lctx = user_data;
  GtkEntry *entry;
  const char *nick;

  entry = GTK_ENTRY (
    gtk_builder_get_object (lctx->nick_dialog_builder,
                            "GNUNET_GTK_enter_nick_window_nick_entry"));
  nick = gtk_entry_get_text (entry);
  if ((NULL == nick) || (0 == strlen (nick)))
  {
    GNUNET_break (0);
    abort_pseu_lookup (lctx);
    return;
  }
  save_pseudonym_with_nick (lctx, nick);
  gtk_widget_destroy (lctx->nick_dialog);
  g_object_unref (G_OBJECT (lctx->nick_dialog_builder));
  lctx->nick_dialog_builder = NULL;
  lctx->nick_dialog = NULL;
}


/**
 * User edited the nickname.  Update sensitivity of the execute button.
 *
 * @param widget the entry that was changed
 * @param user_data the `struct PseuLookupContext`
 */
void
GNUNET_GTK_enter_nick_window_nick_entry_changed_cb (GtkWidget *widget,
                                                    gpointer user_data)
{
  struct PseuLookupContext *lctx = user_data;
  const gchar *new_text;
  GtkButton *button;

  new_text = gtk_entry_get_text (GTK_ENTRY (widget));
  button = GTK_BUTTON (
    gtk_builder_get_object (lctx->nick_dialog_builder,
                            "GNUNET_GTK_enter_nick_window_execute_button"));
  gtk_widget_set_sensitive (GTK_WIDGET (button),
                            (GNUNET_OK ==
                             GNUNET_DNSPARSER_check_label (new_text))
                              ? TRUE
                              : FALSE);
}


/**
 * User clicked on the 'cancel' button in nickname dialog.
 * Abort the operation.
 *
 * @param button the "cancel" button
 * @param user_data the `struct PseuLookupContext`
 */
void
GNUNET_GTK_enter_nick_window_cancel_button_clicked_cb (GtkButton *button,
                                                       gpointer user_data)
{
  struct PseuLookupContext *lctx = user_data;

  abort_pseu_lookup (lctx);
}


/**
 * Run the dialog asking the user to specify a nickname for
 * the namespace.
 *
 * @param lctx namespace request we are processing
 */
static void
ask_for_nickname (struct PseuLookupContext *lctx)
{
  /* setup the dialog and get the widgets we need most */
  lctx->nick_dialog_builder =
    GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_enter_nick_dialog.glade", lctx);
  lctx->nick_dialog =
    GTK_WIDGET (gtk_builder_get_object (lctx->nick_dialog_builder,
                                        "GNUNET_GTK_enter_nick_window"));
  /* show the window */
  gtk_window_present (GTK_WINDOW (lctx->nick_dialog));
}


/**
 * Iterator called on obtained result for a GNS lookup for
 * the PSEU lookup when "saving" a zone.  The actual saving
 * should already have happened via the shortening of GNS,
 * so we only need to clean up.
 *
 * @param cls closure with the `struct PseuLookupContext`
 * @param rd_count number of records in @a rd
 * @param rd the records in reply
 */
static void
lookup_finished (void *cls,
                 uint32_t rd_count,
                 const struct GNUNET_GNSRECORD_Data *rd)
{
  struct PseuLookupContext *lctx = cls;
  unsigned int i;
  const char *nick;

  lctx->lr = NULL;
  if (NULL != lctx->progress_dialog_builder)
  {
    gtk_widget_destroy (lctx->progress_dialog);
    g_object_unref (G_OBJECT (lctx->progress_dialog_builder));
    lctx->progress_dialog_builder = NULL;
    lctx->progress_dialog = NULL;
  }
  for (i = 0; i < rd_count; i++)
  {
    if (GNUNET_GNSRECORD_TYPE_NICK == rd[i].record_type)
    {
      nick = rd[i].data;
      if ('\0' != nick[rd[i].data_size - 1])
      {
        GNUNET_break (0);
        continue;
      }
      save_pseudonym_with_nick (lctx, nick);
      return;
    }
  }
  /* no valid PSEU record found */
  ask_for_nickname (lctx);
}


/**
 * User clicked on the 'cancel' button of the progress dialog.
 * Cancel the operation.
 *
 * @param button the cancel button
 * @param user_data the `struct PseuLookupContext` of our window
 */
void
GNUNET_FS_GTK_pseu_progress_dialog_cancel_button_clicked_cb (GtkButton *button,
                                                             gpointer user_data)
{
  struct PseuLookupContext *lctx = user_data;

  if (NULL != lctx->progress_dialog_builder)
  {
    gtk_widget_destroy (lctx->progress_dialog);
    g_object_unref (G_OBJECT (lctx->progress_dialog_builder));
    lctx->progress_dialog_builder = NULL;
    lctx->progress_dialog = NULL;
  }
  if (NULL != lctx->nick)
    abort_pseu_lookup (lctx);
  else
    ask_for_nickname (lctx);
}


/**
 * User attempted to close the nick dialog.  Refuse.
 *
 * @param widget the widget emitting the event
 * @param event the event
 * @param cls progress dialog context of our window
 * @return TRUE to refuse to close
 */
gboolean
GNUNET_GTK_enter_nick_window_delete_event_cb (GtkWidget *widget,
                                              GdkEvent *event,
                                              void *cls)
{
  /* Don't allow GTK to kill the window, user must click execute or cancel */
  beep ();
  return TRUE;
}


/**
 * User attempted to close the progress dialog.  Refuse.
 *
 * @param widget the widget emitting the event
 * @param event the event
 * @param cls progress dialog context of our window
 * @return TRUE to refuse to close
 */
gboolean
GNUNET_FS_GTK_pseu_progress_dialog_delete_event_cb (GtkWidget *widget,
                                                    GdkEvent *event,
                                                    void *cls)
{
  /* Don't allow GTK to kill the window, until the search is finished */
  beep ();
  return TRUE;
}


/**
 * User clicked on the 'save' button in the search line of the main window.
 * Store the selected namespace in the "sks-fs" zone.
 *
 * @param button the "save" button
 * @param user_data the main window context builder
 */
void
GNUNET_FS_GTK_save_button_clicked_cb (GtkButton *button, gpointer user_data)
{
  struct GNUNET_GTK_MainWindowContext *main_ctx = user_data;
  GtkComboBox *widget;
  const gchar *text;
  struct GNUNET_IDENTITY_PublicKey pkey;
  int ret;
  struct PseuLookupContext *lctx;
  guint anonymity_level;

  if (NULL == main_ctx->gns)
  {
    GNUNET_break (0);
    return;
  }
  /* get anonymity level */
  if (! GNUNET_GTK_get_selected_anonymity_level (
        main_ctx->builder,
        "main_window_search_anonymity_combobox",
        &anonymity_level))
  {
    GNUNET_break (0);
    return;
  }
  widget = GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object (
                            "main_window_search_namespace_combobox"));
  text = gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (widget))));
  ret = GNUNET_GNSRECORD_zkey_to_pkey (text, &pkey);
  if (GNUNET_OK != ret)
  {
    GNUNET_break (0);
    return;
  }
  lctx = GNUNET_new (struct PseuLookupContext);
  lctx->pkey = pkey;
  lctx->main_ctx = main_ctx;
  GNUNET_CONTAINER_DLL_insert (main_ctx->lctx_head, main_ctx->lctx_tail, lctx);

  if (0 == anonymity_level)
  {
    /* setup the dialog and get the widgets we need most */
    lctx->progress_dialog_builder =
      GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_pseu_progress_dialog.glade",
                                  lctx);
    lctx->progress_dialog = GTK_WIDGET (
      gtk_builder_get_object (lctx->progress_dialog_builder,
                              "GNUNET_FS_GTK_pseu_progress_dialog"));
    /* show the window */
    gtk_window_present (GTK_WINDOW (lctx->progress_dialog));
    lctx->lr = GNUNET_GNS_lookup (main_ctx->gns,
                                  GNUNET_GNS_EMPTY_LABEL_AT,
                                  &pkey,
                                  GNUNET_GNSRECORD_TYPE_NICK,
                                  GNUNET_NO,
                                  &lookup_finished,
                                  lctx);
  }
  else
  {
    /* anonymous operation; cannot use GNS/DHT, so user
       must make a suggestion himself */
    ask_for_nickname (lctx);
  }
  /* do not allow save again just yet */
  gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
}


/**
 * The user has somehow changed the selectionin the namespace combo box entry.
 * Update the visibility of the "save" button.
 *
 * @param widget the combo box
 * @param user_data the main window context builder (unused)
 */
void
main_window_search_namespace_combobox_changed_cb (GtkComboBox *widget,
                                                  gpointer user_data)
{
  GtkButton *button;
  const gchar *text;
  struct GNUNET_IDENTITY_PublicKey pkey;
  int ret;

  text = gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (widget))));
  ret = GNUNET_GNSRECORD_zkey_to_pkey (text, &pkey);
  button = GTK_BUTTON (
    GNUNET_FS_GTK_get_main_window_object ("GNUNET_FS_GTK_save_button"));
  gtk_widget_set_visible (GTK_WIDGET (button),
                          (GNUNET_OK == ret) ? TRUE : FALSE);
  gtk_widget_set_sensitive (GTK_WIDGET (button),
                            (GNUNET_OK == ret) ? TRUE : FALSE);
}


/* end of gnunet-fs-gtk_main-window-search.c */
