/*
 * This file is part of YAD.
 *
 * YAD 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.
 *
 * YAD 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 YAD. If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2008-2023, Victor Ananjevsky <victor@sanana.kiev.ua>
 */

#include "yad.h"

enum {
  SIZE_FIT,
  SIZE_ORIG,
  SIZE_INC,
  SIZE_DEC
};

enum {
  ROTATE_LEFT,
  ROTATE_RIGHT,
  ROTATE_FLIP_VERT,
  ROTATE_FLIP_HOR
};

enum {
  COPY_IMAGE,
  COPY_FILE
};

typedef struct {
  gchar *filename;
  GdkPixbufAnimation *anim_pb;
  GdkPixbuf *orig_pb;
  gboolean loaded;
  gboolean animated;
} ImageItem;

static GList *img_list = NULL;
static GList *lp = NULL;
static ImageItem *img = NULL;

static GtkWidget *picture;
static GtkWidget *viewport;
static GtkWidget *popup_menu;

static void create_popup_menu ();

static void
img_free (ImageItem *ii)
{
  if (ii)
    {
      g_free (ii->filename);
      if (ii->orig_pb)
        g_object_unref (ii->orig_pb);
      if (ii->anim_pb)
        g_object_unref (ii->anim_pb);
      g_free (ii);
    }
}

static void
load_picture ()
{
  if (!img)
    return;

  if (!img->loaded)
    {
      if (img->filename && g_file_test (img->filename, G_FILE_TEST_EXISTS))
        {
          img->anim_pb = gdk_pixbuf_animation_new_from_file (img->filename, NULL);
          img->orig_pb = gdk_pixbuf_animation_get_static_image (img->anim_pb);

          img->animated = !gdk_pixbuf_animation_is_static_image (img->anim_pb);
        }
      img->loaded = TRUE;
    }

  if (img->orig_pb)
    {
      if (img->animated)
        gtk_image_set_from_animation (GTK_IMAGE (picture), g_object_ref (img->anim_pb));
      else
        gtk_image_set_from_pixbuf (GTK_IMAGE (picture), g_object_ref (img->orig_pb));
      if (options.picture_data.size == YAD_PICTURE_FIT)
        picture_fit_to_window ();
    }
  else
    gtk_image_set_from_icon_name (GTK_IMAGE (picture), "image-missing", GTK_ICON_SIZE_DIALOG);
}

static void
img_changed_hook ()
{
  gchar *qfn, *cmd = NULL;

  if (options.picture_data.change_cmd == NULL)
    return;
  if (!img)
    return;
  
  qfn = g_shell_quote (img->filename);
  if (g_strstr_len (options.picture_data.change_cmd, -1, "%s") != NULL)
    cmd = g_strdup_printf (options.picture_data.change_cmd, qfn);
  else
    cmd = g_strdup_printf ("%s '%s'", options.picture_data.change_cmd, qfn);
  g_free (qfn);

  run_command_async (cmd);
  g_free (cmd);
}

static void
next_img_cb (GtkWidget *w, gpointer d)
{
  lp = g_list_next (lp);
  if (!lp)
    lp = g_list_first (img_list);
  if (lp)
    img = (ImageItem *) lp->data;

  load_picture ();
  img_changed_hook ();
}

static void
prev_img_cb (GtkWidget *w, gpointer d)
{
  lp = g_list_previous (lp);
  if (!lp)
    lp = g_list_last (img_list);
  if (lp)
    img = (ImageItem *) lp->data;

  load_picture ();
  img_changed_hook ();
}

static void
first_img_cb (GtkWidget *w, gpointer d)
{
  lp = g_list_first (img_list);
  if (lp)
    img = (ImageItem *) lp->data;

  load_picture ();
  img_changed_hook ();
}

static void
last_img_cb (GtkWidget *w, gpointer d)
{
  lp = g_list_last (img_list);
  if (lp)
    img = (ImageItem *) lp->data;

  load_picture ();
  img_changed_hook ();
}

void
picture_fit_to_window ()
{
  gdouble width, height, ww, wh;
  gdouble factor;

  if (!gtk_widget_get_realized (viewport) || !img)
    return;

  width = gdk_pixbuf_get_width (img->orig_pb);
  height = gdk_pixbuf_get_height (img->orig_pb);

  ww = gdk_window_get_width (gtk_viewport_get_view_window (GTK_VIEWPORT (viewport)));
  wh = gdk_window_get_height (gtk_viewport_get_view_window (GTK_VIEWPORT (viewport)));

  factor = MIN (ww / width, wh / height);
  if (factor < 1.0)
    {
      GdkPixbuf *pb = gdk_pixbuf_scale_simple (img->orig_pb, width * factor, height * factor, GDK_INTERP_HYPER);
      if (pb)
        gtk_image_set_from_pixbuf (GTK_IMAGE (picture), pb);
    }
}

static void
change_size_cb (GtkWidget *w, gint type)
{
  gdouble width, height;
  GdkPixbuf *new_pb, *pb = gtk_image_get_pixbuf (GTK_IMAGE (picture));

  if (!pb)
    {
      g_printerr ("picture: can't get pixbuf\n");
      return;
    }

  width = gdk_pixbuf_get_width (pb);
  height = gdk_pixbuf_get_height (pb);

  switch (type)
    {
    case SIZE_FIT:
      picture_fit_to_window ();
      options.picture_data.size = YAD_PICTURE_FIT;
      break;
    case SIZE_ORIG:
      gtk_image_set_from_pixbuf (GTK_IMAGE (picture), g_object_ref (img->orig_pb));
      options.picture_data.size = YAD_PICTURE_ORIG;
      break;
    case SIZE_INC:
      new_pb = gdk_pixbuf_scale_simple (img->orig_pb, width + options.picture_data.inc,
                                        height + options.picture_data.inc, GDK_INTERP_HYPER);
      if (new_pb)
        {
          gtk_image_set_from_pixbuf (GTK_IMAGE (picture), new_pb);
          g_object_unref (pb);
        }
      break;
    case SIZE_DEC:
      new_pb = gdk_pixbuf_scale_simple (img->orig_pb, width - options.picture_data.inc,
                                        height - options.picture_data.inc, GDK_INTERP_HYPER);
      if (new_pb)
        {
          gtk_image_set_from_pixbuf (GTK_IMAGE (picture), new_pb);
          g_object_unref (pb);
        }
      break;
    }
}

static void
rotate_cb (GtkWidget *w, gint type)
{
  GdkPixbuf *new_pb = NULL;
  GdkPixbuf *pb = gtk_image_get_pixbuf (GTK_IMAGE (picture));

  if (!pb)
    {
      g_printerr ("picture: can't get pixbuf\n");
      return;
    }

  switch (type)
    {
    case ROTATE_LEFT:
      new_pb = gdk_pixbuf_rotate_simple (pb, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
      break;
    case ROTATE_RIGHT:
      new_pb = gdk_pixbuf_rotate_simple (pb, GDK_PIXBUF_ROTATE_CLOCKWISE);
      break;
    case ROTATE_FLIP_VERT:
      new_pb = gdk_pixbuf_flip (pb, FALSE);
      break;
    case ROTATE_FLIP_HOR:
      new_pb = gdk_pixbuf_flip (pb, TRUE);
      break;
    }

  if (new_pb)
    {
      gtk_image_set_from_pixbuf (GTK_IMAGE (picture), new_pb);
      g_object_unref (pb);
    }
}

static void
open_file_cb (GtkWidget *w, gpointer d)
{
  GtkWidget *dlg;
  GtkFileFilter *imgf, *allf;
  static gchar *dir = NULL;

  imgf = gtk_file_filter_new ();
  gtk_file_filter_set_name (imgf, _("Images"));
  gtk_file_filter_add_pixbuf_formats (imgf);

  allf = gtk_file_filter_new ();
  gtk_file_filter_set_name (allf, _("All files"));
  gtk_file_filter_add_pattern (allf, "*");

  dlg = gtk_file_chooser_dialog_new (_("YAD - Select Image(s)"),
                                     GTK_WINDOW (gtk_widget_get_toplevel (w)),
                                     GTK_FILE_CHOOSER_ACTION_OPEN,
                                     _("Cancel"), GTK_RESPONSE_CANCEL,
                                     _("OK"), GTK_RESPONSE_ACCEPT,
                                     NULL);
  gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), TRUE);
  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), imgf);
  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), allf);
  if (dir)
    gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dlg), dir);

  if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT)
    {
      GSList *fp, *fn = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dlg));

      if (img_list)
        {
          g_list_free_full (img_list, (GDestroyNotify) img_free);
          img_list = NULL;
        }

      for (fp = fn; fp; fp = fp->next)
        {
          ImageItem *ii = g_new0 (ImageItem, 1);

          ii->filename = g_strdup (fp->data);
          img_list = g_list_append (img_list, ii);
        }
      g_slist_free_full (fn, g_free);

      lp = g_list_first (img_list);
      img = (ImageItem *) lp->data;
      load_picture ();
      img_changed_hook ();

      /* recreate menu */
      gtk_widget_destroy (popup_menu);
      create_popup_menu ();

      /* keep current dir */
      g_free (dir);
      dir = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg));
    }

  gtk_widget_destroy (dlg);
}

static void
copy_cb (GtkWidget *w, gint type)
{
  GtkClipboard *clip;

  clip = gtk_clipboard_get_default (gdk_display_get_default ());

  switch (type)
    {
    case COPY_IMAGE:
      gtk_clipboard_set_image (clip, img->orig_pb);
      break;
    case COPY_FILE:
      gtk_clipboard_set_text (clip, img->filename, -1);
      break;
    }
}

static void
create_popup_menu ()
{
  GtkWidget *mi, *al;

  popup_menu = gtk_menu_new ();
  gtk_menu_set_reserve_toggle_size (GTK_MENU (popup_menu), FALSE);

  if (options.common_data.file_op)
    {
      mi = gtk_menu_item_new_with_label (_("Open file(s)..."));
      al = gtk_bin_get_child (GTK_BIN (mi));
      gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_o, GDK_CONTROL_MASK);
      gtk_widget_show (mi);
      gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
      g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (open_file_cb), NULL);

      mi = gtk_separator_menu_item_new ();
      gtk_widget_show (mi);
      gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
    }

  if (img_list)
    {
      mi = gtk_menu_item_new_with_label (_("Next image"));
      al = gtk_bin_get_child (GTK_BIN (mi));
      gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_Right, 0);
      gtk_widget_show (mi);
      gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
      g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (next_img_cb), NULL);

      mi = gtk_menu_item_new_with_label (_("Previous image"));
      al = gtk_bin_get_child (GTK_BIN (mi));
      gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_Left, 0);
      gtk_widget_show (mi);
      gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
      g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (prev_img_cb), NULL);

      mi = gtk_menu_item_new_with_label (_("First image"));
      al = gtk_bin_get_child (GTK_BIN (mi));
      gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_Home, 0);
      gtk_widget_show (mi);
      gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
      g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (first_img_cb), NULL);

      mi = gtk_menu_item_new_with_label (_("Last image"));
      al = gtk_bin_get_child (GTK_BIN (mi));
      gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_End, 0);
      gtk_widget_show (mi);
      gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
      g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (last_img_cb), NULL);

      mi = gtk_separator_menu_item_new ();
      gtk_widget_show (mi);
      gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
    }

  mi = gtk_menu_item_new_with_label (_("Fit to window"));
  al = gtk_bin_get_child (GTK_BIN (mi));
  gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_w, 0);
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (change_size_cb), GINT_TO_POINTER (SIZE_FIT));

  mi = gtk_menu_item_new_with_label (_("Original size"));
  al = gtk_bin_get_child (GTK_BIN (mi));
  gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_o, 0);
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (change_size_cb), GINT_TO_POINTER (SIZE_ORIG));

  mi = gtk_menu_item_new_with_label (_("Increase size"));
  al = gtk_bin_get_child (GTK_BIN (mi));
  gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_plus, 0);
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (change_size_cb), GINT_TO_POINTER (SIZE_INC));

  mi = gtk_menu_item_new_with_label (_("Decrease size"));
  al = gtk_bin_get_child (GTK_BIN (mi));
  gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_minus, 0);
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (change_size_cb), GINT_TO_POINTER (SIZE_DEC));

  mi = gtk_separator_menu_item_new ();
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);

  mi = gtk_menu_item_new_with_label (_("Rotate left"));
  al = gtk_bin_get_child (GTK_BIN (mi));
  gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_l, 0);
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (rotate_cb), GINT_TO_POINTER (ROTATE_LEFT));

  mi = gtk_menu_item_new_with_label (_("Rotate right"));
  al = gtk_bin_get_child (GTK_BIN (mi));
  gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_r, 0);
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (rotate_cb), GINT_TO_POINTER (ROTATE_RIGHT));

  mi = gtk_menu_item_new_with_label (_("Flip vertical"));
  al = gtk_bin_get_child (GTK_BIN (mi));
  gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_v, 0);
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (rotate_cb), GINT_TO_POINTER (ROTATE_FLIP_VERT));

  mi = gtk_menu_item_new_with_label (_("Flip horizontal"));
  al = gtk_bin_get_child (GTK_BIN (mi));
  gtk_accel_label_set_accel (GTK_ACCEL_LABEL (al), GDK_KEY_h, 0);
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (rotate_cb), GINT_TO_POINTER (ROTATE_FLIP_HOR));

  mi = gtk_separator_menu_item_new ();
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);

  mi = gtk_menu_item_new_with_label (_("Copy image to clipboard"));
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (copy_cb), GINT_TO_POINTER (COPY_IMAGE));

  mi = gtk_menu_item_new_with_label (_("Copy filename to clipboard"));
  gtk_widget_show (mi);
  gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), mi);
  g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (copy_cb), GINT_TO_POINTER (COPY_FILE));
}

static gboolean
button_handler (GtkWidget *w, GdkEventButton *ev, gpointer data)
{
  if (ev->button == 3 && (!img || !img->animated))
    {
      gtk_menu_popup_at_pointer (GTK_MENU (popup_menu), NULL);
      return TRUE;
    }

  return FALSE;
}

static gboolean
key_handler (GtkWidget *w, GdkEventKey *ev, gpointer data)
{
  switch (ev->keyval)
    {
    case GDK_KEY_Right:
    case GDK_KEY_KP_Right:
      next_img_cb (w, NULL);
      return TRUE;
    case GDK_KEY_Left:
    case GDK_KEY_KP_Left:
      prev_img_cb (w, NULL);
      return TRUE;
    case GDK_KEY_Home:
    case GDK_KEY_KP_Home:
      first_img_cb (w, NULL);
      return TRUE;
    case GDK_KEY_End:
    case GDK_KEY_KP_End:
      last_img_cb (w, NULL);
      return TRUE;
    case GDK_KEY_W:
    case GDK_KEY_w:
      change_size_cb (w, SIZE_FIT);
      return TRUE;
    case GDK_KEY_O:
    case GDK_KEY_o:
      if (ev->state & GDK_CONTROL_MASK)
        open_file_cb (w, NULL);
      else
        change_size_cb (w, SIZE_ORIG);
      return TRUE;
    case GDK_KEY_plus:
    case GDK_KEY_KP_Add:
      change_size_cb (w, SIZE_INC);
      return TRUE;
    case GDK_KEY_minus:
    case GDK_KEY_KP_Subtract:
      change_size_cb (w, SIZE_DEC);
      return TRUE;
    case GDK_KEY_L:
    case GDK_KEY_l:
      rotate_cb (w, ROTATE_LEFT);
      return TRUE;
    case GDK_KEY_R:
    case GDK_KEY_r:
      rotate_cb (w, ROTATE_RIGHT);
      return TRUE;
    case GDK_KEY_V:
    case GDK_KEY_v:
      rotate_cb (w, ROTATE_FLIP_VERT);
      return TRUE;
    case GDK_KEY_H:
    case GDK_KEY_h:
      rotate_cb (w, ROTATE_FLIP_HOR);
      return TRUE;
    }
  return FALSE;
}

static void
size_allocate_cb ()
{
  if (options.picture_data.size == YAD_PICTURE_FIT)
    picture_fit_to_window ();
}

static void init_img_data ()
{
  if (options.common_data.uri)
    {
      img = g_new0 (ImageItem, 1);
      img->filename = options.common_data.uri;
    }
  else if (options.extra_data && *options.extra_data)
    {
      gchar **args = options.extra_data;
      gint i = 0;

      while (args[i] != NULL)
        {
          ImageItem *ii;

          ii = g_new0 (ImageItem, 1);
          ii->filename = g_strdup (args[i]);
          img_list = g_list_append (img_list, ii);
          i++;
        }

      lp = g_list_first (img_list);
      img = (ImageItem *) lp->data;
    }
}

GtkWidget *
picture_create_widget (GtkWidget * dlg)
{
  GtkWidget *sw, *ev;

  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), options.data.hscroll_policy, options.data.vscroll_policy);

  viewport = gtk_viewport_new (gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (sw)),
                               gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw)));
  gtk_container_add (GTK_CONTAINER (sw), viewport);

  ev = gtk_event_box_new ();
  gtk_container_add (GTK_CONTAINER (viewport), ev);

  picture = gtk_image_new ();
  gtk_widget_set_can_focus (picture, TRUE); /* need to get key press events */
  gtk_container_add (GTK_CONTAINER (ev), picture);

  g_signal_connect (G_OBJECT (ev), "button-press-event", G_CALLBACK (button_handler), NULL);
  g_signal_connect (G_OBJECT (ev), "key-press-event", G_CALLBACK (key_handler), NULL);
  g_signal_connect (G_OBJECT (ev), "size-allocate", G_CALLBACK (size_allocate_cb), NULL);

  init_img_data ();

  /* must be after calling init_img_data() */
  create_popup_menu ();

  /* load picture */
  if (img)
    load_picture ();
  else
    gtk_image_set_from_icon_name (GTK_IMAGE (picture), "image-missing", GTK_ICON_SIZE_DIALOG);

  return sw;
}