/*
     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_handle-secret-buttons.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_handle-expiration-change.h"
#include "anastasis-gtk_helper.h"
#include <jansson.h>
#include <magic.h>


/**
 * Global handle to MAGIC data.
 */
static magic_t magic;


/**
 * Function called once the user picked the file holding the secret.
 *
 * @param source the file dialog
 * @param res the result of the user's choice
 * @param user_data unused
 */
static void
open_secret_ready_cb (GObject *source,
                      GAsyncResult *res,
                      gpointer user_data)
{
  GFile *file;
  char *filename;
  const char *fn;
  size_t data_size;
  void *data;
  const char *mime;
  GtkEntry *entry;
  const char *name;

  (void) user_data;
  file = gtk_file_dialog_open_finish (GTK_FILE_DIALOG (source),
                                     res,
                                     NULL);
  if (NULL == file)
    return; /* user aborted */
  filename = ANASTASIS_GTK_file_get_path_utf8 (file);
  g_object_unref (file);
  if (NULL == filename)
    return;
  fn = strrchr (filename,
                '/');
  if (NULL == fn)
    fn = filename;
  else
    fn++; /* skip '/' itself */
  {
    struct GNUNET_DISK_FileHandle *fh;
    off_t size;
    enum GNUNET_GenericReturnValue ret;

    fh = GNUNET_DISK_file_open (filename,
                                GNUNET_DISK_OPEN_READ,
                                GNUNET_DISK_PERM_NONE);
    if (NULL == fh)
    {
      AG_error ("Failed to open file `%s': %s",
                filename,
                strerror (errno));
      GNUNET_free (filename);
      return;
    }
    ret = GNUNET_DISK_file_handle_size (fh,
                                        &size);
    if (GNUNET_OK != ret)
    {
      AG_error ("Failed to obtain file size `%s': %s",
                filename,
                strerror (errno));
      GNUNET_free (filename);
      GNUNET_DISK_file_close (fh);
      return;
    }
    data_size = (size_t) size;
    data = GNUNET_malloc_large (data_size);
    if (NULL == data)
    {
      AG_error ("Failed to allocate memory for file `%s': %s",
                filename,
                strerror (errno));
      GNUNET_free (filename);
      GNUNET_DISK_file_close (fh);
      return;
    }
    if (size !=
        GNUNET_DISK_file_read (fh,
                               data,
                               data_size))
    {
      AG_error ("Failed read file `%s': %s",
                filename,
                strerror (errno));
      GNUNET_free (data);
      GNUNET_free (filename);
      GNUNET_DISK_file_close (fh);
      return;
    }
    GNUNET_DISK_file_close (fh);
  }
  entry = GTK_ENTRY (GCG_get_main_window_object (
                       "anastasis_gtk_secret_name_entry"));
  name = gtk_editable_get_text (GTK_EDITABLE (entry));
  mime = magic_buffer (magic,
                       data,
                       data_size);
  {
    json_t *arguments;
    struct GNUNET_TIME_Timestamp expiration;

    expiration = AG_get_desired_expiration ();
    if (GNUNET_TIME_absolute_is_zero (expiration.abs_time))
    {
      GNUNET_free (data);
      GNUNET_free (filename);
      return; /* failured */
    }
    arguments = json_pack ("{s:s?,s:{s:o,s:s,s:s?},s:o}",
                           "name",
                           name,
                           "secret",
                           "value",
                           GNUNET_JSON_from_data (data,
                                                  data_size),
                           "filename",
                           fn,
                           "mime",
                           mime,
                           "expiration",
                           GNUNET_JSON_from_timestamp (expiration));
    GNUNET_free (filename);
    GNUNET_free (data);
    GNUNET_assert (NULL != arguments);
    AG_freeze ();
    AG_ra = ANASTASIS_redux_action (AG_redux_state,
                                    "enter_secret",
                                    arguments,
                                    &AG_action_cb,
                                    NULL);
    json_decref (arguments);
  }
}


/**
 * User clicked the "open" button in the dialog where the secret is entered.
 *
 * @param button the button
 * @param user_data unused
 */
void
anastasis_gtk_enter_secret_open_button_clicked_cb (GtkButton *button,
                                                   gpointer user_data)
{
  GtkFileDialog *dialog;

  (void) user_data;
  dialog = gtk_file_dialog_new ();
  gtk_file_dialog_set_title (dialog,
                             _ ("Open file with secret to back up"));
  gtk_file_dialog_open (dialog,
                        GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))),
                        NULL,
                        &open_secret_ready_cb,
                        NULL);
  g_object_unref (dialog);
}


/**
 * Function called once the user picked the file to save the secret in.
 *
 * @param source the file dialog
 * @param res the result of the user's choice
 * @param user_data unused
 */
static void
save_secret_ready_cb (GObject *source,
                      GAsyncResult *res,
                      gpointer user_data)
{
  GFile *file;
  char *filename;
  size_t data_len = 0;
  const char *text = NULL;
  void *data = NULL;
  json_t *cs;
  struct GNUNET_JSON_Specification cspec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("text",
                               &text),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_varsize ("value",
                                &data,
                                &data_len),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  (void) user_data;
  file = gtk_file_dialog_save_finish (GTK_FILE_DIALOG (source),
                                      res,
                                      NULL);
  if (NULL == file)
    return; /* user aborted */
  filename = ANASTASIS_GTK_file_get_path_utf8 (file);
  g_object_unref (file);
  if (NULL == filename)
    return;
  cs = json_object_get (AG_redux_state,
                        "core_secret");
  GNUNET_assert (NULL != cs);
  if (GNUNET_OK !=
      GNUNET_JSON_parse (cs,
                         cspec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    return;
  }
  {
    enum GNUNET_GenericReturnValue ret;

    ret = GNUNET_DISK_fn_write (filename,
                                (NULL == data)
                                ? text
                                : data,
                                (NULL == data)
                                ? strlen (text)
                                : data_len,
                                GNUNET_DISK_PERM_USER_READ);
    switch (ret)
    {
    case GNUNET_OK:
      break;
    case GNUNET_NO:
      AG_error ("File `%s' exists",
                filename);
      break;
    case GNUNET_SYSERR:
      AG_error ("Writing to file `%s' failed: %s",
                filename,
                strerror (errno));
      break;
    }
  }
  GNUNET_JSON_parse_free (cspec);
  GNUNET_free (filename);
}


/**
 * User clicked the "save as" button in the dialog with the recovered secret.
 *
 * @param button the button
 * @param user_data unused
 */
void
anastasis_gtk_secret_save_as_button_clicked_cb (GtkButton *button,
                                                gpointer user_data)
{
  static const struct
  {
    const char *mime;
    const char *fn;
  } mime_map [] = {
    { .mime = "text/plain",
      .fn = "untitled.txt" },
    { .mime = "text/html",
      .fn = "untitled.html" },
    { .mime = "text/xml",
      .fn = "untitled.xml" },
    { .mime = "text/csv",
      .fn = "untitled.csv" },
    { .mime = "image/jpeg",
      .fn = "untitled.jpeg" },
    { .mime = "image/png",
      .fn = "untitled.png" },
    { .mime = "application/pgp-keys",
      .fn = "untitled.pgp" },
    { .mime = "application/json",
      .fn = "untitled.json" },
    { .mime = "application/taler-wallet-secret",
      .fn = "untitled.tws" },
    { .mime = "application/taler-wallet",
      .fn = "untitled.twd" },
    { .mime = NULL,
      .fn = NULL }
  };

  GtkFileDialog *dialog;
  const char *mime = NULL;
  const char *fn = NULL;
  json_t *cs;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("filename",
                               &fn),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("mime",
                               &mime),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  (void) user_data;
  cs = json_object_get (AG_redux_state,
                        "core_secret");
  GNUNET_assert (NULL != cs);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_JSON_parse (cs,
                                    spec,
                                    NULL, NULL));
  if ( (NULL == fn) &&
       (NULL != mime) )
  {
    fn = "untitled.secret";
    for (unsigned int i = 0; NULL != mime_map[i].mime; i++)
    {
      if (0 != strcmp (mime_map[i].mime,
                       mime))
        continue;
      fn = mime_map[i].fn;
      break;
    }
  }
  dialog = gtk_file_dialog_new ();
  gtk_file_dialog_set_title (dialog,
                             _ ("Save recovered secret"));
  {
    GtkFileFilter *filter;
    GListStore *filters;

    filter = gtk_file_filter_new ();
    gtk_file_filter_set_name (filter,
                              _ ("Secrets"));
    gtk_file_filter_add_pattern (filter,
                                 "*.secret");
    filters = g_list_store_new (GTK_TYPE_FILE_FILTER);
    g_list_store_append (filters,
                         filter);
    g_object_unref (filter);
    gtk_file_dialog_set_filters (dialog,
                                 G_LIST_MODEL (filters));
    g_object_unref (filters);
  }
  if (NULL != fn)
    gtk_file_dialog_set_initial_name (dialog,
                                      fn);
  gtk_file_dialog_save (dialog,
                        GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))),
                        NULL,
                        &save_secret_ready_cb,
                        NULL);
  g_object_unref (dialog);
}


/**
 * User clicked the "copy" button in the dialog with the recovered secret.
 *
 * @param button the button
 * @param user_data unused
 */
void
anastasis_gtk_secret_copy_button_clicked_cb (GtkButton *button,
                                             gpointer user_data)
{
  size_t data_len = 0;
  void *data = NULL;
  const char *mime = NULL;
  const char *text = NULL;
  json_t *cs;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_varsize ("value",
                                &data,
                                &data_len),
      NULL),
    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_end ()
  };
  GdkClipboard *cb;

  (void) user_data;
  cs = json_object_get (AG_redux_state,
                        "core_secret");
  GNUNET_assert (NULL != cs);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_JSON_parse (cs,
                                    spec,
                                    NULL, NULL));
  cb = gtk_widget_get_clipboard (GTK_WIDGET (button));
  GNUNET_assert (NULL != cb);
  if (NULL != text)
  {
    gdk_clipboard_set_text (cb,
                            text);
  }
  else
  {
    if (0 == strncasecmp (mime,
                          "text/",
                          strlen ("text/")))
    {
      char *str;

      /* the secret is not necessarily 0-terminated */
      str = g_strndup (data,
                       data_len);
      gdk_clipboard_set_text (cb,
                              str);
      g_free (str);
    }
    else if (0 == strncasecmp (mime,
                               "image/",
                               strlen ("image/")))
    {
      GdkPixbufLoader *loader;

      loader = gdk_pixbuf_loader_new_with_mime_type (mime,
                                                     NULL);
      if (NULL != loader)
      {
        GdkPixbuf *pb;

        gdk_pixbuf_loader_write (loader,
                                 data,
                                 data_len,
                                 NULL);
        pb = gdk_pixbuf_loader_get_pixbuf (loader);
        if (NULL != pb)
        {
          GdkTexture *texture;

          texture = gdk_texture_new_for_pixbuf (pb);
          gdk_clipboard_set_texture (cb,
                                     texture);
          g_object_unref (texture);
        }
        else
        {
          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                      "Failed to parse secret image data.\n");
        }
        g_object_unref (loader);
      }
      else
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Unsupported image mime type `%s'\n",
                    mime);
      }
    }
    else
    {
      GNUNET_break (0);
    }
  }
  GNUNET_JSON_parse_free (spec);
}


/**
 * Constructor for the library.  Loads the magic file.
 */
void __attribute__ ((constructor))
mime_ltdl_init ()
{
  magic = magic_open (MAGIC_MIME_TYPE);
  if (0 != magic_load (magic,
                       NULL))
  {
    GNUNET_break (0);
  }
}


/**
 * Destructor for the library, cleans up.
 */
void __attribute__ ((destructor))
mime_ltdl_fini ()
{
  if (NULL != magic)
  {
    magic_close (magic);
    magic = NULL;
  }
}
