/*
     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 <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;

};


/**
 * An item was selected from the context menu; destroy the menu shell.
 *
 * @param menushell menu to destroy
 * @param user_data the 'struct MenuContext' of the menu
 */
static void
context_popup_selection_done (GtkMenuShell *menushell,
                              gpointer user_data)
{
  struct MenuContext *ctx = user_data;

  gtk_widget_destroy (GTK_WIDGET (menushell));
  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);
}


/**
 * The user selected the 'view pp' menu.
 *
 * @param menuitem the selected menu
 * @param user_data a `struct MenuContext`
 */
static void
view_terms_of_service (GtkMenuItem *menuitem,
                       gpointer user_data)
{
  struct MenuContext *ctx = user_data;
  char *tos;

  GNUNET_asprintf (&tos,
                   "%sterms",
                   ctx->url);
  xdg_open (tos);
  GNUNET_free (tos);
}


/**
 * The user selected the 'view tos' menu.
 *
 * @param menuitem the selected menu
 * @param user_data a `struct MenuContext`
 */
static void
view_privacy_policy (GtkMenuItem *menuitem,
                     gpointer user_data)
{
  struct MenuContext *ctx = user_data;
  char *pp;

  GNUNET_asprintf (&pp,
                   "%sprivacy",
                   ctx->url);
  xdg_open (pp);
  GNUNET_free (pp);
}


void
provider_toggle_callback (GtkCellRendererToggle *cell,
                          gchar *path_str,
                          gpointer user_data)
{
  GtkBuilder *builder = GTK_BUILDER (user_data);
  GtkTreeIter iter;
  GtkTreePath *path;
  gboolean enabled;
  GtkTreeModel *tm;

  tm = GTK_TREE_MODEL (gtk_builder_get_object (builder,
                                               "provider_liststore"));
  if (NULL == tm)
  {
    GNUNET_break (0);
    return;
  }
  path = gtk_tree_path_new_from_string (path_str);
  if (! gtk_tree_model_get_iter (tm,
                                 &iter,
                                 path))
  {
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (tm,
                      &iter,
                      AG_PMC_PROVIDER_ENABLED, &enabled,
                      -1);
  enabled = ! enabled;
  gtk_list_store_set (GTK_LIST_STORE (tm),
                      &iter,
                      AG_PMC_PROVIDER_ENABLED, enabled,
                      -1);
  gtk_tree_path_free (path);
}


/**
 * User clicked on the tree view. If it was a right-click, show
 * context menu to allow user to view PP or TOS.
 *
 * @param widget the tree view
 * @param event the event
 * @param user_data the builder
 */
gboolean
provider_tree_view_button_press_event_cb (GtkWidget *widget,
                                          GdkEvent *event,
                                          gpointer user_data)
{
  GtkBuilder *builder = GTK_BUILDER (user_data);
  GdkEventButton *event_button = (GdkEventButton *) event;
  GtkTreeView *tv;
  GtkTreeModel *tm;
  GtkTreePath *path;
  GtkTreeIter iter;
  struct MenuContext *ctx;
  GtkMenu *menu;

  if ((GDK_BUTTON_PRESS != event->type) ||
      (3 != event_button->button))
    return FALSE; /* not a right-click */
  tm = GTK_TREE_MODEL (gtk_builder_get_object (builder,
                                               "provider_liststore"));
  if (NULL == tm)
  {
    GNUNET_break (0);
    return FALSE;
  }
  tv = GTK_TREE_VIEW (gtk_builder_get_object (builder,
                                              "provider_tree_view"));
  if (! gtk_tree_view_get_path_at_pos (tv,
                                       event_button->x,
                                       event_button->y,
                                       &path,
                                       NULL,
                                       NULL,
                                       NULL))
  {
    /* nothing selected */
    return FALSE;
  }
  if (! gtk_tree_model_get_iter (tm,
                                 &iter,
                                 path))
  {
    /* not sure how we got a path but no iter... */
    GNUNET_break (0);
    return FALSE;
  }
  gtk_tree_path_free (path);
  ctx = GNUNET_new (struct MenuContext);
  gtk_tree_model_get (tm,
                      &iter,
                      AG_PMC_PROVIDER_URL, &ctx->url,
                      -1);
  menu = GTK_MENU (gtk_menu_new ());
  {
    GtkWidget *child;

    child = gtk_menu_item_new_with_label (_ ("View _privacy policy..."));
    g_signal_connect (child,
                      "activate",
                      G_CALLBACK (&view_privacy_policy),
                      ctx);
    gtk_label_set_use_underline (GTK_LABEL (
                                   gtk_bin_get_child (GTK_BIN (child))),
                                 TRUE);
    gtk_widget_show (child);
    gtk_menu_shell_append (GTK_MENU_SHELL (menu),
                           child);
  }
  {
    GtkWidget *child;

    child = gtk_menu_item_new_with_label (_ ("View _terms of service..."));
    g_signal_connect (child,
                      "activate",
                      G_CALLBACK (&view_terms_of_service),
                      ctx);
    gtk_label_set_use_underline (GTK_LABEL (
                                   gtk_bin_get_child (GTK_BIN (child))),
                                 TRUE);
    gtk_widget_show (child);
    gtk_menu_shell_append (GTK_MENU_SHELL (menu),
                           child);
  }

  g_signal_connect (menu,
                    "selection-done",
                    G_CALLBACK (&context_popup_selection_done),
                    ctx);

  gtk_menu_popup_at_pointer (menu,
                             event);

  return FALSE;
}


/**
 * 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);
  GtkListStore *ls;
  GtkEntry *entry;
  const char *url;

  ls = GTK_LIST_STORE (gtk_builder_get_object (builder,
                                               "provider_liststore"));
  GNUNET_assert (NULL != ls);
  entry = GTK_ENTRY (gtk_builder_get_object (builder,
                                             "url_entry"));
  url = gtk_entry_get_text (entry);
  gtk_list_store_insert_with_values (ls,
                                     NULL,
                                     -1,
                                     AG_PMC_PROVIDER_URL, url,
                                     AG_PMC_PROVIDER_STATUS, _ ("new"),
                                     AG_PMC_PROVIDER_STATUS_COLOR, "blue",
                                     AG_PMC_PROVIDER_ENABLED, true,
                                     AG_PMC_PROVIDER_SENSITIVE, false,
                                     AG_PMC_PROVIDER_NOT_SENSITIVE, true,
                                     AG_PMC_PROVIDER_NAME, url,
                                     -1);
  gtk_entry_set_text (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_entry_get_text (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);
  GtkTreeModel *tm;
  GtkTreeIter iter;
  json_t *args;

  if (GTK_RESPONSE_APPLY != response_id)
  {
    gtk_widget_destroy (GTK_WIDGET (dialog));
    g_object_unref (G_OBJECT (builder));
    return;
  }
  tm = GTK_TREE_MODEL (gtk_builder_get_object (builder,
                                               "provider_liststore"));
  if (NULL == tm)
  {
    GNUNET_break (0);
    return;
  }
  args = json_object ();
  if (gtk_tree_model_get_iter_first (tm,
                                     &iter))
    do {
      gchar *url;
      gboolean enabled;

      gtk_tree_model_get (tm,
                          &iter,
                          AG_PMC_PROVIDER_URL, &url,
                          AG_PMC_PROVIDER_ENABLED, &enabled,
                          -1);
      GNUNET_assert (0 ==
                     json_object_set_new (
                       args,
                       url,
                       GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_string ("status",
                                                  enabled
                                                  ? "not-contacted"
                                                  : "disabled"))));
      g_free (url);
    }
    while (gtk_tree_model_iter_next (tm,
                                     &iter));
  gtk_widget_destroy (GTK_WIDGET (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)
{
  GtkWidget *ad;
  GtkBuilder *builder;
  GtkListStore *ls;
  json_t *providers;

  builder = ANASTASIS_GTK_get_new_builder (
    ANASTASIS_GTK_project_data (),
    "anastasis_gtk_edit_providers.glade",
    NULL);
  if (NULL == builder)
  {
    GNUNET_break (0);
    return;
  }
  ls = GTK_LIST_STORE (gtk_builder_get_object (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;
      gtk_list_store_insert_with_values (
        ls,
        NULL,
        -1,
        AG_PMC_PROVIDER_URL, url,
        AG_PMC_PROVIDER_STATUS, status_str,
        AG_PMC_PROVIDER_STATUS_COLOR, color,
        AG_PMC_PROVIDER_LIABILITY_LIMIT, ll_s,
        AG_PMC_PROVIDER_ENABLED, (0 != strcmp (status,
                                               "disabled")),
        AG_PMC_PROVIDER_SENSITIVE, sensitive,
        AG_PMC_PROVIDER_NOT_SENSITIVE, ! sensitive,
        AG_PMC_PROVIDER_NAME, name,
        -1);
      GNUNET_free (status_str);
    }
  }
  ad = GTK_WIDGET (gtk_builder_get_object (builder,
                                           "edit_provider_dialog"));
  {
    GtkWidget *toplevel;

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