/*
     This file is part of anastasis-gtk.
     Copyright (C) 2020 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_helper.c
 * @brief Helper functions of anastasis-gtk
 * @author Christian Grothoff
 * @author Dennis Neufeld
 */
#define _GNU_SOURCE
#include <stdio.h>
#include "anastasis_gtk_util.h"
#include <gnunet/gnunet_util_lib.h>
#include "anastasis-gtk_helper.h"
#include <jansson.h>
#include <qrencode.h>
#include <gdk-pixbuf/gdk-pixbuf.h>


/**
 * true if we are currently showing an error message.
 */
bool AG_have_error;


void
AG_thaw ()
{
  AG_error_clear ();
  AG_sensitive ("anastasis_gtk_main_window");
  GNUNET_assert (NULL == AG_ra);
}


void
AG_freeze ()
{
  AG_insensitive ("anastasis_gtk_main_window");
  AG_stop_long_action ();
  GNUNET_assert (NULL == AG_ra);
}


void
AG_sensitive (const char *name)
{
  GtkWidget *w;

  w = GTK_WIDGET (GCG_get_main_window_object (name));
  if (NULL == w)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Widget `%s' not found, cannot make it sensitive!\n",
                name);
    return;
  }
  gtk_widget_set_sensitive (w,
                            true);
}


void
AG_focus (const char *name)
{
  GtkWidget *w;

  w = GTK_WIDGET (GCG_get_main_window_object (name));
  if (NULL == w)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Widget `%s' not found, cannot focus on it!\n",
                name);
    return;
  }
  gtk_widget_grab_focus (w);
}


void
AG_insensitive (const char *name)
{
  GtkWidget *w;

  w = GTK_WIDGET (GCG_get_main_window_object (name));
  if (NULL == w)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Widget `%s' not found, cannot make it sensitive!\n",
                name);
    return;
  }
  gtk_widget_set_sensitive (w,
                            false);
}


void
AG_hide (const char *name)
{
  GtkWidget *w;

  w = GTK_WIDGET (GCG_get_main_window_object (name));
  if (NULL == w)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Widget `%s' not found, cannot hide it!\n",
                name);
    return;
  }
  gtk_widget_set_visible (w,
                          FALSE);
}


void
AG_show (const char *name)
{
  GtkWidget *w;

  w = GTK_WIDGET (GCG_get_main_window_object (name));
  if (NULL == w)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Widget `%s' not found, cannot show it!\n",
                name);
    return;
  }
  gtk_widget_set_visible (w,
                          TRUE);
}


/**
 * Look up the widget @a name in the main window and return its first child.
 *
 * @param name widget to inspect
 * @return NULL if the widget does not exist or has no children
 */
static GtkWidget *
first_child (const char *name)
{
  GObject *w;

  w = GCG_get_main_window_object (name);
  if (NULL == w)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Widget `%s' not found, cannot iterate over its children!\n",
                name);
    return NULL;
  }
  return gtk_widget_get_first_child (GTK_WIDGET (w));
}


void
AG_insensitive_children (const char *name)
{
  for (GtkWidget *child = first_child (name);
       NULL != child;
       child = gtk_widget_get_next_sibling (child))
    gtk_widget_set_sensitive (child,
                              false);
}


void
AG_hide_children (const char *name)
{
  for (GtkWidget *child = first_child (name);
       NULL != child;
       child = gtk_widget_get_next_sibling (child))
    gtk_widget_set_visible (child,
                            FALSE);
}


void
AG_show_children (const char *name)
{
  for (GtkWidget *child = first_child (name);
       NULL != child;
       child = gtk_widget_get_next_sibling (child))
    gtk_widget_set_visible (child,
                            TRUE);
}


void
AG_hide_all_frames (void)
{
  AG_hide ("anastasis_gtk_start_frame");
  AG_hide_children ("anastasis_gtk_super_vbox");
  if (AG_have_error)
    AG_show ("anastasis_gtk_error_label");
}


bool
AG_check_state (json_t *state,
                const char *expected_state)
{
  const char *state_name = json_string_value (json_object_get (state,
                                                               "backup_state"));
  if (NULL == state_name)
    state_name = json_string_value (json_object_get (state,
                                                     "recovery_state"));
  if (NULL == state_name)
    return false;
  return (0 == strcasecmp (state_name,
                           expected_state));
}


/**
 * Get an object from the main window.
 *
 * @param name name of the object
 * @return NULL on error
 */
GObject *
GCG_get_main_window_object (const char *name)
{
  if (NULL == AG_ml)
    return NULL;
  return ANASTASIS_GTK_main_loop_get_object (AG_ml,
                                          name);
}


/**
 * Make the 'next' button sensitive (and trigger it via 'Return').
 */
void
AG_enable_next (void)
{
  GtkWidget *fwd;

  AG_show ("anastasis_gtk_main_window_forward_button");
  AG_sensitive ("anastasis_gtk_main_window_forward_button");
  fwd = GTK_WIDGET (GCG_get_main_window_object (
                      "anastasis_gtk_main_window_forward_button"));
  gtk_window_set_default_widget (GTK_WINDOW (gtk_widget_get_root (fwd)),
                                 fwd);
}


void
AG_error_clear ()
{
  AG_have_error = false;
  AG_hide ("anastasis_gtk_error_label");
}


void
AG_error (const char *format,
          ...)
{
  va_list ap;
  char *msg;
  int ret;
  GtkLabel *l;

  va_start (ap, format);
  ret = vasprintf (&msg,
                   format,
                   ap);
  va_end (ap);
  if (-1 == ret)
  {
    GNUNET_break (0);
    return;
  }
  l = GTK_LABEL (GCG_get_main_window_object ("anastasis_gtk_error_label"));
  if (NULL == l)
  {
    GNUNET_break (0);
    return;
  }
  gtk_label_set_text (l,
                      msg);
  free (msg);
  AG_have_error = true;
  gtk_widget_set_visible (GTK_WIDGET (l),
                          TRUE);
}


/**
 * Create a the QR code image from a given @a text.
 *
 * @param scale factor for scaling up the size of the image to create
 * @param text text to encode
 * @return NULL on error
 */
static GdkPixbuf *
create_qrcode (unsigned int scale,
               const char *text,
               size_t text_size)
{
  QRinput *qri;
  QRcode *qrc;
  GdkPixbuf *pb;
  guchar *pixels;
  int n_channels;
  const char *dir;
  char *fn;
  unsigned int size;

  qri = QRinput_new2 (0, QR_ECLEVEL_M);
  if (NULL == qri)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2");
    return NULL;
  }
  /* first try encoding as uppercase-only alpha-numerical
     QR code (much smaller encoding); if that fails, also
     try using binary encoding (in case nick contains
     special characters). */
  if ((0 != QRinput_append (qri,
                            QR_MODE_AN,
                            text_size,
                            (unsigned char *) text)) &&
      (0 != QRinput_append (qri,
                            QR_MODE_8,
                            text_size,
                            (unsigned char *) text)))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
                         "QRinput_append");
    return NULL;
  }
  qrc = QRcode_encodeInput (qri);
  if (NULL == qrc)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
                         "QRcode_encodeInput");
    QRinput_free (qri);
    return NULL;
  }
  /* We create the pixbuf by loading a dummy file from disk, which gives us
     the pixel layout the renderer expects. */
  dir = ANASTASIS_GTK_get_data_dir (ANASTASIS_GTK_project_data ());
  GNUNET_asprintf (&fn,
                   "%s%s",
                   dir,
                   "qr_dummy.png");
  size = (qrc->width + 8) * scale;
  size += 8 - (size % 8);
  pb = gdk_pixbuf_new_from_file_at_size (fn,
                                         size,
                                         size,
                                         NULL);
  GNUNET_free (fn);
  if (NULL == pb)
  {
    QRcode_free (qrc);
    QRinput_free (qri);
    return NULL;
  }
  pixels = gdk_pixbuf_get_pixels (pb);
  n_channels = gdk_pixbuf_get_n_channels (pb);
  for (unsigned int x = 4 * scale; x < size - 4 * scale; x++)
    for (unsigned int y = 4 * scale; y < size - 4 * scale; y++)
    {
      unsigned int xx = x - 4 * scale;
      unsigned int yy = y - 4 * scale;
      unsigned int ss = size - 8 * scale;
      unsigned int off =
        (xx * qrc->width / ss) + (yy * qrc->width / ss) * qrc->width;
      for (int c = 0; c < n_channels; c++)
        pixels[(y * size + x) * n_channels + c] =
          (0 == (qrc->data[off] & 1)) ? 0xFF : 0;
    }
  QRcode_free (qrc);
  QRinput_free (qri);
  return pb;
}


GdkPixbuf *
AG_setup_qrcode (GtkWidget *w,
                 const char *text,
                 size_t text_size)
{
  GtkSettings *settings;
  gint dpi;
  int scale;

  if (NULL == w)
  {
    GNUNET_break (0);
    return NULL;
  }
  /* adjust scale to screen resolution */
  settings = gtk_widget_get_settings (w);
  g_object_get (G_OBJECT (settings),
                "gtk-xft-dpi",
                &dpi,
                NULL);
  if (-1 == dpi)
    scale = 2;
  else if (dpi >= 122800)
    scale = 4;
  else if (dpi >= 98304)
    scale = 3;
  else
    scale = 2;
  return create_qrcode (scale,
                        text,
                        text_size);
}


void
AG_image_set_from_pixbuf (GtkImage *img,
                          GdkPixbuf *pb)
{
  GdkTexture *texture;

  texture = gdk_texture_new_for_pixbuf (pb);
  gtk_image_set_from_paintable (img,
                                GDK_PAINTABLE (texture));
  g_object_unref (texture);
}


char *
AG_expand_name (const char *name,
                const char *type)
{
  static struct
  {
    const char *type;
    const char *suffix;
  } type_map [] = {
    { .type = "string",
      .suffix = "entry" },
    { .type = "date",
      .suffix = "cal" },
    { .type = NULL,
      .suffix = NULL }
  };
  char *data_widget;

  for (unsigned int i = 0; NULL != type_map[i].type; i++)
  {
    if (0 != strcmp (type_map[i].type,
                     type))
      continue;
    GNUNET_asprintf (&data_widget,
                     "%s_%s",
                     name,
                     type_map[i].suffix);
    return data_widget;
  }
  return NULL;
}


/**
 * Show widget when the watched entry takes the focus.
 *
 * @param w widget to show (the UI file connects this 'swapped')
 * @param controller the focus controller of the entry
 */
void
anastasis_gtk_show_on_focus_enter_cb (GtkWidget *w,
                                      GtkEventControllerFocus *controller)
{
  gtk_widget_set_visible (w,
                          TRUE);
}


/**
 * Hide widget when the watched entry loses the focus.
 *
 * @param w widget to hide (the UI file connects this 'swapped')
 * @param controller the focus controller of the entry
 */
void
anastasis_gtk_hide_on_focus_leave_cb (GtkWidget *w,
                                      GtkEventControllerFocus *controller)
{
  gtk_widget_set_visible (w,
                          FALSE);
}
