/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"
#include "libpikathumb/pikathumb.h"
#include "libpikawidgets/pikawidgets.h"

#include "widgets-types.h"

#include "config/pikacoreconfig.h"

#include "core/pika.h"
#include "core/pikacontext.h"
#include "core/pikaimagefile.h"
#include "core/pikaprogress.h"
#include "core/pikasubprogress.h"

#include "plug-in/pikapluginmanager-file.h"

#include "pikafiledialog.h" /* eek */
#include "pikathumbbox.h"
#include "pikaview.h"
#include "pikaviewrenderer-frame.h"
#include "pikawidgets-utils.h"

#include "pika-intl.h"


/*  local function prototypes  */

static void     pika_thumb_box_progress_iface_init (PikaProgressInterface *iface);

static void     pika_thumb_box_dispose            (GObject           *object);
static void     pika_thumb_box_finalize           (GObject           *object);

static PikaProgress *
                pika_thumb_box_progress_start     (PikaProgress      *progress,
                                                   gboolean           cancellable,
                                                   const gchar       *message);
static void     pika_thumb_box_progress_end       (PikaProgress      *progress);
static gboolean pika_thumb_box_progress_is_active (PikaProgress      *progress);
static void     pika_thumb_box_progress_set_value (PikaProgress      *progress,
                                                   gdouble            percentage);
static gdouble  pika_thumb_box_progress_get_value (PikaProgress      *progress);
static void     pika_thumb_box_progress_pulse     (PikaProgress      *progress);

static gboolean pika_thumb_box_progress_message   (PikaProgress      *progress,
                                                   Pika              *pika,
                                                   PikaMessageSeverity  severity,
                                                   const gchar       *domain,
                                                   const gchar       *message);

static gboolean pika_thumb_box_ebox_button_press  (GtkWidget         *widget,
                                                   GdkEventButton    *bevent,
                                                   PikaThumbBox      *box);
static void pika_thumb_box_thumbnail_clicked      (GtkWidget         *widget,
                                                   GdkModifierType    state,
                                                   PikaThumbBox      *box);
static void pika_thumb_box_imagefile_info_changed (PikaImagefile     *imagefile,
                                                   PikaThumbBox      *box);
static void pika_thumb_box_thumb_state_notify     (PikaThumbnail     *thumb,
                                                   GParamSpec        *pspec,
                                                   PikaThumbBox      *box);
static void pika_thumb_box_create_thumbnails      (PikaThumbBox      *box,
                                                   gboolean           force);
static void pika_thumb_box_create_thumbnail       (PikaThumbBox      *box,
                                                   GFile             *file,
                                                   PikaThumbnailSize  size,
                                                   gboolean           force,
                                                   PikaProgress      *progress);
static gboolean pika_thumb_box_auto_thumbnail     (PikaThumbBox      *box);


G_DEFINE_TYPE_WITH_CODE (PikaThumbBox, pika_thumb_box, GTK_TYPE_FRAME,
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_PROGRESS,
                                                pika_thumb_box_progress_iface_init))

#define parent_class pika_thumb_box_parent_class


static void
pika_thumb_box_class_init (PikaThumbBoxClass *klass)
{
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->dispose  = pika_thumb_box_dispose;
  object_class->finalize = pika_thumb_box_finalize;

  gtk_widget_class_set_css_name (widget_class, "treeview");
}

static void
pika_thumb_box_init (PikaThumbBox *box)
{
  gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_IN);

  box->idle_id = 0;
}

static void
pika_thumb_box_progress_iface_init (PikaProgressInterface *iface)
{
  iface->start     = pika_thumb_box_progress_start;
  iface->end       = pika_thumb_box_progress_end;
  iface->is_active = pika_thumb_box_progress_is_active;
  iface->set_value = pika_thumb_box_progress_set_value;
  iface->get_value = pika_thumb_box_progress_get_value;
  iface->pulse     = pika_thumb_box_progress_pulse;
  iface->message   = pika_thumb_box_progress_message;
}

static void
pika_thumb_box_dispose (GObject *object)
{
  PikaThumbBox *box = PIKA_THUMB_BOX (object);

  if (box->idle_id)
    {
      g_source_remove (box->idle_id);
      box->idle_id = 0;
    }

  G_OBJECT_CLASS (parent_class)->dispose (object);

  box->progress = NULL;
}

static void
pika_thumb_box_finalize (GObject *object)
{
  PikaThumbBox *box = PIKA_THUMB_BOX (object);

  pika_thumb_box_take_files (box, NULL);

  g_clear_object (&box->imagefile);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static PikaProgress *
pika_thumb_box_progress_start (PikaProgress *progress,
                               gboolean      cancellable,
                               const gchar  *message)
{
  PikaThumbBox *box = PIKA_THUMB_BOX (progress);

  if (! box->progress)
    return NULL;

  if (! box->progress_active)
    {
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);
      GtkWidget      *toplevel;

      gtk_progress_bar_set_fraction (bar, 0.0);

      box->progress_active = TRUE;

      toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box));

      if (PIKA_IS_FILE_DIALOG (toplevel))
        gtk_dialog_set_response_sensitive (GTK_DIALOG (toplevel),
                                           GTK_RESPONSE_CANCEL, cancellable);

      return progress;
    }

  return NULL;
}

static void
pika_thumb_box_progress_end (PikaProgress *progress)
{
  if (pika_thumb_box_progress_is_active (progress))
    {
      PikaThumbBox   *box = PIKA_THUMB_BOX (progress);
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);

      gtk_progress_bar_set_fraction (bar, 0.0);

      box->progress_active = FALSE;
    }
}

static gboolean
pika_thumb_box_progress_is_active (PikaProgress *progress)
{
  PikaThumbBox *box = PIKA_THUMB_BOX (progress);

  return (box->progress && box->progress_active);
}

static void
pika_thumb_box_progress_set_value (PikaProgress *progress,
                                   gdouble       percentage)
{
  if (pika_thumb_box_progress_is_active (progress))
    {
      PikaThumbBox   *box = PIKA_THUMB_BOX (progress);
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);

      gtk_progress_bar_set_fraction (bar, percentage);
    }
}

static gdouble
pika_thumb_box_progress_get_value (PikaProgress *progress)
{
  if (pika_thumb_box_progress_is_active (progress))
    {
      PikaThumbBox   *box = PIKA_THUMB_BOX (progress);
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);

      return gtk_progress_bar_get_fraction (bar);
    }

  return 0.0;
}

static void
pika_thumb_box_progress_pulse (PikaProgress *progress)
{
  if (pika_thumb_box_progress_is_active (progress))
    {
      PikaThumbBox   *box = PIKA_THUMB_BOX (progress);
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);

      gtk_progress_bar_pulse (bar);
    }
}

static gboolean
pika_thumb_box_progress_message (PikaProgress        *progress,
                                 Pika                *pika,
                                 PikaMessageSeverity  severity,
                                 const gchar         *domain,
                                 const gchar         *message)
{
  /*  PikaThumbBox never shows any messages  */

  return TRUE;
}


/*  stupid PikaHeader class just so we get a "header" CSS node  */

#define PIKA_TYPE_HEADER (pika_header_get_type ())

typedef struct _GtkBox      PikaHeader;
typedef struct _GtkBoxClass PikaHeaderClass;

static GType pika_header_get_type (void) G_GNUC_CONST;

G_DEFINE_TYPE (PikaHeader, pika_header, GTK_TYPE_BOX)

static void
pika_header_class_init (PikaHeaderClass *klass)
{
  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (klass), "header");
}

static void
pika_header_init (PikaHeader *header)
{
  gtk_orientable_set_orientation (GTK_ORIENTABLE (header),
                                  GTK_ORIENTATION_VERTICAL);
}


/*  public functions  */

GtkWidget *
pika_thumb_box_new (PikaContext *context)
{
  PikaThumbBox   *box;
  GtkWidget      *vbox;
  GtkWidget      *vbox2;
  GtkWidget      *ebox;
  GtkWidget      *button;
  GtkWidget      *label;
  gchar          *str;
  gint            h, v;

  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  box = g_object_new (PIKA_TYPE_THUMB_BOX, NULL);

  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (box)),
                               GTK_STYLE_CLASS_VIEW);

  box->context = context;

  ebox = gtk_event_box_new ();
  gtk_container_add (GTK_CONTAINER (box), ebox);
  gtk_widget_show (ebox);

  g_signal_connect (ebox, "button-press-event",
                    G_CALLBACK (pika_thumb_box_ebox_button_press),
                    box);

  str = g_strdup_printf (_("Click to update preview\n"
                           "%s-Click to force update even "
                           "if preview is up-to-date"),
                         pika_get_mod_string (pika_get_toggle_behavior_mask ()));

  pika_help_set_help_data (ebox, str, NULL);

  g_free (str);

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_container_add (GTK_CONTAINER (ebox), vbox);
  gtk_widget_show (vbox);

  vbox2 = g_object_new (PIKA_TYPE_HEADER, NULL);
  gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0);
  gtk_widget_show (vbox2);

  button = gtk_button_new ();
  gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  label = gtk_label_new_with_mnemonic (_("Pr_eview"));
  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
  gtk_container_add (GTK_CONTAINER (button), label);
  gtk_widget_show (label);

  g_signal_connect (button, "button-press-event",
                    G_CALLBACK (gtk_true),
                    NULL);
  g_signal_connect (button, "button-release-event",
                    G_CALLBACK (gtk_true),
                    NULL);
  g_signal_connect (button, "enter-notify-event",
                    G_CALLBACK (gtk_true),
                    NULL);
  g_signal_connect (button, "leave-notify-event",
                    G_CALLBACK (gtk_true),
                    NULL);

  vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  gtk_container_set_border_width (GTK_CONTAINER (vbox2), 4);
  gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0);
  gtk_widget_show (vbox2);

  box->imagefile = pika_imagefile_new (context->pika, NULL);

  g_signal_connect (box->imagefile, "info-changed",
                    G_CALLBACK (pika_thumb_box_imagefile_info_changed),
                    box);

  g_signal_connect (pika_imagefile_get_thumbnail (box->imagefile),
                    "notify::thumb-state",
                    G_CALLBACK (pika_thumb_box_thumb_state_notify),
                    box);

  pika_view_renderer_get_frame_size (&h, &v);

  box->preview = pika_view_new (context,
                                PIKA_VIEWABLE (box->imagefile),
                                /* add padding for the shadow frame */
                                context->pika->config->thumbnail_size +
                                MAX (h, v),
                                0, FALSE);

  gtk_style_context_add_class (gtk_widget_get_style_context (box->preview),
                               GTK_STYLE_CLASS_VIEW);
  gtk_box_pack_start (GTK_BOX (vbox2), box->preview, FALSE, FALSE, 0);
  gtk_widget_show (box->preview);

  gtk_label_set_mnemonic_widget (GTK_LABEL (label), box->preview);

  g_signal_connect (box->preview, "clicked",
                    G_CALLBACK (pika_thumb_box_thumbnail_clicked),
                    box);

  box->filename = gtk_label_new (_("No selection"));
  gtk_label_set_max_width_chars (GTK_LABEL (box->filename), 1);
  gtk_label_set_ellipsize (GTK_LABEL (box->filename), PANGO_ELLIPSIZE_MIDDLE);
  gtk_label_set_justify (GTK_LABEL (box->filename), GTK_JUSTIFY_CENTER);
  pika_label_set_attributes (GTK_LABEL (box->filename),
                             PANGO_ATTR_STYLE, PANGO_STYLE_OBLIQUE,
                             -1);
  gtk_box_pack_start (GTK_BOX (vbox2), box->filename, FALSE, FALSE, 0);
  gtk_widget_show (box->filename);

  box->info = gtk_label_new (" \n \n \n ");
  gtk_label_set_justify (GTK_LABEL (box->info), GTK_JUSTIFY_CENTER);
  gtk_label_set_line_wrap (GTK_LABEL (box->info), TRUE);
  pika_label_set_attributes (GTK_LABEL (box->info),
                             PANGO_ATTR_SCALE, PANGO_SCALE_SMALL,
                             -1);
  gtk_box_pack_start (GTK_BOX (vbox2), box->info, FALSE, FALSE, 0);
  gtk_widget_show (box->info);

  box->progress = gtk_progress_bar_new ();
  gtk_widget_set_halign (box->progress, GTK_ALIGN_FILL);
  gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR (box->progress), TRUE);
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), "Fog");
  gtk_box_pack_end (GTK_BOX (vbox2), box->progress, FALSE, FALSE, 0);
  /* don't gtk_widget_show (box->progress); */

  gtk_widget_set_size_request (GTK_WIDGET (box),
                               MAX ((gint) PIKA_THUMB_SIZE_NORMAL,
                                    (gint) context->pika->config->thumbnail_size) +
                               2 * MAX (h, v),
                               -1);

  return GTK_WIDGET (box);
}

void
pika_thumb_box_take_file (PikaThumbBox *box,
                          GFile        *file)
{
  g_return_if_fail (PIKA_IS_THUMB_BOX (box));
  g_return_if_fail (file == NULL || G_IS_FILE (file));

  if (box->idle_id)
    {
      g_source_remove (box->idle_id);
      box->idle_id = 0;
    }

  pika_imagefile_set_file (box->imagefile, file);

  if (file)
    {
      gchar *basename = g_path_get_basename (pika_file_get_utf8_name (file));
      gtk_label_set_text (GTK_LABEL (box->filename), basename);
      g_free (basename);
    }
  else
    {
      gtk_label_set_text (GTK_LABEL (box->filename), _("No selection"));
    }

  gtk_widget_set_sensitive (GTK_WIDGET (box), file != NULL);
  pika_imagefile_update (box->imagefile);
}

void
pika_thumb_box_take_files (PikaThumbBox *box,
                           GSList       *files)
{
  g_return_if_fail (PIKA_IS_THUMB_BOX (box));

  if (box->files)
    {
      g_slist_free_full (box->files, (GDestroyNotify) g_object_unref);
      box->files = NULL;
    }

  box->files = files;
}


/*  private functions  */

static gboolean
pika_thumb_box_ebox_button_press (GtkWidget      *widget,
                                  GdkEventButton *bevent,
                                  PikaThumbBox   *box)
{
  pika_thumb_box_thumbnail_clicked (widget, bevent->state, box);

  return TRUE;
}

static void
pika_thumb_box_thumbnail_clicked (GtkWidget       *widget,
                                  GdkModifierType  state,
                                  PikaThumbBox    *box)
{
  pika_thumb_box_create_thumbnails (box,
                                    (state & pika_get_toggle_behavior_mask ()) ?
                                    TRUE : FALSE);
}

static void
pika_thumb_box_imagefile_info_changed (PikaImagefile *imagefile,
                                       PikaThumbBox  *box)
{
  gtk_label_set_text (GTK_LABEL (box->info),
                      pika_imagefile_get_desc_string (imagefile));
}

static void
pika_thumb_box_thumb_state_notify (PikaThumbnail *thumb,
                                   GParamSpec    *pspec,
                                   PikaThumbBox  *box)
{
  if (box->idle_id)
    return;

  if (thumb->image_state == PIKA_THUMB_STATE_REMOTE)
    return;

  switch (thumb->thumb_state)
    {
    case PIKA_THUMB_STATE_NOT_FOUND:
    case PIKA_THUMB_STATE_OLD:
      box->idle_id =
        g_idle_add_full (G_PRIORITY_LOW,
                         (GSourceFunc) pika_thumb_box_auto_thumbnail,
                         box, NULL);
      break;

    default:
      break;
    }
}

static void
pika_thumb_box_create_thumbnails (PikaThumbBox *box,
                                  gboolean      force)
{
  Pika           *pika     = box->context->pika;
  PikaProgress   *progress = PIKA_PROGRESS (box);
  PikaFileDialog *dialog   = NULL;
  GtkWidget      *toplevel;
  GSList         *list;
  gint            n_files;
  gint            i;

  if (pika->config->thumbnail_size == PIKA_THUMBNAIL_SIZE_NONE)
    return;

  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box));

  if (PIKA_IS_FILE_DIALOG (toplevel))
    dialog = PIKA_FILE_DIALOG (toplevel);

  pika_set_busy (pika);

  if (dialog)
    pika_file_dialog_set_sensitive (dialog, FALSE);
  else
    gtk_widget_set_sensitive (toplevel, FALSE);

  if (box->files)
    {
      gtk_widget_hide (box->info);
      gtk_widget_show (box->progress);
    }

  n_files = g_slist_length (box->files);

  if (n_files > 1)
    {
      gchar *str;

      pika_progress_start (PIKA_PROGRESS (box), TRUE, "%s", "");

      progress = pika_sub_progress_new (PIKA_PROGRESS (box));

      pika_sub_progress_set_step (PIKA_SUB_PROGRESS (progress), 0, n_files);

      for (list = box->files->next, i = 1;
           list;
           list = g_slist_next (list), i++)
        {
          str = g_strdup_printf (_("Thumbnail %d of %d"), i, n_files);
          gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), str);
          g_free (str);

          pika_progress_set_value (progress, 0.0);

          while (g_main_context_pending (NULL))
            g_main_context_iteration (NULL, FALSE);

          pika_thumb_box_create_thumbnail (box,
                                           list->data,
                                           pika->config->thumbnail_size,
                                           force,
                                           progress);

          if (dialog && dialog->canceled)
            goto canceled;

          pika_sub_progress_set_step (PIKA_SUB_PROGRESS (progress), i, n_files);
        }

      str = g_strdup_printf (_("Thumbnail %d of %d"), n_files, n_files);
      gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), str);
      g_free (str);

      pika_progress_set_value (progress, 0.0);

      while (g_main_context_pending (NULL))
        g_main_context_iteration (NULL, FALSE);
    }

  if (box->files)
    {
      pika_thumb_box_create_thumbnail (box,
                                       box->files->data,
                                       pika->config->thumbnail_size,
                                       force,
                                       progress);

      pika_progress_set_value (progress, 1.0);
    }

 canceled:

  if (n_files > 1)
    {
      g_object_unref (progress);

      pika_progress_end (PIKA_PROGRESS (box));
      gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), "");
    }

  if (box->files)
    {
      gtk_widget_hide (box->progress);
      gtk_widget_show (box->info);
    }

  if (dialog)
    pika_file_dialog_set_sensitive (dialog, TRUE);
  else
    gtk_widget_set_sensitive (toplevel, TRUE);

  pika_unset_busy (pika);
}

static void
pika_thumb_box_create_thumbnail (PikaThumbBox      *box,
                                 GFile             *file,
                                 PikaThumbnailSize  size,
                                 gboolean           force,
                                 PikaProgress      *progress)
{
  PikaThumbnail *thumb = pika_imagefile_get_thumbnail (box->imagefile);
  gchar         *basename;

  basename = g_path_get_basename (pika_file_get_utf8_name (file));
  gtk_label_set_text (GTK_LABEL (box->filename), basename);
  g_free (basename);

  pika_imagefile_set_file (box->imagefile, file);

  if (force ||
      (pika_thumbnail_peek_thumb (thumb, (PikaThumbSize) size) < PIKA_THUMB_STATE_FAILED &&
       ! pika_thumbnail_has_failed (thumb)))
    {
      GError *error = NULL;

      if (! pika_imagefile_create_thumbnail (box->imagefile, box->context,
                                             progress,
                                             size, ! force, &error))
        {
          pika_message_literal (box->context->pika,
                                G_OBJECT (progress), PIKA_MESSAGE_ERROR,
                                error->message);
          g_clear_error (&error);
        }
    }
}

static gboolean
pika_thumb_box_auto_thumbnail (PikaThumbBox *box)
{
  Pika          *pika  = box->context->pika;
  PikaThumbnail *thumb = pika_imagefile_get_thumbnail (box->imagefile);
  GFile         *file  = pika_imagefile_get_file (box->imagefile);

  box->idle_id = 0;

  if (thumb->image_state == PIKA_THUMB_STATE_NOT_FOUND)
    return FALSE;

  switch (thumb->thumb_state)
    {
    case PIKA_THUMB_STATE_NOT_FOUND:
    case PIKA_THUMB_STATE_OLD:
      if (thumb->image_filesize < pika->config->thumbnail_filesize_limit &&
          ! pika_thumbnail_has_failed (thumb)                            &&
          pika_plug_in_manager_file_procedure_find_by_extension (pika->plug_in_manager,
                                                                 PIKA_FILE_PROCEDURE_GROUP_OPEN,
                                                                 file))
        {
          if (thumb->image_filesize > 0)
            {
              gchar *size;
              gchar *text;

              size = g_format_size (thumb->image_filesize);
              text = g_strdup_printf ("%s\n%s",
                                      size, _("Creating preview..."));

              gtk_label_set_text (GTK_LABEL (box->info), text);

              g_free (text);
              g_free (size);
            }
          else
            {
              gtk_label_set_text (GTK_LABEL (box->info),
                                  _("Creating preview..."));
            }

          pika_imagefile_create_thumbnail_weak (box->imagefile, box->context,
                                                PIKA_PROGRESS (box),
                                                pika->config->thumbnail_size,
                                                TRUE);
        }
      break;

    default:
      break;
    }

  return FALSE;
}
