/*
     This file is part of anastasis-gtk.
     Copyright (C) 2020, 2021, 2022 Anastasis SARL

     Anastasis 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.

     Anastasis 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 Anastasis; 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/anastasis/anastasis-gtk_action.c
 * @brief Handle redux action results
 * @author Christian Grothoff
 * @author Dennis Neufeld
 */
#include <gnunet/gnunet_util_lib.h>
#include <strings.h>
#include "anastasis_gtk_util.h"
#include "anastasis-gtk_action.h"
#include "anastasis-gtk_attributes.h"
#include "anastasis-gtk_dispatch.h"
#include "anastasis-gtk_helper.h"
#include "anastasis-gtk_handle-identity-changed.h"
#include "anastasis-gtk_progress.h"
#include "anastasis-gtk_rows.h"
#include <jansson.h>
#include <qrencode.h>
#include <gdk-pixbuf/gdk-pixbuf.h>


/**
 * After how long does our long-poller time out?
 */
#define LP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)

/**
 * Are we currently processing an action?
 */
bool AG_in_action;

/**
 * Are we currently editing the secret?
 */
bool AG_in_secret_editing;

/**
 * Are we currently editing the secret name?
 */
bool AG_in_secret_name_editing;


#define DEBUG 0


/**
 * Add space on both sides of @a child along the orientation of the box it is
 * about to be appended to.  This is what the `padding` argument of GTK3's
 * gtk_box_pack_start() used to do; GTK4 boxes only know margins.
 *
 * @param orientation orientation of the box @a child goes into
 * @param child widget to pad
 * @param padding number of pixels to add on either side
 */
static void
pad_along (GtkOrientation orientation,
           GtkWidget *child,
           int padding)
{
  if (GTK_ORIENTATION_HORIZONTAL == orientation)
  {
    gtk_widget_set_margin_start (child,
                                 padding);
    gtk_widget_set_margin_end (child,
                               padding);
  }
  else
  {
    gtk_widget_set_margin_top (child,
                               padding);
    gtk_widget_set_margin_bottom (child,
                                  padding);
  }
}


/**
 * Lookup provider name by provider URL in our state.
 *
 * @param provider_url URL to lookup name for
 * @return provider name, or if unknown, the @a provider_url
 */
static const char *
lookup_provider_name (const char *provider_url)
{
  json_t *providers;
  json_t *provider;
  const char *name = NULL;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("business_name",
                               &name),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  providers = json_object_get (AG_redux_state,
                               "authentication_providers");
  provider = json_object_get (providers,
                              provider_url);
  if (NULL == provider)
    return provider_url;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (provider,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    json_dumpf (provider,
                stderr,
                JSON_INDENT (2));
    return provider_url;
  }
  return name;
}


/**
 * Set country selection next-button sensitivity based on
 * whether a country is currently selected.
 */
static void
set_country_selection_next (void)
{
  if (NULL != AG_selected_row ("anastasis_gtk_country_selection"))
    AG_enable_next ();
  else
    AG_insensitive ("anastasis_gtk_main_window_forward_button");
}


/**
 * Prepare window for selection of the continent.
 */
static void
action_continent_selecting (void)
{
  AG_hide_all_frames ();
  AG_list_clear ("country_liststore");
  {
    json_t *continents;

    AG_list_clear ("continent_liststore");
    continents = json_object_get (AG_redux_state,
                                  "continents");
    if (NULL != continents)
    {
      json_t *continent;
      size_t index;

      json_array_foreach (continents,
                          index,
                          continent)
      {
        const char *name;
        struct GNUNET_JSON_Specification spec[] = {
          GNUNET_JSON_spec_string ("name",
                                   &name),
          GNUNET_JSON_spec_end ()
        };

        if (GNUNET_OK !=
            GNUNET_JSON_parse (continent,
                               spec,
                               NULL, NULL))
        {
          GNUNET_break (0);
          GNUNET_JSON_parse_free (spec);
          continue;
        }

        AG_list_add ("continent_liststore",
                     GNUNET_JSON_PACK (
                       GNUNET_JSON_pack_string ("name",
                                                name),
                       GNUNET_JSON_pack_string ("name_i18n",
                                                dgettext ("anastasis",
                                                          name))));
        GNUNET_JSON_parse_free (spec);
      }
    }
  }

  AG_sensitive ("anastasis_gtk_main_window_prev_button");
  AG_insensitive ("anastasis_gtk_main_window_forward_button");
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  if (NULL != json_object_get (AG_redux_state,
                               "backup_state"))
  {
    AG_show ("anastasis_gtk_backup_progress_scrolled_window");
    AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  }
  else
  {
    AG_show ("anastasis_gtk_recovery_progress_scrolled_window");
    AG_hide ("anastasis_gtk_backup_progress_scrolled_window");
  }
  AG_show ("anastasis_gtk_main_window_prev_button");
  set_country_selection_next ();
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_continent_frame");
}


/**
 * Prepare window for selection of the country.
 */
static void
action_country_selecting (void)
{
  json_t *countries;
  const char *selected_country;

  AG_hide_all_frames ();
  countries = json_object_get (AG_redux_state,
                               "countries");
  selected_country
    = json_string_value (json_object_get (AG_redux_state,
                                          "selected_country"));
  AG_list_clear ("country_liststore");
  {
    json_t *country;
    size_t index;
    guint position = 0;

    json_array_foreach (countries, index, country)
    {
      const char *code;
      const char *name;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_string ("code",
                                 &code),
        GNUNET_JSON_spec_string ("name",
                                 &name),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (country,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break (0);
        GNUNET_JSON_parse_free (spec);
        continue;
      }

      AG_list_add ("country_liststore",
                   GNUNET_JSON_PACK (
                     GNUNET_JSON_pack_string ("name",
                                              dgettext ("anastasis",
                                                        name)),
                     GNUNET_JSON_pack_string ("code",
                                              code)));
      if ( (NULL != selected_country) &&
           (NULL != code) &&
           (0 == strcmp (code,
                         selected_country)) )
        AG_select_row ("anastasis_gtk_country_selection",
                       position);
      position++;
      GNUNET_JSON_parse_free (spec);
    }
  }

  AG_sensitive ("anastasis_gtk_main_window_prev_button");
  AG_insensitive ("anastasis_gtk_main_window_forward_button");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  if (NULL != json_object_get (AG_redux_state,
                               "backup_state"))
  {
    AG_show ("anastasis_gtk_backup_progress_scrolled_window");
    AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  }
  else
  {
    AG_show ("anastasis_gtk_recovery_progress_scrolled_window");
    AG_hide ("anastasis_gtk_backup_progress_scrolled_window");
  }
  AG_show ("anastasis_gtk_main_window_prev_button");

  set_country_selection_next ();

  AG_show ("anastasis_gtk_continent_frame");
}


/**
 * Create widget for "string" type user attributes.
 *
 * @param details not used
 * @return widget to be used for string entry
 */
static GtkWidget *
ctor_entry (const json_t *details)
{
  (void) details;
  return gtk_entry_new ();
}


/**
 * Create widget for "date" type user attributes.
 *
 * @param details not used
 * @return widget to be used for date entry
 */
static GtkWidget *
ctor_date (const json_t *details)
{
  (void) details;
  return gtk_calendar_new ();
}


/**
 * Create widget of @a type under @a uuid with @a label and @a tooltip
 * for the identity attribute editing dialog.  Stores all created widgets
 * in the #AG_entry_attributes and ensures that we never create the same
 * widget (by @a uuid) twice.
 *
 * @param uh hash of unique ID of the widget, only create one per UUID
 * @param type type of the widget to create
 * @param label label to use
 * @param tooltip tooltip to use
 * @param id_attr potential additional inputs for the widget creation
 * @return created widget
 */
static GtkWidget *
create_attribute_widget (const struct GNUNET_HashCode *uh,
                         const char *type,
                         const char *label,
                         const char *tooltip,
                         const json_t *id_attr)
{
  static struct
  {
    const char *type;
    GtkWidget *(*ctor)(const json_t *details);
  } type_map [] = {
    { .type = "string",
      .ctor = &ctor_entry },
    { .type = "date",
      .ctor = &ctor_date },
    { .type = NULL,
      .ctor = NULL }
  };
  GtkWidget *w;

  w = GNUNET_CONTAINER_multihashmap_get (AG_entry_attributes,
                                         uh);
  if (NULL != w)
  {
    GtkWidget *p;

    gtk_widget_set_visible (w,
                            TRUE);
    p = gtk_widget_get_parent (w);
    gtk_widget_set_visible (p,
                            TRUE);
    p = gtk_widget_get_parent (p);
    gtk_widget_set_visible (p,
                            TRUE);
    return w;
  }
  for (unsigned int i = 0; NULL != type_map[i].type; i++)
  {
    GtkBox *box;
    GtkBox *vbox;

    if (0 != strcmp (type_map[i].type,
                     type))
      continue;
    w = type_map[i].ctor (id_attr);
    GNUNET_assert (NULL != w);
    box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL,
                                5 /* spacing in pixels */));
    vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL,
                                 5 /* spacing in pixels */));
    {
      GtkWidget *glabel;

      glabel = gtk_label_new (label);
      pad_along (GTK_ORIENTATION_HORIZONTAL,
                 glabel,
                 5);
      gtk_box_append (box,
                      glabel);
    }
    GNUNET_assert (0 <
                   g_signal_connect (w,
                                     "changed",
                                     G_CALLBACK (&AG_identity_changed),
                                     NULL));
    gtk_widget_set_tooltip_text (w,
                                 tooltip);
    pad_along (GTK_ORIENTATION_HORIZONTAL,
               w,
               5);
    gtk_box_append (box,
                    w);
    pad_along (GTK_ORIENTATION_VERTICAL,
               GTK_WIDGET (box),
               5);
    gtk_box_append (vbox,
                    GTK_WIDGET (box));
    {
      GtkWidget *private_widget;
      GtkBuilder *builder;
      GtkWindow *window;

      builder =
        ANASTASIS_GTK_get_new_builder (
          ANASTASIS_GTK_project_data (),
          "this_stays_private.ui",
          NULL);
      GNUNET_break (NULL != builder);
      /* load frame */
      window = GTK_WINDOW (gtk_builder_get_object (builder,
                                                   "private_dummy_window"));
      GNUNET_break (NULL != window);
      private_widget = gtk_window_get_child (window);
      GNUNET_break (NULL != private_widget);
      g_object_ref (private_widget);
      gtk_window_set_child (window,
                            NULL);
      gtk_window_destroy (window);
      g_object_unref (G_OBJECT (builder));
      pad_along (GTK_ORIENTATION_VERTICAL,
                 private_widget,
                 5);
      gtk_box_append (vbox,
                      private_widget);
      g_object_unref (private_widget);
    }
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multihashmap_put (AG_entry_attributes,
                                                      uh,
                                                      w,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
    {
      GtkBox *pbox;

      pbox = GTK_BOX (GCG_get_main_window_object (
                        "anastasis_gtk_identity_vbox"));
      pad_along (GTK_ORIENTATION_VERTICAL,
                 GTK_WIDGET (vbox),
                 5);
      gtk_box_append (pbox,
                      GTK_WIDGET (vbox));
    }
    return w;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              "FATAL: required attribute type `%s' not supported\n",
              type);
  GNUNET_assert (0);
  return NULL;
}


/**
 * Update GtkLabel @a name, setting text to @a value.
 *
 * @param name name of the widget to update
 * @param value value to set
 */
static void
update_label (const char *name,
              const char *value)
{
  GtkLabel *label;

  label = GTK_LABEL (GCG_get_main_window_object (name));
  if (NULL == label)
    return;
  if (NULL == value)
  {
    gtk_widget_set_visible (GTK_WIDGET (label),
                            FALSE);
  }
  else
  {
    gtk_label_set_text (label,
                        value);
    gtk_widget_set_visible (GTK_WIDGET (label),
                            TRUE);
  }
}


/**
 * We are entering the tab where the user has to enter their personal
 * attributes.  Show widgets applicable to the selected country, and
 * dynamically generate widgets if necessary.  Furthermore, restore
 * widget contents from our state (if stored attribute values are
 * present in our state).
 */
static void
action_user_attributes_collecting (void)
{
  const json_t *id_attributes;

  AG_hide_all_frames ();
  id_attributes = json_object_get (AG_redux_state,
                                   "required_attributes");
  GNUNET_assert (NULL != id_attributes);
  AG_hide_children ("anastasis_gtk_identity_vbox");
  {
    size_t index;
    json_t *id_attr;

    json_array_foreach (id_attributes, index, id_attr)
    {
      const char *widget_name = NULL;
      const char *attr_tooltip = NULL;
      const char *attr_label = NULL;
      const char *attr_type;
      const char *attr_uuid;
      const char *attr_name;
      bool optional = false;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_string ("widget",
                                   &widget_name),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_bool ("optional",
                                 &optional),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_string ("tooltip",
                                   &attr_tooltip),
          NULL),
        GNUNET_JSON_spec_string ("type",
                                 &attr_type),
        GNUNET_JSON_spec_string ("uuid",
                                 &attr_uuid),
        GNUNET_JSON_spec_string ("name",
                                 &attr_name),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_string ("label",
                                   &attr_label),
          NULL),
        GNUNET_JSON_spec_end ()
      };
      struct GNUNET_HashCode uh;
      GtkWidget *w = NULL;
      char *l = NULL;

      GNUNET_assert (GNUNET_OK ==
                     GNUNET_JSON_parse (id_attr,
                                        spec,
                                        NULL, NULL));
      GNUNET_CRYPTO_hash (attr_uuid,
                          strlen (attr_uuid),
                          &uh);
      if (NULL != widget_name)
      {
        char *data_name;

        data_name = AG_expand_name (widget_name,
                                    attr_type);
        w = GTK_WIDGET (GCG_get_main_window_object (data_name));
        if (NULL == w)
        {
          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                      "Widget `%s' not found, will try to create dynamic replacement\n",
                      data_name);
        }
        GNUNET_free (data_name);
      }
      if (optional)
        GNUNET_asprintf (&l,
                         _ ("%s (optional)"),
                         dgettext ("anastasis",
                                   attr_label));
      else
        l = GNUNET_strdup (dgettext ("anastasis",
                                     attr_label));
      if ( (NULL != widget_name) &&
           (NULL != w) )
      {
        char *box_widget;
        GObject *box;

        GNUNET_asprintf (&box_widget,
                         "%s_box",
                         widget_name);
        box = GCG_get_main_window_object (box_widget);
        if (NULL == box)
        {
          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                      "Widget `%s' not found, cannot show entry element. BAD.\n",
                      box_widget);
        }
        else
        {
          AG_show (box_widget);
          AG_show_children (box_widget);
        }
        GNUNET_free (box_widget);
      }
      if ( (NULL != w) &&
           (! GNUNET_CONTAINER_multihashmap_contains (AG_entry_attributes,
                                                      &uh)) )
      {
        GNUNET_assert (GNUNET_OK ==
                       GNUNET_CONTAINER_multihashmap_put (AG_entry_attributes,
                                                          &uh,
                                                          w,
                                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
      }
      if (NULL == w)
        w = create_attribute_widget (&uh,
                                     attr_type,
                                     l,
                                     attr_tooltip,
                                     id_attr);
      GNUNET_free (l);
      if (NULL != w)
      {
        json_t *ia;
        json_t *val;

        ia = json_object_get (AG_redux_state,
                              "identity_attributes");
        val = json_object_get (ia,
                               attr_name);
        if ( (NULL != val) &&
             (! json_is_null (val)) )
          AG_import_attribute_data (w,
                                    attr_type,
                                    val);
        gtk_widget_set_visible (w,
                                TRUE);
      }
      GNUNET_JSON_parse_free (spec);
    }
  }

  AG_sensitive ("anastasis_gtk_main_window_prev_button");
  AG_identity_changed ();
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  if (NULL != json_object_get (AG_redux_state,
                               "backup_state"))
  {
    AG_show ("anastasis_gtk_backup_progress_scrolled_window");
    AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  }
  else
  {
    AG_show ("anastasis_gtk_recovery_progress_scrolled_window");
    AG_hide ("anastasis_gtk_backup_progress_scrolled_window");
  }
  AG_show ("anastasis_gtk_main_window_save_as_button");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_main_window_prev_button");
  AG_identity_changed ();
  AG_show ("anastasis_gtk_identity_frame");
  AG_focus ("anastasis_gtk_ia_full_name_entry");
}


static void
activate_by_method (json_t *methods)
{
  size_t index;
  const json_t *method;

  json_array_foreach (methods,
                      index,
                      method)
  {
    const char *type;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("type",
                               &type),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (method,
                           spec,
                           NULL, NULL))
    {
      GNUNET_break (0);
      json_dumpf (method,
                  stderr,
                  JSON_INDENT (2));
      continue;
    }

    {
      char btn[64];

      GNUNET_snprintf (btn,
                       sizeof (btn),
                       "anastasis_gtk_btn_add_auth_%s",
                       type);
      AG_sensitive (btn);
    }
  }
}


/**
 * Function called with the results of #ANASTASIS_redux_action on "poll_providers".
 *
 * @param cls NULL
 * @param error_code Error code
 * @param response new state as result or config information of a provider
 */
static void
long_poll_providers_action_cb (void *cls,
                               enum TALER_ErrorCode error_code,
                               json_t *response);


/**
 * Schedules the specified action.
 *
 * @param cls NULL
 */
static void
long_poll_providers_task (void *cls)
{
  struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_PROVIDERS];
  json_t *tspec;

  (void) cls;
  la->task = NULL;
  if (GNUNET_TIME_absolute_is_future (la->next_time))
  {
    la->task = GNUNET_SCHEDULER_add_at (la->next_time,
                                        &long_poll_providers_task,
                                        NULL);
    return;
  }
  la->next_time = GNUNET_TIME_relative_to_absolute (LP_TIMEOUT);
  tspec = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_time_rel ("timeout",
                               LP_TIMEOUT));
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Starting long polling task for provider configurations\n");
  la->ra = ANASTASIS_redux_action (AG_redux_state,
                                   "poll_providers",
                                   tspec,
                                   &long_poll_providers_action_cb,
                                   NULL);
  json_decref (tspec);
}


/**
 * Check if all providers we care about are ready,
 * and if not try to long poll them.
 *
 * @return false if we have no providers at all
 */
static bool
sync_providers (void)
{
  struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_PROVIDERS];
  json_t *ap;
  const char *url;
  json_t *obj;
  bool ready = false;
  bool poll = false;

  ap = json_object_get (AG_redux_state,
                        "authentication_providers");
  json_object_foreach (ap, url, obj)
  {
    struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
    enum GNUNET_GenericReturnValue ret;

    ret = ANASTASIS_reducer_lookup_salt (AG_redux_state,
                                         url,
                                         &provider_salt);
    switch (ret)
    {
    case GNUNET_OK:
      ready = true;
      break;
    case GNUNET_NO:
      poll = true;
      break;
    case GNUNET_SYSERR:
      GNUNET_break (0);
      break;
    }
  }
  if (poll)
  {
    la->next_time = GNUNET_TIME_UNIT_ZERO_ABS;
    la->task = GNUNET_SCHEDULER_add_now (&long_poll_providers_task,
                                         NULL);
  }
  return ready || poll;
}


/**
 * Allow the user to configure authorization methods from
 * the set of methods offered by known providers.
 */
static void
action_authentications_editing (void)
{
  json_t *aps;
  bool have_auth;

  AG_hide_all_frames ();
  AG_insensitive_children ("anastasis_gtk_auth_button_grid");
  (void) sync_providers ();
  aps = json_object_get (AG_redux_state,
                         "authentication_providers");
  {
    const json_t *ap;
    const char *provider_url;

    json_object_foreach (aps,
                         provider_url,
                         ap)
    {
      uint32_t ec = 0;
      uint32_t hc = 0;
      const char *status;
      json_t *methods = NULL;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_string ("status",
                                 &status),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_uint32 ("error_code",
                                   &ec),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_json ("methods",
                                 &methods),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_uint32 ("http_status",
                                   &hc),
          NULL),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (ap,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break (0);
        continue;
      }
      if (0 == strcmp (status,
                       "disabled"))
        continue;
      switch (hc)
      {
      case MHD_HTTP_OK:
        if (NULL == methods)
        {
          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                      "Provider `%s' has no authentication methods?\n",
                      provider_url);
          break;
        }
        activate_by_method (methods);
        break;
      default:
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Status of provider `%s' is %u/%u\n",
                    provider_url,
                    (unsigned int) ec,
                    (unsigned int) hc);
        break;
      }
      GNUNET_JSON_parse_free (spec);
    }
  }

  have_auth = false;
  {
    json_t *ams;
    size_t index;
    json_t *am;

    AG_list_clear ("authentication_methods_liststore");
    ams = json_object_get (AG_redux_state,
                           "authentication_methods");
    json_array_foreach (ams,
                        index,
                        am)
    {
      const char *type;
      const char *instructions;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_string ("type",
                                 &type),
        GNUNET_JSON_spec_string ("instructions",
                                 &instructions),
        GNUNET_JSON_spec_end ()
      };

      GNUNET_assert (GNUNET_OK ==
                     GNUNET_JSON_parse (am,
                                        spec,
                                        NULL, NULL));
      AG_list_add ("authentication_methods_liststore",
                   GNUNET_JSON_PACK (
                     GNUNET_JSON_pack_string ("type",
                                              type),
                     GNUNET_JSON_pack_string ("visualization",
                                              instructions),
                     GNUNET_JSON_pack_uint64 ("index",
                                              index)));
      have_auth = true;
    }
  }

  AG_sensitive ("anastasis_gtk_main_window_prev_button");
  if (have_auth)
    AG_enable_next ();
  else
    AG_insensitive ("anastasis_gtk_main_window_forward_button");
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  AG_show ("anastasis_gtk_backup_progress_scrolled_window");
  AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_main_window_prev_button");
  AG_enable_next ();
  AG_show ("anastasis_gtk_b_authentication_frame");
}


/**
 * Lookup @a method_cost of authentication method @a type at @a provider in our
 * #AG_redux_state.
 *
 * @param provider URL of provider
 * @param type authentication method to look for
 * @param[out] method_cost cost to return
 * @return #GNUNET_OK on success, #GNUNET_NO if
 *       the provider is down, #GNUNET_SYSERR on other failures
 */
static enum GNUNET_GenericReturnValue
lookup_recovery_cost (const char *provider,
                      const char *type,
                      struct TALER_Amount *method_cost)
{
  json_t *aps;
  json_t *ap;
  json_t *methods;
  size_t index;
  json_t *method;

  memset (method_cost,
          0,
          sizeof (struct TALER_Amount));
  aps = json_object_get (AG_redux_state,
                         "authentication_providers");
  GNUNET_assert (NULL != aps);
  ap = json_object_get (aps,
                        provider);
  if (NULL == ap)
  {
    GNUNET_break (0);
    json_dumpf (aps,
                stderr,
                JSON_INDENT (2));
    return GNUNET_SYSERR;
  }
  methods = json_object_get (ap,
                             "methods");
  if (NULL == methods)
    return GNUNET_NO; /* provider down */
  json_array_foreach (methods, index, method)
  {
    struct TALER_Amount fee;
    const char *mtype;
    struct GNUNET_JSON_Specification spec[] = {
      TALER_JSON_spec_amount_any ("usage_fee",
                                  &fee),
      GNUNET_JSON_spec_string ("type",
                               &mtype),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (method,
                           spec,
                           NULL, NULL))
    {
      GNUNET_break (0);
      json_dumpf (method,
                  stderr,
                  JSON_INDENT (2));
      continue;
    }
    if (0 == strcmp (mtype,
                     type))
    {
      *method_cost = fee;
      return GNUNET_OK;
    }
  }
  GNUNET_break (0);
  json_dumpf (methods,
              stderr,
              JSON_INDENT (2));
  return GNUNET_SYSERR;
}


static void
action_policies_reviewing (void)
{
  json_t *policies;
  size_t pindex;
  json_t *policy;
  GListStore *ts;

  AG_hide_all_frames ();
  ts = AG_list ("policy_review_treestore");
  if (NULL == ts)
    return;
  g_list_store_remove_all (ts);
  policies = json_object_get (AG_redux_state,
                              "policies");
  GNUNET_assert (NULL != policies);
  json_array_foreach (policies, pindex, policy)
  {
    AGRow *prow;
    json_t *methods;
    struct GNUNET_JSON_Specification pspec[] = {
      GNUNET_JSON_spec_json ("methods",
                             &methods),
      GNUNET_JSON_spec_end ()
    };
    size_t mindex;
    json_t *method;
    char *summary;

    if (GNUNET_OK !=
        GNUNET_JSON_parse (policy,
                           pspec,
                           NULL, NULL))
    {
      GNUNET_break (0);
      continue;
    }
    /* the row is only added to the list once its challenges are known,
       as that is when the list learns that it can be expanded */
    prow = AG_row_new (GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_uint64 ("policy_index",
                                                  pindex),
                         GNUNET_JSON_pack_bool ("is_challenge",
                                                false),
                         GNUNET_JSON_pack_string ("expiration",
                                                  "N/A")));
    summary = NULL;
    json_array_foreach (methods, mindex, method)
    {
      uint32_t imethod;
      const char *provider_url;
      struct GNUNET_JSON_Specification mspec[] = {
        GNUNET_JSON_spec_string ("provider",
                                 &provider_url),
        GNUNET_JSON_spec_uint32 ("authentication_method",
                                 &imethod),
        GNUNET_JSON_spec_end ()
      };
      json_t *jmethods;
      json_t *jmethod;

      if (GNUNET_OK !=
          GNUNET_JSON_parse (method,
                             mspec,
                             NULL, NULL))
      {
        json_dumpf (method,
                    stderr,
                    JSON_INDENT (2));
        GNUNET_break (0);
        continue;
      }
      jmethods = json_object_get (AG_redux_state,
                                  "authentication_methods");
      jmethod = json_array_get (jmethods,
                                imethod);
      {
        const char *instructions;
        const char *type;
        struct GNUNET_JSON_Specification tspec[] = {
          GNUNET_JSON_spec_string ("instructions",
                                   &instructions),
          GNUNET_JSON_spec_string ("type",
                                   &type),
          GNUNET_JSON_spec_end ()
        };
        struct TALER_Amount method_cost;
        const char *provider_name;

        if (GNUNET_OK !=
            GNUNET_JSON_parse (jmethod,
                               tspec,
                               NULL, NULL))
        {
          GNUNET_break (0);
          continue;
        }
        if (GNUNET_OK !=
            lookup_recovery_cost (provider_url,
                                  type,
                                  &method_cost))
        {
          GNUNET_break (0);
          continue;
        }
        provider_name = lookup_provider_name (provider_url);
        AG_list_append (AG_row_children (prow),
                        GNUNET_JSON_PACK (
                          GNUNET_JSON_pack_string ("policy_name",
                                                   instructions),
                          GNUNET_JSON_pack_string ("method_type",
                                                   type),
                          GNUNET_JSON_pack_string ("cost",
                                                   TALER_amount2s (
                                                     &method_cost)),
                          GNUNET_JSON_pack_string ("provider_url",
                                                   provider_url),
                          GNUNET_JSON_pack_string ("expiration",
                                                   "N/A"),
                          GNUNET_JSON_pack_uint64 ("policy_index",
                                                   pindex),
                          GNUNET_JSON_pack_bool ("is_challenge",
                                                 true),
                          GNUNET_JSON_pack_uint64 ("method_index",
                                                   mindex),
                          GNUNET_JSON_pack_string ("provider_name",
                                                   provider_name)));
        if (NULL == summary)
        {
          summary = GNUNET_strdup (type);
        }
        else
        {
          char *tmp;

          GNUNET_asprintf (&tmp,
                           "%s + %s",
                           summary,
                           type);
          GNUNET_free (summary);
          summary = tmp;
        }
      }
      GNUNET_JSON_parse_free (mspec);
    }
    if (NULL != summary)
    {
      AG_row_set (prow,
                  "policy_name",
                  json_string (summary));
      GNUNET_free (summary);
    }
    AG_list_add_row (ts,
                     prow);
    GNUNET_JSON_parse_free (pspec);
  }
  AG_sensitive ("anastasis_gtk_main_window_prev_button");
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  AG_show ("anastasis_gtk_backup_progress_scrolled_window");
  AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_show ("anastasis_gtk_b_policy_frame");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_main_window_prev_button");
  AG_enable_next ();
}


/**
 * Update GtkEntry @a name, setting text to @a value.
 *
 * @param name name of the widget to update
 * @param value value to set
 */
static void
update_entry (const char *name,
              const char *value)
{
  GtkEntry *entry;
  const char *old;

  if (NULL == value)
    value = "";
  entry = GTK_ENTRY (GCG_get_main_window_object (name));
  if (NULL == entry)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "`%s' is not a GtkEntry!\n",
                name);
    return;
  }
  old = gtk_editable_get_text (GTK_EDITABLE (entry));
  if (NULL == old)
    old = "";
  if (0 != strcmp (old,
                   value))
    gtk_editable_set_text (GTK_EDITABLE (entry),
                           value);
}


/**
 * Function called when we begin editing the secret.
 */
static void
action_secret_editing (void)
{
  struct GNUNET_TIME_Timestamp exp_time;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_timestamp ("expiration",
                                &exp_time),
    GNUNET_JSON_spec_end ()
  };
  struct tm tv;
  bool is_free = false;

  AG_hide_all_frames ();
  if (GNUNET_OK !=
      GNUNET_JSON_parse (AG_redux_state,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    AG_error ("State did not parse correctly: lacks expiration");
    return;
  }

  {
    time_t t;

    t = exp_time.abs_time.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us;
    GNUNET_assert (NULL !=
                   localtime_r (&t,
                                &tv));
  }

  {
    json_t *fees;

    fees = json_object_get (AG_redux_state,
                            "upload_fees");
    if (0 == json_array_size (fees))
    {
      update_label ("backup_fee_value_label",
                    _ ("gratis"));
      is_free = true;
    }
    else
    {
      char *val = GNUNET_strdup ("");
      size_t pos;
      json_t *fee;
      struct TALER_Amount a;

      json_array_foreach (fees, pos, fee)
      {
        struct GNUNET_JSON_Specification ispec[] = {
          TALER_JSON_spec_amount_any ("fee",
                                      &a),
          GNUNET_JSON_spec_end ()
        };
        char *tmp;

        if (GNUNET_OK !=
            GNUNET_JSON_parse (fee,
                               ispec,
                               NULL, NULL))
        {
          GNUNET_break (0);
          json_dumpf (fees,
                      stderr,
                      JSON_INDENT (2));
          continue;
        }

        GNUNET_asprintf (&tmp,
                         "%s%s%llu.%u %s",
                         val,
                         strlen (val) > 0 ? "\n" : "",
                         (unsigned long long) a.value,
                         (unsigned int) a.fraction,
                         a.currency);
        GNUNET_free (val);
        val = tmp;
      }
      update_label ("backup_fee_value_label",
                    val);
      GNUNET_free (val);
    }
  }
  {
    char estr[128];

    if (is_free)
      GNUNET_assert (sizeof (estr) >
                     strftime (estr,
                               sizeof (estr),
                               "%d %B %Y",
                               &tv));
    else
      GNUNET_assert (sizeof (estr) >
                     strftime (estr,
                               sizeof (estr),
                               "%d %B",
                               &tv));
    update_label ("expiration_date_without_year_label",
                  estr);
  }

  {
    GtkSpinButton *sb;
    unsigned int this_year;
    unsigned int exp_year;

    sb = GTK_SPIN_BUTTON (GCG_get_main_window_object (
                            "expiration_year_spin_button"));
    if (is_free)
      gtk_widget_set_visible (GTK_WIDGET (sb),
                              FALSE);
    else
      gtk_widget_set_visible (GTK_WIDGET (sb),
                              TRUE);
    this_year = GNUNET_TIME_get_current_year ();
    /* We allow at most 5 years into the future */
    gtk_spin_button_set_range (sb,
                               this_year + 1,
                               this_year + 6);
    exp_year = tv.tm_year + 1900;
    gtk_spin_button_set_value (sb,
                               (double) exp_year);
  }

  AG_insensitive ("anastasis_gtk_main_window_forward_button");
  AG_sensitive ("anastasis_gtk_enter_secret_open_button");
  AG_sensitive ("anastasis_gtk_enter_secret_textview");
  AG_hide ("anastasis_gtk_secret_clear_file_button");
  AG_hide ("anastasis_gtk_secret_clear_text_button");
  AG_hide ("anastasis_gtk_secret_file_name_hbox");
  AG_show ("anastasis_gtk_secret_file_chooser_hbox");
  {
    const char *name = "";
    const json_t *jsecret = NULL;
    const char *filename = NULL;
    struct GNUNET_JSON_Specification ispec[] = {
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_object_const ("core_secret",
                                       &jsecret),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("secret_name",
                                 &name),
        NULL),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (AG_redux_state,
                           ispec,
                           NULL, NULL))
    {
      GNUNET_break (0);
      json_dumpf (AG_redux_state,
                  stderr,
                  JSON_INDENT (2));
      AG_error ("State did not parse correctly: invalid secret data");
      return;
    }
    if (! AG_in_secret_name_editing)
      update_entry ("anastasis_gtk_secret_name_entry",
                    name);
    if (NULL != jsecret)
    {
      const char *mime = NULL;
      const char *text = NULL;
      struct GNUNET_JSON_Specification sspec[] = {
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_string ("text",
                                   &text),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_string ("mime",
                                   &mime),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_string ("filename",
                                   &filename),
          NULL),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (jsecret,
                             sspec,
                             NULL, NULL))
      {
        GNUNET_break (0);
        json_dumpf (AG_redux_state,
                    stderr,
                    JSON_INDENT (2));
        AG_error ("State did not parse correctly: invalid secret data");
        return;
      }
      if ( (NULL != text) &&
           (0 == strlen (text)) )
        text = NULL;
      if (! AG_in_secret_editing)
      {
        GtkTextBuffer *tb = GTK_TEXT_BUFFER (GCG_get_main_window_object (
                                               "anastasis_gtk_enter_secret_textbuffer"));
        const char *old;
        GtkTextIter start;
        GtkTextIter end;

        gtk_text_buffer_get_start_iter (tb,
                                        &start);
        gtk_text_buffer_get_end_iter (tb,
                                      &end);
        old = gtk_text_buffer_get_text (tb,
                                        &start,
                                        &end,
                                        true);
        if (0 != strcmp (old,
                         NULL != text
       ? text
       : ""))
          gtk_text_buffer_set_text (tb,
                                    NULL != text
            ? text
            : "",
                                    -1);
      }
      update_label ("anastasis_gtk_secret_file_name_label",
                    filename);
      if ( (NULL != text) ||
           (NULL != filename) )
      {
        AG_enable_next ();
      }
      if (NULL != text)
      {
        AG_insensitive ("anastasis_gtk_enter_secret_open_button");
        AG_show ("anastasis_gtk_secret_clear_text_button");
      }
      if (NULL != filename)
      {
        AG_insensitive ("anastasis_gtk_enter_secret_textview");
        AG_show ("anastasis_gtk_secret_clear_file_button");
        AG_show ("anastasis_gtk_secret_file_name_hbox");
        AG_hide ("anastasis_gtk_secret_file_chooser_hbox");
      }
      GNUNET_JSON_parse_free (sspec);
    }
    else
    {
      /* secret is NULL */
      GtkTextBuffer *tb = GTK_TEXT_BUFFER (GCG_get_main_window_object (
                                             "anastasis_gtk_enter_secret_textbuffer"));

      gtk_text_buffer_set_text (tb,
                                "",
                                -1);
    }
    if ( (NULL == name) ||
         (0 == strlen (name) ) )
      AG_focus ("anastasis_gtk_secret_name_entry");
    else if (NULL == filename)
      AG_focus ("anastasis_gtk_enter_secret_textview");
  }
  AG_sensitive ("anastasis_gtk_main_window_prev_button");
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  AG_show ("anastasis_gtk_backup_progress_scrolled_window");
  AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_show ("anastasis_gtk_enter_secret_frame");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_main_window_prev_button");
  AG_enable_next ();
}


/**
 * Add a row with the QR code of the payment request @a payto to the list of
 * unpaid payment requests.
 *
 * @param payto the "taler://pay" URI to encode
 * @param provider the provider that wants to be paid
 */
static void
add_qrcode (const char *payto,
            const char *provider)
{
  GtkWidget *w;
  GdkPixbuf *pb;
  GListStore *list;
  AGRow *row;

  w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview"));
  if (NULL == w)
  {
    GNUNET_break (0);
    return;
  }
  pb = AG_setup_qrcode (w,
                        payto,
                        strlen (payto));
  if (NULL == pb)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Failed to initialize QR-code pixbuf for `%s'\n"),
                payto);
    return;
  }
  list = AG_list ("unpaid_qrcodes_liststore");
  if (NULL == list)
  {
    g_object_unref (pb);
    return;
  }
  row = AG_row_new (GNUNET_JSON_PACK (
                      GNUNET_JSON_pack_string ("url",
                                               payto),
                      GNUNET_JSON_pack_string ("provider",
                                               provider)));
  /* the row must know its image before it is added to the list, as the
     list renders it right away */
  AG_row_set_image (row,
                    GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pb)));
  g_object_unref (pb);
  AG_list_add_row (list,
                   row);
}


static void
action_truths_paying (void)
{
  json_t *pprs;
  size_t index;
  json_t *pt;

  AG_hide_all_frames ();
  AG_list_clear ("unpaid_qrcodes_liststore");
  pprs = json_object_get (AG_redux_state,
                          "payments");
  json_array_foreach (pprs, index, pt)
  {
    const char *payto = json_string_value (pt);

    if (NULL == payto)
    {
      GNUNET_break (0);
      continue;
    }
    add_qrcode (payto,
                "");
  }

  {
    json_t *args;
    struct GNUNET_TIME_Relative timeout;

    timeout = GNUNET_TIME_UNIT_MINUTES;
    GNUNET_assert (NULL == AG_ra);
    args = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_time_rel ("timeout",
                                 timeout));
    AG_ra = ANASTASIS_redux_action (AG_redux_state,
                                    "pay",
                                    args,
                                    &AG_action_cb,
                                    NULL);
    json_decref (args);
  }
  AG_show ("anastasis_gtk_pay_frame");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  AG_show ("anastasis_gtk_backup_progress_scrolled_window");
  AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_hide ("anastasis_gtk_main_window_prev_button");
  AG_hide ("anastasis_gtk_main_window_forward_button");
}


static void
action_policies_paying (void)
{
  json_t *pprs;
  size_t index;
  json_t *ppr;

  AG_hide_all_frames ();
  AG_list_clear ("unpaid_qrcodes_liststore");
  pprs = json_object_get (AG_redux_state,
                          "policy_payment_requests");
  json_array_foreach (pprs, index, ppr)
  {
    const char *provider;
    const char *payto;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("provider",
                               &provider),
      GNUNET_JSON_spec_string ("payto",
                               &payto),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (ppr,
                           spec,
                           NULL, NULL))
    {
      GNUNET_break (0);
      continue;
    }
    add_qrcode (payto,
                provider);
  }
  {
    json_t *args;
    struct GNUNET_TIME_Relative timeout;

    timeout = GNUNET_TIME_UNIT_MINUTES;
    GNUNET_assert (NULL == AG_ra);
    args = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_time_rel ("timeout",
                                 timeout));
    AG_ra = ANASTASIS_redux_action (AG_redux_state,
                                    "pay",
                                    args,
                                    &AG_action_cb,
                                    NULL);
    json_decref (args);
  }
  AG_show ("anastasis_gtk_pay_frame");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  AG_show ("anastasis_gtk_backup_progress_scrolled_window");
  AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_hide ("anastasis_gtk_main_window_prev_button");
  AG_hide ("anastasis_gtk_main_window_forward_button");
}


/**
 * The backup has finished, show the providers, policy version and
 * expiration dates.
 */
static void
action_backup_finished (void)
{
  json_t *det;
  json_t *se;
  const char *url;
  struct GNUNET_TIME_Timestamp mexp;

  AG_hide_all_frames ();
  det = json_object_get (AG_redux_state,
                         "success_details");
  AG_list_clear ("backup_provider_liststore");
  mexp = GNUNET_TIME_UNIT_FOREVER_TS;
  json_object_foreach (det, url, se)
  {
    struct GNUNET_TIME_Timestamp pexp;
    uint64_t version;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_uint64 ("policy_version",
                               &version),
      GNUNET_JSON_spec_timestamp ("policy_expiration",
                                  &pexp),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (se,
                           spec,
                           NULL, NULL))
    {
      GNUNET_break_op (0);
      AG_error ("State did not parse correctly");
      return;
    }
    mexp = GNUNET_TIME_timestamp_min (mexp,
                                      pexp);
    AG_list_add ("backup_provider_liststore",
                 GNUNET_JSON_PACK (
                   GNUNET_JSON_pack_string ("provider_url",
                                            url),
                   GNUNET_JSON_pack_uint64 ("version",
                                            version),
                   GNUNET_JSON_pack_string ("expiration",
                                            GNUNET_TIME_timestamp2s (pexp)),
                   GNUNET_JSON_pack_bool ("success",
                                          true),
                   GNUNET_JSON_pack_string ("provider_name",
                                            lookup_provider_name (url))));
  }
  {
    struct tm tv;
    char estr[128];
    time_t t;
    struct GNUNET_TIME_Absolute sexp;

    /* be more conservative in what we show */
    sexp = GNUNET_TIME_absolute_subtract (mexp.abs_time,
                                          GNUNET_TIME_UNIT_DAYS);
    t = sexp.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us;
    GNUNET_assert (NULL !=
                   localtime_r (&t,
                                &tv));
    GNUNET_assert (sizeof (estr) >
                   strftime (estr,
                             sizeof (estr),
                             "%d %B %Y",
                             &tv));
    update_label ("backup_expiration_date_label",
                  GNUNET_TIME_absolute2s (sexp));
  }
  AG_hide ("anastasis_gtk_progress_vbox");
  AG_hide ("anastasis_gtk_backup_progress_scrolled_window");
  AG_show ("anastasis_gtk_completed_frame");
  AG_show ("anastasis_gtk_backup_complete_box");
  AG_hide ("anastasis_gtk_success_recovery_box");
  AG_show ("anastasis_gtk_success_backup_label");
  AG_hide ("anastasis_gtk_success_recovery_box");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_hide ("anastasis_gtk_main_window_save_as_button");
  AG_show ("anastasis_gtk_restart_button");
  AG_show ("anastasis_gtk_main_window_quit_button");
  AG_hide ("anastasis_gtk_main_window_prev_button");
  AG_hide ("anastasis_gtk_main_window_forward_button");
}


static const json_t *
find_challenge_by_uuid (const char *uuid)
{
  json_t *rd;
  json_t *cs;
  size_t index;
  json_t *c;

  rd = json_object_get (AG_redux_state,
                        "recovery_document");
  cs = json_object_get (rd,
                        "challenges");
  json_array_foreach (cs, index, c)
  {
    const char *u;

    u = json_string_value (json_object_get (c,
                                            "uuid"));
    if (NULL == u)
    {
      GNUNET_break (0);
      continue;
    }
    if (0 == strcmp (u,
                     uuid))
      return c;
  }
  return NULL;
}


/**
 * Update the list store with the challenge feedback.
 *
 * @param uuid the UUID to render feedback for
 * @param builder the builder to use to grab display widgets
 */
static void
show_challenge_feedback (const char *uuid,
                         GtkBuilder *builder)
{
  json_t *cf;
  const json_t *f;
  const char *state;
  const char *hint = NULL;
  const char *taler_pay_uri = NULL;
  uint32_t ec = TALER_EC_NONE;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("state",
                             &state),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("taler_pay_uri",
                               &taler_pay_uri),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("display_hint",
                               &hint),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_uint32 ("error_code",
                               &ec),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  cf = json_object_get (AG_redux_state,
                        "challenge_feedback");
  f = json_object_get (cf, uuid);
  if (NULL == f)
    return; /* no feedback */
  if (GNUNET_OK !=
      GNUNET_JSON_parse (f,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    json_dumpf (f,
                stderr,
                JSON_INDENT (2));
    return;
  }
  if (0)
  {
    /* FIXME: do we want to show the payment request here at all? */
    if (NULL != taler_pay_uri)
    {
      GdkPixbuf *qr;
      GtkWidget *w = NULL;

      qr = AG_setup_qrcode (w,
                            taler_pay_uri,
                            strlen (taler_pay_uri));
      g_object_unref (qr);
    }
  }
  if (TALER_EC_NONE != ec)
  {
    const char *emsg;
    GtkLabel *l;
    char *msg;

    emsg = TALER_ErrorCode_get_hint (ec);
    GNUNET_asprintf (&msg,
                     "#%u: %s",
                     (unsigned int) ec,
                     emsg);
    l = GTK_LABEL (gtk_builder_get_object (builder,
                                           "error_label"));
    gtk_label_set_text (l,
                        msg);
    GNUNET_free (msg);
    gtk_widget_set_visible (GTK_WIDGET (l),
                            TRUE);
  }
  if (NULL != hint)
  {
    GtkLabel *l;

    l = GTK_LABEL (gtk_builder_get_object (builder,
                                           "hint_label"));
    gtk_label_set_text (l,
                        hint);
    gtk_widget_set_visible (GTK_WIDGET (l),
                            TRUE);
  }
  GNUNET_JSON_parse_free (spec);
}


/**
 * Function called on each discovered recovery policy. Called
 * with all arguments NULL if we have received all policies that
 * we could possibly receive for the current operation.
 *
 * The client can then start a new policy discovery process, using the
 * smallest (also most recent) @a version received per @a provider_url
 * in the cursor to resume.  Note that in this case, the application
 * logic is responsible for de-duplication using @a hcpd, or it may show
 * policies again if they are at different providers under versions not
 * queried up to the cursor.
 *
 * @param cls closure with the list of secrets to update.
 * @param hcpd hash of the compressed policy document (unique per policy)
 * @param provider_url which provider claims to have this policy
 * @param version version of the policy at this provider
 * @param attribute_mask combination of optional identity attributes
 *           present in the state that was used to locate this version
 * @param server_time when did the provider receive the upload
 * @param secret_name name the user assigned to the backup
 * @param providers json array of providers offering this policy
 */
static void
expand_policy_list (void *cls,
                    const struct GNUNET_HashCode *hcpd,
                    const char *provider_url,
                    uint32_t version,
                    json_int_t attribute_mask,
                    struct GNUNET_TIME_Timestamp server_time,
                    const char *secret_name,
                    const json_t *providers)
{
  GListStore *ls = cls;
  bool have_first;

  have_first = (0 != g_list_model_get_n_items (G_LIST_MODEL (ls)));
  AG_list_append (
    ls,
    GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("provider_url",
                               provider_url),
      GNUNET_JSON_pack_uint64 ("version",
                               version),
      GNUNET_JSON_pack_uint64 ("attribute_mask",
                               attribute_mask),
      GNUNET_JSON_pack_string ("secret_name",
                               secret_name),
      GNUNET_JSON_pack_string ("date_string",
                               GNUNET_TIME_timestamp2s (server_time)),
      GNUNET_JSON_pack_uint64 ("date",
                               server_time.abs_time.abs_value_us),
      GNUNET_JSON_pack_array_incref ("providers",
                                     (json_t *) providers)));
  if (! have_first)
    AG_select_row ("anastasis_gtk_secret_selection_treeselection",
                   0);
}


/**
 * Function called with the results of #ANASTASIS_redux_action on "poll".
 *
 * @param cls NULL
 * @param error_code Error code
 * @param response new state as result or config information of a provider
 */
static void
long_poll_challenges_cb (void *cls,
                         enum TALER_ErrorCode error_code,
                         json_t *response);


/**
 * Schedules the "poll" challenges action.
 *
 * @param cls NULL
 */
static void
long_poll_challenges_task (void *cls)
{
  struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_CHALLENGES];
  json_t *tspec;

  (void) cls;
  la->task = NULL;
  if (GNUNET_TIME_absolute_is_future (la->next_time))
  {
    la->task = GNUNET_SCHEDULER_add_at (la->next_time,
                                        &long_poll_challenges_task,
                                        NULL);
    return;
  }
  la->next_time = GNUNET_TIME_relative_to_absolute (LP_TIMEOUT);
  tspec = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_time_rel ("timeout",
                               LP_TIMEOUT));
  la->ra = ANASTASIS_redux_action (AG_redux_state,
                                   "poll",
                                   tspec,
                                   &long_poll_challenges_cb,
                                   NULL);
  json_decref (tspec);
}


static void
long_poll_challenges_cb (void *cls,
                         enum TALER_ErrorCode error_code,
                         json_t *response)
{
  struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_CHALLENGES];

  (void) cls;
  la->ra = NULL;
  switch (error_code)
  {
  case TALER_EC_NONE:
    /* continued below */
    break;
  default:
    AG_error (_ ("poll failed: %s (#%u)"),
              TALER_ErrorCode_get_hint (error_code),
              (unsigned int) error_code);
    /* simply try again */
    la->task = GNUNET_SCHEDULER_add_now (&long_poll_challenges_task,
                                         NULL);
    return;
  }
  AG_action_cb (NULL,
                TALER_EC_NONE,
                response);
}


/**
 * Function called with the results of #ANASTASIS_redux_action on "sync_providers".
 *
 * @param cls NULL
 * @param error_code Error code
 * @param response new state as result or config information of a provider
 */
static void
long_poll_sync_action_cb (void *cls,
                          enum TALER_ErrorCode error_code,
                          json_t *response);


/**
 * Schedules the "sync_providers" action.
 *
 * @param cls NULL
 */
static void
long_poll_sync_task (void *cls)
{
  struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_SYNC_PROVIDERS];
  json_t *tspec;

  (void) cls;
  la->task = NULL;
  if (GNUNET_TIME_absolute_is_future (la->next_time))
  {
    la->task = GNUNET_SCHEDULER_add_at (la->next_time,
                                        &long_poll_sync_task,
                                        NULL);
    return;
  }
  la->next_time = GNUNET_TIME_relative_to_absolute (LP_TIMEOUT);
  tspec = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_time_rel ("timeout",
                               LP_TIMEOUT));
  la->ra = ANASTASIS_redux_action (AG_redux_state,
                                   "sync_providers",
                                   tspec,
                                   &long_poll_sync_action_cb,
                                   NULL);
  json_decref (tspec);
}


static void
long_poll_sync_action_cb (void *cls,
                          enum TALER_ErrorCode error_code,
                          json_t *response)
{
  struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_SYNC_PROVIDERS];

  (void) cls;
  la->ra = NULL;
  switch (error_code)
  {
  case TALER_EC_NONE:
    /* continued below */
    break;
  case TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID:
    /* we are in sync, nothing left to do */
    return;
  default:
    AG_error (_ ("Synchronizing with providers failed: %s (#%u)"),
              TALER_ErrorCode_get_hint (error_code),
              (unsigned int) error_code);
    /* simply try again */
    la->task = GNUNET_SCHEDULER_add_now (&long_poll_sync_task,
                                         NULL);
    return;
  }
  AG_action_cb (NULL,
                TALER_EC_NONE,
                response);
}


/**
 * Start policy discovery process. Triggers download(s)
 * of the various provider configurations and (once we
 * have any) starts the policy discovery process.
 */
static void
begin_discovery (void);


static void
long_poll_providers_action_cb (void *cls,
                               enum TALER_ErrorCode error_code,
                               json_t *response)
{
  struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_PROVIDERS];
  json_t *ap;
  const char *url;
  json_t *obj;

  la->ra = NULL;
  switch (error_code)
  {
  case TALER_EC_NONE:
    /* continued below */
    break;
  default:
    AG_error (_ ("Polling for providers failed: %s (#%u)"),
              TALER_ErrorCode_get_hint (error_code),
              (unsigned int) error_code);
    /* simply try again */
    la->task = GNUNET_SCHEDULER_add_now (&long_poll_providers_task,
                                         NULL);
    return;
  }
  ap = json_object_get (response,
                        "authentication_providers");
  GNUNET_assert (NULL != ap);
  if (NULL == AG_pd)
  {
    json_t *ns;

    /* Simply merge the state */
    ap = json_object_get (response,
                          "authentication_providers");
    ns = json_incref (AG_redux_state);
    GNUNET_assert (0 ==
                   json_object_set (ns,
                                    "authentication_providers",
                                    ap));
    AG_action_cb (NULL,
                  TALER_EC_NONE,
                  ns);
    json_decref (ns);
    return;
  }
  /* Find out which provider is new, merge that one! */
  json_object_foreach (ap, url, obj)
  {
    struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;

    if (GNUNET_OK ==
        ANASTASIS_reducer_lookup_salt (AG_redux_state,
                                       url,
                                       &provider_salt))
      continue;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Expanding policy discovery to recently discovered live provider `%s'\n",
                url);
    ANASTASIS_policy_discovery_more (AG_pd,
                                     url,
                                     obj);
  }
  /* update our state to the new state, but without
     going through the usual action loop */
  json_decref (AG_redux_state);
  AG_redux_state = json_incref (response);
  GNUNET_assert (NULL != AG_redux_state);
}


static void
begin_discovery (void)
{
  GListStore *ls;
  bool have_providers;

  ls = AG_list ("secret_selection_liststore");
  GNUNET_assert (NULL != ls);
  if (NULL == AG_pd)
    g_list_store_remove_all (ls);
  have_providers = sync_providers ();
  if (NULL == AG_pd)
    AG_pd = ANASTASIS_policy_discovery_start (AG_redux_state,
                                              NULL,
                                              &expand_policy_list,
                                              ls);
  if (! have_providers)
  {
    AG_error (_ ("No available providers! Try to add one!"));
  }
}


/**
 * Function called when it is time for the user to select a secret
 * from the list of secrets.  Builds the respective tree model.
 */
static void
action_secret_selecting (void)
{
  AG_hide ("anastasis_gtk_start_frame");
  AG_stop_long_action ();
  if (AG_have_error)
    AG_show ("anastasis_gtk_error_label");
  AG_hide ("anastasis_gtk_challenge_frame");
  AG_hide ("anastasis_gtk_identity_frame");
  if (NULL == AG_pd)
    begin_discovery ();
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  AG_show ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_hide ("anastasis_gtk_backup_progress_scrolled_window");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_main_window_save_as_button");
  AG_show ("anastasis_gtk_select_secret_frame");
  AG_show ("anastasis_gtk_main_window_prev_button");
  AG_hide ("anastasis_gtk_main_window_quit_button");
  AG_insensitive ("anastasis_gtk_main_window_forward_button");
}


/**
 * Callback invoked if the a "challenge"-button is clicked.
 *
 * @param object
 * @param user_data points to the UUID of the challenge
 */
static void
challenge_button_clicked_cb (GObject *object,
                             gpointer user_data)
{
  const char *uuid = user_data;
  json_t *args;

  (void) object;
  args = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_string ("uuid",
                             uuid));
  AG_freeze ();
  AG_ra = ANASTASIS_redux_action (AG_redux_state,
                                  "select_challenge",
                                  args,
                                  &AG_action_cb,
                                  NULL);
  json_decref (args);
}


/**
 * Generate widget about a @a challenge and add it to the
 * @a challenge_box.
 *
 * @param challenge_box box to expand
 * @param rd our recovery document (to use)
 * @param challenge the challenge to add
 * @return #GNUNET_OK on success, #GNUNET_NO
 *    if the provider is down, #GNUNET_SYSERR on harder
 *    internal failures
 */
static enum GNUNET_GenericReturnValue
add_challenge (GtkBox *challenge_box,
               json_t *rd,
               json_t *uchallenge)
{
  GtkBuilder *builder;
  GtkWindow *window;
  GtkWidget *hbox;
  GtkLabel *label;
  GtkButton *button;
  const char *instructions;
  const char *provider;
  const char *type;
  const char *uuid;
  struct TALER_Amount cost;
  bool async = false;
  bool solved = false;
  struct GNUNET_JSON_Specification uspec[] = {
    GNUNET_JSON_spec_string ("uuid",
                             &uuid),
    GNUNET_JSON_spec_end ()
  };
  const char *iuuid;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("instructions",
                             &instructions),
    GNUNET_JSON_spec_string ("type",
                             &type),
    GNUNET_JSON_spec_string ("url",
                             &provider),
    GNUNET_JSON_spec_string ("uuid",
                             &iuuid),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_bool ("async",
                             &async),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_bool ("solved",
                             &solved),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  bool ok = true;
  enum GNUNET_GenericReturnValue ret;

  if (GNUNET_OK !=
      GNUNET_JSON_parse (uchallenge,
                         uspec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  {
    json_t *challenges;
    size_t index;
    json_t *challenge;

    challenges = json_object_get (rd,
                                  "challenges");
    /* FIXME: change data structure to have 'uuid'
       as the index into the 'challenges' object, instead of this
       'challenges' being an array */
    json_array_foreach (challenges, index, challenge)
    {
      if (GNUNET_OK !=
          GNUNET_JSON_parse (challenge,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
      if (0 == strcmp (uuid,
                       iuuid))
        break;
    }
  }
  ret = lookup_recovery_cost (provider,
                              type,
                              &cost);
  if (GNUNET_SYSERR == ret)
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Could not find cost of %s at %s\n",
                type,
                provider);
    json_dumpf (AG_redux_state,
                stderr,
                JSON_INDENT (2));
    return GNUNET_SYSERR;
  }
  if (GNUNET_NO == ret)
    ok = false;

  builder = ANASTASIS_GTK_get_new_builder (
    ANASTASIS_GTK_project_data (),
    "anastasis_gtk_challenge_template.ui",
    NULL);
  window = GTK_WINDOW (gtk_builder_get_object (builder,
                                               "challenge_template_window"));
  hbox = GTK_WIDGET (gtk_builder_get_object (builder,
                                             "challenge_hbox"));
  label = GTK_LABEL (gtk_builder_get_object (builder,
                                             "challenge_label"));
  button = GTK_BUTTON (gtk_builder_get_object (builder,
                                               "solve_challenge_button"));
  if (! ok)
    gtk_widget_set_sensitive (GTK_WIDGET (button),
                              false);
  if (solved)
  {
    GtkWidget *solved_img;

    solved_img = GTK_WIDGET (gtk_builder_get_object (builder,
                                                     "challenge_solved_image"));
    gtk_widget_set_visible (solved_img,
                            TRUE);
    gtk_widget_set_visible (GTK_WIDGET (button),
                            FALSE);
  }
  else
  {
    g_signal_connect (button,
                      "clicked",
                      G_CALLBACK (challenge_button_clicked_cb),
                      (void *) uuid);
    if (ok)
      gtk_widget_set_tooltip_text (GTK_WIDGET (button),
                                   TALER_amount2s (&cost));
    else
      gtk_widget_set_tooltip_text (GTK_WIDGET (button),
                                   _ ("unknown"));
  }
  {
    GtkWidget *icon;
    char *lab;

    GNUNET_asprintf (&lab,
                     "challenge_category_%s_image",
                     type);
    icon = GTK_WIDGET (gtk_builder_get_object (builder,
                                               lab));
    GNUNET_free (lab);
    if (NULL != icon)
      gtk_widget_set_visible (icon,
                              TRUE);
  }
  show_challenge_feedback (uuid,
                           builder);
  if (ok)
    gtk_label_set_text (label,
                        instructions);
  else
    gtk_label_set_text (label,
                        _ ("provider down"));
  g_object_ref (hbox);
  gtk_window_set_child (window,
                        NULL);
  g_object_unref (builder);
  pad_along (GTK_ORIENTATION_VERTICAL,
             hbox,
             15);
  gtk_box_append (challenge_box,
                  hbox);
  g_object_unref (hbox);
  if (! ok)
    return GNUNET_NO;
  return GNUNET_OK;
}


/**
 * Generate widget about a @a policy and add it to the
 * @a policy_box.
 *
 * @param policy_box box to expand
 * @param rd our recovery document (to use)
 * @param pindex policy index (for the label)
 * @param policy policy to add
 * @return true if we should long poll "sync_providers"
 */
static bool
add_policy (GtkBox *policy_box,
            json_t *rd,
            size_t pindex,
            json_t *policy)
{
  GtkBuilder *builder;
  GtkWindow *window;
  GtkWidget *widget;
  GtkLabel *label;
  GtkBox *vbox;
  char *txt;
  json_t *challenges;
  bool ok;

  challenges = json_object_get (policy,
                                "challenges");
  if (NULL == challenges)
  {
    GNUNET_break_op (0);
    AG_error ("Policy did not parse correctly");
    return false;
  }
  builder = ANASTASIS_GTK_get_new_builder (
    ANASTASIS_GTK_project_data (),
    "anastasis_gtk_policy_template.ui",
    NULL);
  window = GTK_WINDOW (gtk_builder_get_object (builder,
                                               "policy_template_window"));
  widget = GTK_WIDGET (gtk_builder_get_object (builder,
                                               "policy_frame"));
  label = GTK_LABEL (gtk_builder_get_object (builder,
                                             "policy_label"));
  vbox = GTK_BOX (gtk_builder_get_object (builder,
                                          "policy_vbox"));
  ok = true;
  {
    size_t index;
    json_t *challenge;

    json_array_foreach (challenges, index, challenge)
    {
      enum GNUNET_GenericReturnValue ret;

      ret = add_challenge (vbox,
                           rd,
                           challenge);
      if (GNUNET_SYSERR == ret)
      {
        GNUNET_break (0);
        g_object_unref (builder);
        GNUNET_asprintf (&txt,
                         "Failed to process challenge #%u of policy #%u",
                         (unsigned int) (1 + index),
                         (unsigned int) (1 + pindex));
        AG_error ("%s",
                  txt);
        GNUNET_free (txt);
        return false;
      }
      if (GNUNET_NO == ret)
      {
        gtk_widget_set_sensitive (GTK_WIDGET (vbox),
                                  false);
        ok = false;
      }
    }
  }
  if (ok)
  {
    GNUNET_asprintf (&txt,
                     _ ("Policy #%u"),
                     (unsigned int) (1 + pindex));
  }
  else
  {
    GNUNET_asprintf (&txt,
                     _ ("Policy #%u (unavailable, provider down)"),
                     (unsigned int) (1 + pindex));
  }
  gtk_label_set_text (label,
                      txt);
  GNUNET_free (txt);
  g_object_ref (widget);
  gtk_window_set_child (window,
                        NULL);
  g_object_unref (builder);
  pad_along (GTK_ORIENTATION_VERTICAL,
             widget,
             15);
  gtk_box_append (policy_box,
                  widget);
  g_object_unref (widget);
  return ! ok;
}


/**
 * The user must select the next challenge to solve
 * during the recovery process.
 */
static void
action_challenge_selecting (void)
{
  json_t *rd;
  json_t *policies;
  GtkBox *policy_box;
  bool sp = false;

  AG_hide_all_frames ();
  AG_stop_long_action ();
  rd = json_object_get (AG_redux_state,
                        "recovery_document");
  policies = json_object_get (rd,
                              "decryption_policies");
  GNUNET_assert (NULL != policies);
  policy_box = GTK_BOX (GCG_get_main_window_object (
                          "anastasis_gtk_policy_vbox"));
  /* remove all existing entries from the box */
  {
    GtkWidget *child;

    while (NULL != (child = gtk_widget_get_first_child (
                      GTK_WIDGET (policy_box))))
      gtk_box_remove (policy_box,
                      child);
  }
  {
    size_t pindex;
    json_t *policy;

    json_array_foreach (policies, pindex, policy)
    {
      sp |= add_policy (policy_box,
                        rd,
                        pindex,
                        policy);
    }
  }
  if (sp)
  {
    struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_SYNC_PROVIDERS];

    la->next_time = GNUNET_TIME_UNIT_ZERO_ABS;
    GNUNET_assert (NULL == la->task);
    la->task = GNUNET_SCHEDULER_add_now (&long_poll_sync_task,
                                         NULL);
  }

  {
    struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_CHALLENGES];
    json_t *challenges;
    size_t index;
    json_t *challenge;

    challenges = json_object_get (rd,
                                  "challenges");
    GNUNET_assert (NULL != challenges);
    json_array_foreach (challenges, index, challenge)
    {
      bool async = false;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_bool ("async",
                                 &async),
          NULL),
        GNUNET_JSON_spec_end ()
      };
      const json_t *ks;

      ks = json_object_get (challenge,
                            "key_share");
      if ( (NULL != ks) &&
           (! json_is_null (ks)) )
        continue;   /* already solved */
      if (GNUNET_OK !=
          GNUNET_JSON_parse (challenge,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break (0);
        json_dumpf (challenge,
                    stderr,
                    JSON_INDENT (2));
        continue;
      }
      if (async &&
          (NULL == la->task) )
      {
        la->next_time = GNUNET_TIME_UNIT_ZERO_ABS;
        la->task = GNUNET_SCHEDULER_add_now (&long_poll_challenges_task,
                                             NULL);
      }
    }
  }

  AG_sensitive ("anastasis_gtk_review_policy_treeview");
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  AG_show ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_hide ("anastasis_gtk_backup_progress_scrolled_window");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_show ("anastasis_gtk_main_window_save_as_button");
  AG_show ("anastasis_gtk_challenge_frame");
  AG_show ("anastasis_gtk_main_window_prev_button");
  AG_hide ("anastasis_gtk_main_window_quit_button");
  AG_hide ("anastasis_gtk_main_window_forward_button");
}


/**
 * An Anastasis provider requires payment for a challenge.
 * Give opportunity to the user to pay.
 */
static void
action_challenge_paying (void)
{
  json_t *pprs;
  json_t *ppr;
  const char *uuid;
  bool found = false;
  const char *ps;

  AG_hide_all_frames ();
  AG_list_clear ("unpaid_qrcodes_liststore");
  pprs = json_object_get (AG_redux_state,
                          "challenge_feedback");
  json_object_foreach (pprs, uuid, ppr)
  {
    const char *state;
    const char *payto = NULL;
    const char *provider = NULL;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("taler_pay_uri",
                               &payto),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("provider",
                                 &provider),
        NULL),
      GNUNET_JSON_spec_string ("payment_secret",
                               &ps),
      GNUNET_JSON_spec_end ()
    };

    /* The state must be tested before the payment details are parsed: the
       other feedback states have no payment secret, and the reducer calls
       this one "taler-payment" (it was "payment" before Anastasis v0.3.0). */
    state = json_string_value (json_object_get (ppr,
                                                "state"));
    if ( (NULL == state) ||
         (0 != strcmp (state,
                       "taler-payment")) )
      continue; /* this challenge does not want to be paid for */
    if (GNUNET_OK !=
        GNUNET_JSON_parse (ppr,
                           spec,
                           NULL, NULL))
    {
      GNUNET_break (0);
      json_dumpf (ppr,
                  stderr,
                  JSON_INDENT (2));
      continue;
    }
    found = true;
    add_qrcode (payto,
                (NULL != provider) ? provider : "");
    break;
  }

  if (found)
  {
    json_t *args;
    struct GNUNET_TIME_Relative timeout;

    timeout = GNUNET_TIME_UNIT_MINUTES;
    GNUNET_assert (NULL == AG_ra);
    args = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_time_rel ("timeout",
                                 timeout),
      GNUNET_JSON_pack_string ("payment_secret",
                               ps));
    AG_ra = ANASTASIS_redux_action (AG_redux_state,
                                    "pay",
                                    args,
                                    &AG_action_cb,
                                    NULL);
    json_decref (args);
  }
  else
  {
    AG_error ("ERROR: Internal error: should pay, but do not know what");
  }
  AG_show ("anastasis_gtk_progress_vbox");
  AG_progress_update ();
  AG_show ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_hide ("anastasis_gtk_backup_progress_scrolled_window");
  AG_show ("anastasis_gtk_pay_frame");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_sensitive ("anastasis_gtk_main_window_prev_button");
  AG_show ("anastasis_gtk_main_window_prev_button");
  AG_hide ("anastasis_gtk_main_window_forward_button");
}


/**
 * Render challenge feedback for challenge @a uuid_str in a dialog of
 * @a builder in the label under @a target_widget.
 *
 * Useful in case the operation previously failed at the
 * server and we have some useful information to return
 * to the user.
 *
 * FIXME: only do it AFTER the first attempt of the
 * user to enter a code, and/or change what the
 * server returns so we do NOT render a confusing
 * error message on first use!
 *
 * @param builder a builder to get widgets from
 * @param target_widget the widget to update
 * @param uuid_str the UUID to render feedback for
 */
static void
render_feedback (GtkBuilder *builder,
                 const char *target_widget,
                 const char *uuid_str)
{
  json_t *cf;
  json_t *cs;
  const char *state;
  const char *redirect_url = NULL;
  const char *hint = NULL;
  json_t *details = NULL;
  const char *taler_pay_uri = NULL;
  uint32_t ec = TALER_EC_NONE;
  uint32_t http_status = 0;
  bool hide = false;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("state",
                             &state),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("taler_pay_uri",
                               &taler_pay_uri),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_json ("details",
                             &details),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("redirect_url",
                               &redirect_url),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("hint",
                               &hint),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_uint32 ("http_status",
                               &http_status),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_uint32 ("error_code",
                               &ec),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  GtkLabel *elabel;
  char *msg;

  cf = json_object_get (AG_redux_state,
                        "challenge_feedback");
  cs = json_object_get (cf,
                        uuid_str);
  if (NULL == cs)
    return;

  elabel = GTK_LABEL (gtk_builder_get_object (builder,
                                              target_widget));
  if (NULL == elabel)
  {
    GNUNET_break (0);
    return;
  }
  if (GNUNET_OK !=
      GNUNET_JSON_parse (cs,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    gtk_label_set_text (elabel,
                        _ ("INTERNAL ERROR: could not parse state"));
    gtk_widget_set_visible (GTK_WIDGET (elabel),
                            TRUE);
    return;
  }
  if ( (0 == strcmp (state,
                     "hint")) &&
       (NULL != hint) )
  {
    GNUNET_asprintf (&msg,
                     _ ("Hint (#%u): %s"),
                     (unsigned int) http_status,
                     dgettext ("taler-exchange",
                               hint));
  }
  else if ( (0 == strcmp (state,
                          "details")) &&
            (NULL != details) )
  {
    uint32_t code;
    const char *dhint = NULL;
    const char *detail = NULL;
    struct GNUNET_JSON_Specification ispec[] = {
      GNUNET_JSON_spec_uint32 ("code",
                               &code),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("hint",
                                 &dhint),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("detail",
                                 &detail),
        NULL),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (details,
                           ispec,
                           NULL, NULL))
    {
      GNUNET_break (0);
      json_dumpf (details,
                  stderr,
                  JSON_INDENT (2));
      msg = GNUNET_strdup (
        _ ("ERROR: failed to parse server JSON instructions"));
    }
    else
    {
      const char *ihint;
      const char *type = "Error";

      switch (code)
      {
      case TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED:
        hide = true;
        type = "Hint";
        break;
      default:
        break;
      }
      ihint = TALER_ErrorCode_get_hint (code);
      if ( (NULL != hint) &&
           ( (NULL == ihint) ||
             ('<' == ihint[0])) )
        ihint = dhint;           /* use server hint */
      ihint = dgettext ("taler-exchange",
                        ihint);
      if (NULL == detail)
      {
        if (NULL == ihint)
          GNUNET_asprintf (&msg,
                           "%s #%u",
                           type,
                           (unsigned int) code);
        else
          GNUNET_asprintf (&msg,
                           "%s #%u: %s",
                           type,
                           (unsigned int) code,
                           ihint);
      }
      else
      {
        if (NULL == ihint)
          GNUNET_asprintf (&msg,
                           "%s #%u (%s)",
                           type,
                           (unsigned int) code,
                           detail);
        else
          GNUNET_asprintf (&msg,
                           "%s #%u: %s (%s)",
                           type,
                           (unsigned int) code,
                           ihint,
                           detail);
      }
    }
  }
  else
  {
    GNUNET_asprintf (&msg,
                     "ERROR: state `%s` with HTTP Status %u",
                     state,
                     (unsigned int) http_status);
  }
  gtk_label_set_text (elabel,
                      msg);
  GNUNET_free (msg);
  if (hide)
    gtk_widget_set_visible (GTK_WIDGET (elabel),
                            FALSE);
  else
    gtk_widget_set_visible (GTK_WIDGET (elabel),
                            TRUE);
  GNUNET_JSON_parse_free (spec);
}


/**
 * Open dialog to allow user to answer security question.
 *
 * @param details details about the challenge
 * @return the dialog object, or NULL on error
 */
static GtkDialog *
diag_question (const json_t *details)
{
  GtkBuilder *builder;
  GtkDialog *ad;

  builder = ANASTASIS_GTK_get_new_builder (
    ANASTASIS_GTK_project_data (),
    "anastasis_gtk_challenge_question.ui",
    NULL);
  if (NULL == builder)
  {
    GNUNET_break (0);
    return NULL;
  }
  ad = GTK_DIALOG (gtk_builder_get_object (builder,
                                           "anastasis_gtk_c_question_dialog"));
  {
    GtkLabel *label;
    const char *instructions;

    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "security_question_label"));
    instructions = json_string_value (json_object_get (details,
                                                       "instructions"));
    gtk_label_set_text (label,
                        instructions);
  }
  {
    const char *uuid_str;

    uuid_str = json_string_value (json_object_get (details,
                                                   "uuid-display"));
    render_feedback (builder,
                     "anastasis_gtk_c_question_error_label",
                     uuid_str);
  }
  return ad;
}


/**
 * Create a dialog for the user to enter a PIN code.
 *
 * @param details details about the dialog to render
 * @return dialog object
 */
static GtkDialog *
diag_code (const json_t *details)
{
  GtkBuilder *builder;
  const char *instructions;
  const char *uuid_str;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("instructions",
                             &instructions),
    GNUNET_JSON_spec_string ("uuid-display",
                             &uuid_str),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (details,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    json_dumpf (details,
                stderr,
                JSON_INDENT (2));
    return NULL;
  }

  builder = ANASTASIS_GTK_get_new_builder (
    ANASTASIS_GTK_project_data (),
    "anastasis_gtk_challenge_code.ui",
    NULL);
  if (NULL == builder)
  {
    GNUNET_break (0);
    return NULL;
  }
  {
    GtkLabel *label;

    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "challenge_instructions_label"));
    gtk_label_set_text (label,
                        instructions);
    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "anastasis_gtk_c_challenge_label"));
    gtk_label_set_text (label,
                        uuid_str);
    render_feedback (builder,
                     "anastasis_gtk_c_code_error_label",
                     uuid_str);
  }
  {
    GtkDialog *ad;

    ad = GTK_DIALOG (gtk_builder_get_object (builder,
                                             "anastasis_gtk_c_code_dialog"));
    return ad;
  }
}


/**
 * Create a dialog for the user to enter an TOTP code.
 *
 * @param details details about the dialog to render
 * @return dialog object
 */
static GtkDialog *
diag_totp (const json_t *details)
{
  GtkBuilder *builder;
  const char *instructions;
  const char *uuid_str;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("instructions",
                             &instructions),
    GNUNET_JSON_spec_string ("uuid-display",
                             &uuid_str),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (details,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    json_dumpf (details,
                stderr,
                JSON_INDENT (2));
    return NULL;
  }

  builder = ANASTASIS_GTK_get_new_builder (
    ANASTASIS_GTK_project_data (),
    "anastasis_gtk_challenge_totp.ui",
    NULL);
  if (NULL == builder)
  {
    GNUNET_break (0);
    return NULL;
  }
  {
    GtkLabel *label;

    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "challenge_instructions_label"));
    gtk_label_set_text (label,
                        instructions);
  }
  render_feedback (builder,
                   "anastasis_gtk_c_totp_error_label",
                   uuid_str);
  {
    GtkDialog *ad;

    ad = GTK_DIALOG (gtk_builder_get_object (builder,
                                             "anastasis_gtk_c_totp_dialog"));
    return ad;
  }
}


/**
 * Create a dialog for the user to make an IBAN transfer.
 *
 * @param details details about the dialog to render
 * @return dialog object
 */
static GtkDialog *
diag_iban (const json_t *details)
{
  GtkBuilder *builder;
  struct TALER_Amount amount;
  const char *credit_iban;
  const char *business;
  const char *subject;
  const char *uuid_str;
  const char *debit_iban_hint;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("uuid",
                             &uuid_str),
    GNUNET_JSON_spec_string ("instructions",
                             &debit_iban_hint),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (details,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    json_dumpf (details,
                stderr,
                JSON_INDENT (2));
    return NULL;
  }
  {
    json_t *cf = json_object_get (AG_redux_state,
                                  "challenge_feedback");
    json_t *ci = json_object_get (cf,
                                  uuid_str);
    json_t *cd = json_object_get (ci,
                                  "details");
    struct GNUNET_JSON_Specification ispec[] = {
      TALER_JSON_spec_amount_any ("challenge_amount",
                                  &amount),
      GNUNET_JSON_spec_string ("credit_iban",
                               &credit_iban),
      GNUNET_JSON_spec_string ("business_name",
                               &business),
      GNUNET_JSON_spec_string ("wire_transfer_subject",
                               &subject),
      GNUNET_JSON_spec_end ()
    };

    if ( (NULL == cd) ||
         (GNUNET_OK !=
          GNUNET_JSON_parse (cd,
                             ispec,
                             NULL, NULL)) )
    {
      GNUNET_break (0);
      json_dumpf (AG_redux_state,
                  stderr,
                  JSON_INDENT (2));
      return NULL;
    }
  }

  builder = ANASTASIS_GTK_get_new_builder (
    ANASTASIS_GTK_project_data (),
    "anastasis_gtk_challenge_iban.ui",
    NULL);
  if (NULL == builder)
  {
    GNUNET_break (0);
    return NULL;
  }
  {
    GtkLabel *label;

    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "debit_account_label"));
    gtk_label_set_text (label,
                        debit_iban_hint);
    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "credit_account_label"));
    gtk_label_set_text (label,
                        credit_iban);
    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "provider_name_label"));
    gtk_label_set_text (label,
                        business);
    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "wire_transfer_subject_label"));
    gtk_label_set_text (label,
                        subject);
    label = GTK_LABEL (gtk_builder_get_object (builder,
                                               "amount_label"));
    gtk_label_set_text (label,
                        TALER_amount2s (&amount));
  }

  {
    GtkDialog *ad;

    ad = GTK_DIALOG (gtk_builder_get_object (builder,
                                             "anastasis_gtk_c_iban_dialog"));
    return ad;
  }
}


/**
 * The user wants to solve the selected challenge. Launch the
 * dialog to allow the user to enter the solution.
 */
static void
action_challenge_solving (void)
{
  struct
  {
    const char *type;
    GtkDialog *(*ctor)(const json_t *details);
  } type_map [] = {
    { .type = gettext_noop ("question"),
      .ctor = &diag_question },
    { .type = gettext_noop ("sms"),
      .ctor = &diag_code },
    { .type = gettext_noop ("post"),
      .ctor = &diag_code },
    { .type = gettext_noop ("email"),
      .ctor = &diag_code },
    { .type = gettext_noop ("iban"),
      .ctor = &diag_iban },
    { .type = gettext_noop ("totp"),
      .ctor = &diag_totp },
    { .type = NULL,
      .ctor = NULL }
  };
  const char *type;
  GtkDialog *diag;
  const char *uuid;
  const json_t *challenge;

  /* This state only puts a dialog on top of the frame the previous state
     rendered, so the progress list is the one thing that must be updated. */
  AG_progress_update ();
  uuid = json_string_value (json_object_get (AG_redux_state,
                                             "selected_challenge_uuid"));
  if (NULL == uuid)
  {
    GNUNET_break (0);
    return;
  }
  challenge = find_challenge_by_uuid (uuid);
  if (NULL == challenge)
  {
    GNUNET_break (0);
    return;
  }
  type = json_string_value (json_object_get (challenge,
                                             "type"));
  if (NULL == type)
  {
    GNUNET_break (0);
    return;
  }
  /* create dialog based on challenge type */
  diag = NULL;
  for (unsigned int i = 0; NULL != type_map[i].type; i++)
  {
    if (0 != strcmp (type_map[i].type,
                     type))
      continue;
    diag = type_map[i].ctor (challenge);
    break;
  }
  if (NULL == diag)
  {
    GNUNET_break (0);
    return;
  }
  /* show dialog */
  {
    GtkRoot *toplevel;
    GtkWidget *box;

    box = GTK_WIDGET (GCG_get_main_window_object (
                        "anastasis_gtk_policy_vbox"));
    toplevel = gtk_widget_get_root (box);
    gtk_window_set_transient_for (GTK_WINDOW (diag),
                                  GTK_WINDOW (toplevel));
    gtk_window_present (GTK_WINDOW (diag));
  }
}


/**
 * The recovery process was finished. Show the recovered secret to the
 * user.
 */
static void
action_recovery_finished (void)
{
  const char *mime = NULL;
  const char *text = NULL;
  const char *name = NULL;
  void *data = NULL;
  size_t data_size = 0;
  const json_t *cs;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("mime",
                               &mime),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("text",
                               &text),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_varsize ("value",
                                &data,
                                &data_size),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  GdkPixbuf *pb;
  GtkImage *img;

  AG_hide_all_frames ();
  name = json_string_value (json_object_get (json_object_get (AG_redux_state,
                                                              "recovery_information"),
                                             "secret_name"));

  cs = json_object_get (AG_redux_state,
                        "core_secret");
  GNUNET_assert (NULL != cs);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_JSON_parse (cs,
                                    spec,
                                    NULL, NULL));
  AG_hide ("anastasis_gtk_secret_copy_button");
  update_label ("anastasis_gtk_secret_value_label",
                text);
  if ( (NULL != name) &&
       (0 != strlen (name)) )
    update_label ("recovery_secret_name_value_label",
                  name);
  else
    update_label ("recovery_secret_name_value_label",
                  _ ("You did not name this secret"));
  if ( (0 == strncasecmp (mime,
                          "text/",
                          strlen ("text/"))) ||
       (0 == strncasecmp (mime,
                          "image/",
                          strlen ("image/"))) ||
       (NULL != text) )
  {
    /* images and text can be copied */
    AG_show ("anastasis_gtk_secret_copy_button");
  }
  pb = NULL;
  img = GTK_IMAGE (GCG_get_main_window_object (
                     "anastasis_gtk_secret_qr_image"));
  if (NULL != text)
  {
    pb = AG_setup_qrcode (GTK_WIDGET (img),
                          text,
                          strlen (text));
  }
  else
  {
    pb = AG_setup_qrcode (GTK_WIDGET (img),
                          data,
                          data_size);
  }
  if (NULL != pb)
  {
    AG_image_set_from_pixbuf (img,
                              pb);
    g_object_unref (pb);
  }
  else
  {
    AG_hide ("anastasis_gtk_secret_qr_image");
  }
  GNUNET_JSON_parse_free (spec);
  AG_hide ("anastasis_gtk_progress_vbox");
  AG_hide ("anastasis_gtk_recovery_progress_scrolled_window");
  AG_hide ("anastasis_gtk_backup_progress_scrolled_window");
  AG_show ("anastasis_gtk_completed_frame");
  AG_hide ("anastasis_gtk_backup_complete_box");
  AG_hide ("anastasis_gtk_success_backup_label");
  AG_show ("anastasis_gtk_success_recovery_box");
  AG_show ("anastasis_gtk_main_control_vbox");
  AG_hide ("anastasis_gtk_main_window_save_as_button");
  AG_show ("anastasis_gtk_restart_button");
  AG_show ("anastasis_gtk_main_window_quit_button");
  AG_hide ("anastasis_gtk_main_window_prev_button");
  AG_hide ("anastasis_gtk_main_window_forward_button");
}


/**
 * Function called with the results of #ANASTASIS_redux_action.
 *
 * @param cls closure
 * @param error_code Error code
 * @param response new state as result or config information of a provider
 */
void
AG_action_cb (void *cls,
              enum TALER_ErrorCode error_code,
              json_t *response)
{
  struct DispatchItem actions[] = {
    { .state = "CONTINENT_SELECTING",
      .action = &action_continent_selecting },
    { .state = "COUNTRY_SELECTING",
      .action = &action_country_selecting },
    { .state = "USER_ATTRIBUTES_COLLECTING",
      .action = &action_user_attributes_collecting },
    { .state = "AUTHENTICATIONS_EDITING",
      .action = &action_authentications_editing },
    { .state = "POLICIES_REVIEWING",
      .action = &action_policies_reviewing },
    { .state = "SECRET_EDITING",
      .action = &action_secret_editing },
    { .state = "TRUTHS_PAYING",
      .action = &action_truths_paying },
    { .state = "POLICIES_PAYING",
      .action = &action_policies_paying },
    { .state = "BACKUP_FINISHED",
      .action = &action_backup_finished },
    { .state = "SECRET_SELECTING",
      .action = &action_secret_selecting },
    { .state = "CHALLENGE_SELECTING",
      .action = &action_challenge_selecting },
    { .state = "CHALLENGE_PAYING",
      .action = &action_challenge_paying },
    { .state = "CHALLENGE_SOLVING",
      .action = &action_challenge_solving },
    { .state = "RECOVERY_FINISHED",
      .action = &action_recovery_finished },
    { .state = NULL,
      .action = NULL }
  };

  (void) cls;
  AG_ra = NULL;
  AG_thaw ();
#if DEBUG
  fprintf (stderr,
           "Action result %d\n",
           error_code);
  json_dumpf (response,
              stderr,
              JSON_INDENT (2));
  fprintf (stderr,
           "END action result %d\n",
           error_code);
#endif
  if (TALER_EC_NONE != error_code)
  {
    /* FIXME: maybe also render 'detail'
       if present in state?
       See for example ~ anastasis_api_backup_redux.c:3082 */
    AG_error ("Error #%d: %s\n",
              (int) error_code,
              TALER_ErrorCode_get_hint (error_code));
    if (AG_in_action)
    {
      GNUNET_break (0);
      return;
    }
  }
  if ( (NULL != json_object_get (response,
                                 "backup_state")) ||
       (NULL != json_object_get (response,
                                 "recovery_state")) )
  {
    json_decref (AG_redux_state);
    AG_stop_long_action ();
    AG_redux_state = json_incref (response);
  }
  if ( (TALER_EC_ANASTASIS_TRUTH_UNKNOWN == error_code) ||
       (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED == error_code) )
  {
    /* special case: do not remain in previous (challenge selected)
       state but revert to challenge selecting */
    GNUNET_assert (0 ==
                   json_object_set_new (AG_redux_state,
                                        "recovery_state",
                                        json_string ("CHALLENGE_SELECTING")));
  }
  if ( (TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED == error_code) ||
       (TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED == error_code) ||
       (TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED == error_code) )
  {
    /* special case: do not remain in previous (enter identity)
       state but advance to secret selecting */
    GNUNET_assert (0 ==
                   json_object_set_new (AG_redux_state,
                                        "recovery_state",
                                        json_string ("SECRET_SELECTING")));
  }
  AG_in_action = true;
  if (GNUNET_OK ==
      AG_dispatch (actions))
  {
    AG_in_action = false;
    return;
  }
  AG_in_action = false;
  AG_error ("Unhandled state `%s/%s'",
            json_string_value (json_object_get (AG_redux_state,
                                                "backup_state")),
            json_string_value (json_object_get (AG_redux_state,
                                                "recovery_state")));
  json_dumpf (AG_redux_state,
              stderr,
              JSON_INDENT (2));
  json_decref (AG_redux_state);
  AG_redux_state = NULL;
  AG_hide_all_frames ();
  AG_show ("anastasis_gtk_start_frame");
}
