/*
     This file is part of anastasis-gtk.
     Copyright (C) 2021 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_progress.c
 * @brief Functions dealing with the lists that show the user where we are in the process
 * @author Christian Grothoff
 */
#include <gnunet/gnunet_util_lib.h>
#include "anastasis_gtk_util.h"
#include "anastasis-gtk_helper.h"
#include "anastasis-gtk_rows.h"
#include <gdk-pixbuf/gdk-pixbuf.h>


/**
 * One step of one of the two processes we guide the user through.
 */
struct Step
{
  /**
   * Name of the step, as shown to the user.
   */
  const char *description;

  /**
   * Regular expression matching the reducer states this step covers.
   */
  const char *regex;

  /**
   * Long explanation of the step, shown as a tooltip.
   */
  const char *tooltip;
};


/**
 * The steps of making a backup.
 */
static const struct Step backup_steps[] = {
  { .description = N_ ("1. Location & currency"),
    .regex = "^(CONTINENT|COUNTRY)_SELECTING$",
    .tooltip = N_ (
      "Where you live will determine which personal information can be used to identify your backup.") },
  { .description = N_ ("2. Personal information"),
    .regex = "^USER_ATTRIBUTES_COLLECTING$",
    .tooltip = N_ (
      "Your personal information will not be shared with anyone. But you must provide the same information during recovery.\\") },
  { .description = N_ ("3. Authorization methods"),
    .regex = "^AUTHENTICATIONS_EDITING$",
    .tooltip = N_ (
      "Specify which methods could be used to authenticate you during recovery.") },
  { .description = N_ ("4. Recovery policies"),
    .regex = "^POLICIES_REVIEWING$",
    .tooltip = N_ (
      "These are possible ways how the secret could be recovered. Please review and possibly edit the policies.") },
  { .description = N_ ("5. Enter secret"),
    .regex = "^SECRET_EDITING$",
    .tooltip = N_ ("Please specify the secret to be backed up.") },
  { .description = N_ ("6. Payment (optional)"),
    .regex = "^.*_PAYING$",
    .tooltip = N_ (
      "Some Anastasis providers require payment for their service. Please pay.") },
  { .description = N_ ("7. Backup completed"),
    .regex = "BACKUP_FINISHED",
    .tooltip = N_ (
      "Your backup is complete. Please do test the recovery on occasion.") },
  { .description = NULL }
};


/**
 * The steps of a recovery.
 */
static const struct Step recovery_steps[] = {
  { .description = N_ ("1. Location & currency"),
    .regex = "^(CONTINENT|COUNTRY)_SELECTING$",
    .tooltip = N_ (
      "Where you live will determine which personal information can be used to identify your backup.") },
  { .description = N_ ("2. Personal information"),
    .regex = "^USER_ATTRIBUTES_COLLECTING$",
    .tooltip = N_ (
      "Your personal information will not be shared with anyone. But you must provide the same information during recovery.") },
  { .description = N_ ("3. Select secret"),
    .regex = "^SECRET_SELECTING$",
    .tooltip = N_ (
      "Please select which secret to recover. You may switch to a different version or provider.") },
  { .description = N_ ("4. Solve challenges"),
    /* paying for a challenge and answering it are both part of this step */
    .regex = "^CHALLENGE_(SELECTING|PAYING|SOLVING)$",
    .tooltip = N_ (
      "Please select an authentication challenge to pass to recover the secret.") },
  { .description = N_ ("5. Secret recovered"),
    .regex = "^RECOVERY_FINISHED$",
    .tooltip = N_ ("Your secret was successfully recovered.") },
  { .description = NULL }
};


/**
 * Fill the list called @a name with the given @a steps.
 *
 * @param name name of the model in the UI file
 * @param steps the steps to show
 */
static void
fill_progress (const char *name,
               const struct Step *steps)
{
  GListStore *list = AG_list (name);

  if (NULL == list)
    return;
  for (unsigned int i = 0; NULL != steps[i].description; i++)
    AG_list_append (list,
                    GNUNET_JSON_PACK (
                      GNUNET_JSON_pack_string ("description",
                                               _ (steps[i].description)),
                      GNUNET_JSON_pack_string ("regex",
                                               steps[i].regex),
                      GNUNET_JSON_pack_string ("tooltip",
                                               _ (steps[i].tooltip))));
}


void
AG_progress_setup (void)
{
  fill_progress ("anastasis_gtk_backup_progress_liststore",
                 backup_steps);
  fill_progress ("anastasis_gtk_recovery_progress_liststore",
                 recovery_steps);
}


/**
 * The progress lists only report where we are in the process; the user must
 * not be able to change the selection by clicking.  The gesture runs in the
 * capture phase, so claiming the sequence here keeps the click from ever
 * reaching the list.
 *
 * @param gesture the click gesture of the list
 * @param n_press number of presses
 * @param x horizontal position of the click
 * @param y vertical position of the click
 * @param user_data NULL
 */
void
anastasis_gtk_progress_treeview_pressed_cb (GtkGestureClick *gesture,
                                            gint n_press,
                                            gdouble x,
                                            gdouble y,
                                            gpointer user_data)
{
  (void) n_press;
  (void) x;
  (void) y;
  (void) user_data;
  gtk_gesture_set_state (GTK_GESTURE (gesture),
                         GTK_EVENT_SEQUENCE_CLAIMED);
}


/**
 * Function to validate an input by regular expression ("validation-regex").
 *
 * @param input text to validate
 * @param regexp regular expression to validate form
 * @return true if validation passed, else false
 */
static bool
validate_regex (const char *input,
                const char *regexp)
{
  regex_t regex;

  if (0 != regcomp (&regex,
                    regexp,
                    REG_EXTENDED))
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to compile regular expression `%s'.",
                regexp);
    return true;
  }
  /* check if input has correct form */
  if (0 != regexec (&regex,
                    input,
                    0,
                    NULL,
                    0))
  {
    regfree (&regex);
    return false;
  }
  regfree (&regex);
  return true;
}


void
AG_progress_update (void)
{
  const char *sel_name;
  GListModel *steps;
  const char *state;
  guint n;

  state = json_string_value (json_object_get (AG_redux_state,
                                              "backup_state"));
  if (NULL == state)
  {
    state = json_string_value (json_object_get (AG_redux_state,
                                                "recovery_state"));
    if (NULL == state)
    {
      GNUNET_break (0);
      return;
    }
    sel_name = "anastasis_gtk_recovery_progress_tree_selection";
    steps = G_LIST_MODEL (AG_list (
                            "anastasis_gtk_recovery_progress_liststore"));
  }
  else
  {
    sel_name = "anastasis_gtk_backup_progress_tree_selection";
    steps = G_LIST_MODEL (AG_list (
                            "anastasis_gtk_backup_progress_liststore"));
  }
  if (NULL == steps)
    return;
  n = g_list_model_get_n_items (steps);
  for (guint i = 0; i < n; i++)
  {
    AGRow *row = g_list_model_get_item (steps,
                                        i);
    bool match;

    match = validate_regex (state,
                            AG_row_string (row,
                                           "regex"));
    g_object_unref (row);
    if (match)
    {
      /* The selection is what highlights the step the user is at.  Nothing
         else ever moves it: the lists are not focusable and they swallow
         clicks. */
      AG_select_row (sel_name,
                     i);
      return;
    }
  }
  /* the reducer is in a state none of the steps above cover */
  GNUNET_break (0);
  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "No progress step matches state `%s'\n",
              state);
}
