/*
     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_backup.c
 * @brief Main function of anastasis-gtk
 * @author Christian Grothoff
 * @author Dennis Neufeld
 */
#include <gnunet/gnunet_util_lib.h>
#include "anastasis_gtk_util.h"
#include "anastasis-gtk_action.h"
#include "anastasis-gtk_helper.h"
#include "anastasis-gtk_handle-main-window-forward-clicked.h"
#include "anastasis-gtk_rows.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <jansson.h>
#include <microhttpd.h>


/**
 * Context for menu callbacks.
 */
struct MenuContext
{
  /**
   * Base URL of the selected provider.
   */
  char *url;

};


/**
 * Free a `struct MenuContext`.  Called when the action group holding it goes
 * away, which happens on the next right-click at the latest.
 *
 * @param cls the `struct MenuContext` to free
 */
static void
free_menu_context (gpointer cls)
{
  struct MenuContext *ctx = cls;

  GNUNET_free (ctx->url);
  GNUNET_free (ctx);
}


/**
 * Open @a url in a browser.
 *
 * @param url the URL to open
 */
static void
xdg_open (const char *url)
{
  pid_t chld;
  int status;

  chld = fork ();
  if (-1 == chld)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "fork");
    return;
  }
  if (0 == chld)
  {
    pid_t c2;

    c2 = fork ();
    if (-1 == c2)
      _exit (EXIT_FAILURE);
    if (0 != c2)
      _exit (EXIT_SUCCESS);
    execlp ("xdg-open",
            "xdg-open",
            url,
            NULL);
    execlp ("open",
            "open",
            url,
            NULL);
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                              "exec",
                              "open");
    _exit (EXIT_FAILURE);
  }
  waitpid (chld, &status, 0);
}


/**
 * Return the provider the user right-clicked on.
 *
 * @param view the list the context menu belongs to
 * @return the context, NULL if there is none
 */
static struct MenuContext *
current_context (gpointer view)
{
  return g_object_get_data (G_OBJECT (view),
                            "anastasis-menu-context");
}


/**
 * The user selected the 'view terms of service' menu item.
 *
 * @param action the activated action
 * @param parameter unused
 * @param user_data the tree view holding the `struct MenuContext`
 */
static void
view_terms_of_service (GSimpleAction *action,
                       GVariant *parameter,
                       gpointer user_data)
{
  struct MenuContext *ctx = current_context (user_data);
  char *tos;

  (void) action;
  (void) parameter;
  if (NULL == ctx)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_asprintf (&tos,
                   "%sterms",
                   ctx->url);
  xdg_open (tos);
  GNUNET_free (tos);
}


/**
 * The user selected the 'view privacy policy' menu item.
 *
 * @param action the activated action
 * @param parameter unused
 * @param user_data the tree view holding the `struct MenuContext`
 */
static void
view_privacy_policy (GSimpleAction *action,
                     GVariant *parameter,
                     gpointer user_data)
{
  struct MenuContext *ctx = current_context (user_data);
  char *pp;

  (void) action;
  (void) parameter;
  if (NULL == ctx)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_asprintf (&pp,
                   "%sprivacy",
                   ctx->url);
  xdg_open (pp);
  GNUNET_free (pp);
}


/**
 * Return the context menu of @a tv, creating it on first use.  A single
 * popover is reused for the lifetime of the list.
 *
 * @param tv the list
 * @return the popover to show
 */
static GtkPopover *
get_popover (GtkWidget *tv)
{
  static const GActionEntry entries[] = {
    { .name = "privacy",
      .activate = &view_privacy_policy },
    { .name = "terms",
      .activate = &view_terms_of_service }
  };
  GtkWidget *popover;

  popover = g_object_get_data (G_OBJECT (tv),
                               "anastasis-context-menu");
  if (NULL != popover)
    return GTK_POPOVER (popover);
  {
    GMenu *menu = g_menu_new ();

    g_menu_append (menu,
                   _ ("View _privacy policy..."),
                   "provider.privacy");
    g_menu_append (menu,
                   _ ("View _terms of service..."),
                   "provider.terms");
    popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu));
    g_object_unref (menu);
  }
  gtk_popover_set_has_arrow (GTK_POPOVER (popover),
                             FALSE);
  gtk_widget_set_halign (popover,
                         GTK_ALIGN_START);
  gtk_widget_set_parent (popover,
                         tv);
  g_object_set_data_full (G_OBJECT (tv),
                          "anastasis-context-menu",
                          popover,
                          (GDestroyNotify) & gtk_widget_unparent);
  {
    GSimpleActionGroup *actions;

    actions = g_simple_action_group_new ();
    g_action_map_add_action_entries (G_ACTION_MAP (actions),
                                     entries,
                                     G_N_ELEMENTS (entries),
                                     tv);
    gtk_widget_insert_action_group (tv,
                                    "provider",
                                    G_ACTION_GROUP (actions));
    g_object_unref (actions);
  }
  return GTK_POPOVER (popover);
}


/**
 * User right-clicked on the list: show the context menu that lets them
 * view the provider's privacy policy or terms of service.
 *
 * @param gesture the click gesture, restricted to button 3 by the UI file
 * @param n_press number of presses
 * @param x horizontal position of the click, in widget coordinates
 * @param y vertical position of the click, in widget coordinates
 * @param user_data the builder
 */
void
provider_tree_view_pressed_cb (GtkGestureClick *gesture,
                               gint n_press,
                               gdouble x,
                               gdouble y,
                               gpointer user_data)
{
  GtkWidget *widget
    = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
  struct MenuContext *ctx;
  AGRow *row;

  (void) n_press;
  (void) user_data;
  row = AG_row_at (widget,
                   y);
  if (NULL == row)
    return; /* the user clicked past the last provider */
  ctx = GNUNET_new (struct MenuContext);
  ctx->url = GNUNET_strdup (AG_row_string (row,
                                           "url"));
  /* the list owns the context of the row currently pointed at; the
     previous one is released here */
  g_object_set_data_full (G_OBJECT (widget),
                          "anastasis-menu-context",
                          ctx,
                          &free_menu_context);
  {
    GtkPopover *popover = get_popover (widget);
    const GdkRectangle at = {
      .x = (int) x,
      .y = (int) y,
      .width = 1,
      .height = 1
    };

    gtk_popover_set_pointing_to (popover,
                                 &at);
    gtk_popover_popup (popover);
  }
}


/**
 * The user clicked the "add" button to add a new provider to the list.
 *
 * @param button the button object
 * @param user_data the builder
 */
void
url_add_button_clicked_cb (GtkButton *button,
                           gpointer user_data)
{
  GtkBuilder *builder = GTK_BUILDER (user_data);
  GListStore *ls;
  GtkEntry *entry;
  const char *url;

  ls = AG_list_of (builder,
                   "provider_liststore");
  GNUNET_assert (NULL != ls);
  entry = GTK_ENTRY (gtk_builder_get_object (builder,
                                             "url_entry"));
  url = gtk_editable_get_text (GTK_EDITABLE (entry));
  AG_list_append (ls,
                  GNUNET_JSON_PACK (
                    GNUNET_JSON_pack_string ("url",
                                             url),
                    GNUNET_JSON_pack_string ("status",
                                             _ ("new")),
                    GNUNET_JSON_pack_string ("color",
                                             "blue"),
                    GNUNET_JSON_pack_bool ("enabled",
                                           true),
                    GNUNET_JSON_pack_bool ("sensitive",
                                           false),
                    GNUNET_JSON_pack_bool ("not_sensitive",
                                           true),
                    GNUNET_JSON_pack_string ("name",
                                             url)));
  gtk_editable_set_text (GTK_EDITABLE (entry),
                         "");
}


/**
 * The user changed the URL of a provider to be possibly added.
 * Validate the syntax and if it is a valid URL, enable the button.
 *
 * @param entry the edited widget
 * @param user_data the dialog builder
 */
void
url_entry_changed_cb (GtkEntry *entry,
                      gpointer user_data)
{
  GtkBuilder *builder = GTK_BUILDER (user_data);
  GtkWidget *button;
  const char *url;

  button = GTK_WIDGET (gtk_builder_get_object (builder,
                                               "add_button"));
  url = gtk_editable_get_text (GTK_EDITABLE (entry));
  gtk_widget_set_sensitive (button,
                            (0 == strncasecmp (url,
                                               "http://",
                                               strlen ("http://"))) ||
                            (0 == strncasecmp (url,
                                               "https://",
                                               strlen ("https://"))));
}


/**
 * Function called from the edit-provider dialog upon completion.
 *
 * @param dialog the pseudonym selection dialog
 * @param response_id response code from the dialog
 * @param user_data the builder of the dialog
 */
void
edit_provider_dialog_response_cb (GtkDialog *dialog,
                                  gint response_id,
                                  gpointer user_data)
{
  GtkBuilder *builder = GTK_BUILDER (user_data);
  GListModel *lm;
  json_t *args;
  guint n;

  if (GTK_RESPONSE_APPLY != response_id)
  {
    gtk_window_destroy (GTK_WINDOW (dialog));
    g_object_unref (G_OBJECT (builder));
    return;
  }
  lm = G_LIST_MODEL (AG_list_of (builder,
                                 "provider_liststore"));
  if (NULL == lm)
  {
    GNUNET_break (0);
    return;
  }
  args = json_object ();
  n = g_list_model_get_n_items (lm);
  for (guint i = 0; i < n; i++)
  {
    AGRow *row = g_list_model_get_item (lm,
                                        i);

    GNUNET_assert (0 ==
                   json_object_set_new (
                     args,
                     AG_row_string (row,
                                    "url"),
                     GNUNET_JSON_PACK (
                       GNUNET_JSON_pack_string ("status",
                                                AG_row_bool (row,
                                                             "enabled")
                                                ? "not-contacted"
                                                : "disabled"))));
    g_object_unref (row);
  }
  gtk_window_destroy (GTK_WINDOW (dialog));
  g_object_unref (G_OBJECT (builder));
  AG_freeze ();
  AG_ra = ANASTASIS_redux_action (AG_redux_state,
                                  "add_provider",
                                  args,
                                  &AG_action_cb,
                                  NULL);
  json_decref (args);
}


/**
 * Callback invoked if the the "Edit"-provider list button is clicked.
 *
 * @param object
 * @param user_data unused
 */
void
anastasis_gtk_edit_provider_list_clicked_cb (GtkButton *object,
                                             gpointer user_data)
{
  static const struct AG_SortSpec sorts[] = {
    { .column = "enabled_column",
      .field = "not_sensitive",
      .numeric = true },
    { .column = "status_column",
      .field = "not_sensitive",
      .numeric = true },
    { .column = NULL }
  };
  GtkWidget *ad;
  GtkBuilder *builder;
  GListStore *ls;
  json_t *providers;

  builder = ANASTASIS_GTK_get_new_builder (
    ANASTASIS_GTK_project_data (),
    "anastasis_gtk_edit_providers.ui",
    NULL);
  if (NULL == builder)
  {
    GNUNET_break (0);
    return;
  }
  AG_list_make_sortable (builder,
                         "provider_tree_view",
                         "provider_sorted",
                         sorts);
  ls = AG_list_of (builder,
                   "provider_liststore");
  providers = json_object_get (AG_redux_state,
                               "authentication_providers");
  {
    const char *url;
    const json_t *provider;
    json_object_foreach (providers, url, provider)
    {
      uint32_t http_code = 0;
      uint32_t ec = TALER_EC_NONE;
      struct TALER_Amount ll;
      const char *status;
      const char *name = NULL;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_string ("status",
                                 &status),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_uint32 ("http_status",
                                   &http_code),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_string ("business_name",
                                   &name),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          TALER_JSON_spec_amount_any ("liability_limit",
                                      &ll),
          NULL),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_uint32 ("error_code",
                                   &ec),
          NULL),
        GNUNET_JSON_spec_end ()
      };
      char *status_str;
      const char *color;
      bool sensitive = false;
      const char *ll_s = NULL;

      memset (&ll,
              0,
              sizeof (ll));
      if (GNUNET_OK !=
          GNUNET_JSON_parse (provider,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break (0);
        json_dumpf (provider,
                    stderr,
                    JSON_INDENT (2));
        continue;
      }
      if ( (MHD_HTTP_OK == http_code) &&
           (0 != strcmp (status,
                         "disabled")) )
      {
        status_str = GNUNET_strdup (_ ("available"));
        color = "green";
        sensitive = true;
        if (GNUNET_OK ==
            TALER_amount_is_valid (&ll))
          ll_s = TALER_amount2s (&ll);
        else
          GNUNET_break (0);
      }
      else if ( (0 == http_code) &&
                (0 != strcmp (status,
                              "disabled")) )
      {
        GNUNET_asprintf (&status_str,
                         _ ("Network failure: %s (#%u)"),
                         TALER_ErrorCode_get_hint (ec),
                         (unsigned int) ec);
        color = "red";
      }
      else if (0 == strcmp (status,
                            "disabled"))
      {
        GNUNET_asprintf (&status_str,
                         _ ("disabled"));
        color = "blue";
        sensitive = true;
      }
      else
      {
        GNUNET_asprintf (&status_str,
                         _ ("HTTP %s (%u): %s (#%u)"),
                         MHD_get_reason_phrase_for (http_code),
                         (unsigned int) http_code,
                         TALER_ErrorCode_get_hint (ec),
                         (unsigned int) ec);
        color = "red";
      }
      if (NULL == name)
        name = url;
      AG_list_append (
        ls,
        GNUNET_JSON_PACK (
          GNUNET_JSON_pack_string ("url",
                                   url),
          GNUNET_JSON_pack_string ("status",
                                   status_str),
          GNUNET_JSON_pack_string ("color",
                                   color),
          GNUNET_JSON_pack_allow_null (
            GNUNET_JSON_pack_string ("liability",
                                     ll_s)),
          GNUNET_JSON_pack_bool ("enabled",
                                 0 != strcmp (status,
                                              "disabled")),
          GNUNET_JSON_pack_bool ("sensitive",
                                 sensitive),
          GNUNET_JSON_pack_bool ("not_sensitive",
                                 ! sensitive),
          GNUNET_JSON_pack_string ("name",
                                   name)));
      GNUNET_free (status_str);
    }
  }
  ad = GTK_WIDGET (gtk_builder_get_object (builder,
                                           "edit_provider_dialog"));
  {
    GtkRoot *toplevel;

    toplevel = gtk_widget_get_root (GTK_WIDGET (object));
    gtk_window_set_transient_for (GTK_WINDOW (ad),
                                  GTK_WINDOW (toplevel));
    gtk_window_present (GTK_WINDOW (ad));
  }
}
