/* 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 <gegl.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

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

#include "actions-types.h"

#include "config/pikadisplayconfig.h"
#include "config/pikaguiconfig.h"

#include "core/pika.h"
#include "core/pikaimage.h"
#include "core/pikalist.h"

#include "widgets/pikaaction.h"
#include "widgets/pikaactiongroup.h"
#include "widgets/pikadialogfactory.h"
#include "widgets/pikadock.h"
#include "widgets/pikadockwindow.h"
#include "widgets/pikahelp-ids.h"

#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"

#include "dialogs/dialogs.h"

#include "windows-actions.h"
#include "windows-commands.h"

#include "pika-intl.h"


static void  windows_actions_display_add               (PikaContainer     *container,
                                                        PikaDisplay       *display,
                                                        PikaActionGroup   *group);
static void  windows_actions_display_remove            (PikaContainer     *container,
                                                        PikaDisplay       *display,
                                                        PikaActionGroup   *group);
static void  windows_actions_display_reorder           (PikaContainer     *container,
                                                        PikaDisplay       *display,
                                                        gint               position,
                                                        PikaActionGroup   *group);
static void  windows_actions_image_notify              (PikaDisplay       *display,
                                                        const GParamSpec  *unused,
                                                        PikaActionGroup   *group);
static void  windows_actions_title_notify              (PikaDisplayShell  *shell,
                                                        const GParamSpec  *unused,
                                                        PikaActionGroup   *group);
static void  windows_actions_update_display_accels     (PikaActionGroup   *group);

static void  windows_actions_dock_window_added         (PikaDialogFactory *factory,
                                                        PikaDockWindow    *dock_window,
                                                        PikaActionGroup   *group);
static void  windows_actions_dock_window_removed       (PikaDialogFactory *factory,
                                                        PikaDockWindow    *dock_window,
                                                        PikaActionGroup   *group);
static void  windows_actions_dock_window_notify        (PikaDockWindow    *dock,
                                                        const GParamSpec  *pspec,
                                                        PikaActionGroup   *group);
static void  windows_actions_recent_add                (PikaContainer     *container,
                                                        PikaSessionInfo   *info,
                                                        PikaActionGroup   *group);
static void  windows_actions_recent_remove             (PikaContainer     *container,
                                                        PikaSessionInfo   *info,
                                                        PikaActionGroup   *group);
static void  windows_actions_single_window_mode_notify (PikaDisplayConfig *config,
                                                        GParamSpec        *pspec,
                                                        PikaActionGroup   *group);


/* The only reason we have "Tab" in the action entries below is to
 * give away the hardcoded keyboard shortcut. If the user changes the
 * shortcut to something else, both that shortcut and Tab will
 * work. The reason we have the shortcut hardcoded is because
 * gtk_accelerator_valid() returns FALSE for GDK_tab.
 */

static const PikaActionEntry windows_actions[] =
{
  { "windows-show-display-next", NULL,
    NC_("windows-action", "Next Image"), NULL, { "<alt>Tab", "Forward", NULL },
    NC_("windows-action", "Switch to the next image"),
    windows_show_display_next_cmd_callback,
    NULL },

  { "windows-show-display-previous", NULL,
    NC_("windows-action", "Previous Image"), NULL, { "<alt><shift>Tab", "Back", NULL },
    NC_("windows-action", "Switch to the previous image"),
    windows_show_display_previous_cmd_callback,
    NULL },

  { "windows-tab-position",        NULL, NC_("windows-action",
                                             "_Tabs Position")   },
};

static const PikaToggleActionEntry windows_toggle_actions[] =
{
  { "windows-hide-docks", NULL,
    NC_("windows-action", "_Hide Docks"), NULL, { NULL },
    NC_("windows-action", "When enabled, docks and other dialogs are hidden, leaving only image windows."),
    windows_hide_docks_cmd_callback,
    FALSE,
    PIKA_HELP_WINDOWS_HIDE_DOCKS },

  { "windows-show-tabs", NULL,
    NC_("windows-action", "_Show Tabs"), NULL, { NULL },
    NC_("windows-action", "When enabled, the image tabs bar is shown."),
    windows_show_tabs_cmd_callback,
    TRUE,
    PIKA_HELP_WINDOWS_SHOW_TABS },

  { "windows-use-single-window-mode", NULL,
    NC_("windows-action", "Single-Window _Mode"), NULL, { NULL },
    NC_("windows-action", "When enabled, PIKA is in a single-window mode."),
    windows_use_single_window_mode_cmd_callback,
    TRUE,
    PIKA_HELP_WINDOWS_USE_SINGLE_WINDOW_MODE }
};

static const PikaRadioActionEntry windows_tabs_position_actions[] =
{
  { "windows-tabs-position-top", PIKA_ICON_GO_TOP,
    NC_("windows-tabs-position-action", "_Top"), NULL, { NULL },
    NC_("windows-tabs-position-action", "Position the tabs on the top"),
    PIKA_POSITION_TOP, PIKA_HELP_WINDOWS_TABS_POSITION },

  { "windows-tabs-position-bottom", PIKA_ICON_GO_BOTTOM,
    NC_("windows-tabs-position-action", "_Bottom"), NULL, { NULL },
    NC_("windows-tabs-position-action", "Position the tabs on the bottom"),
    PIKA_POSITION_BOTTOM, PIKA_HELP_WINDOWS_TABS_POSITION },

  { "windows-tabs-position-left", PIKA_ICON_GO_FIRST,
    NC_("windows-tabs-position-action", "_Left"), NULL, { NULL },
    NC_("windows-tabs-position-action", "Position the tabs on the left"),
    PIKA_POSITION_LEFT, PIKA_HELP_WINDOWS_TABS_POSITION },

  { "windows-tabs-position-right", PIKA_ICON_GO_LAST,
    NC_("windows-tabs-position-action", "_Right"), NULL, { NULL },
    NC_("windows-tabs-position-action", "Position the tabs on the right"),
    PIKA_POSITION_RIGHT, PIKA_HELP_WINDOWS_TABS_POSITION },
};

void
windows_actions_setup (PikaActionGroup *group)
{
  GList *list;

  pika_action_group_add_actions (group, "windows-action",
                                 windows_actions,
                                 G_N_ELEMENTS (windows_actions));

  pika_action_group_add_toggle_actions (group, "windows-action",
                                        windows_toggle_actions,
                                        G_N_ELEMENTS (windows_toggle_actions));

  pika_action_group_add_radio_actions (group, "windows-tabs-position-action",
                                       windows_tabs_position_actions,
                                       G_N_ELEMENTS (windows_tabs_position_actions),
                                       NULL, 0,
                                       windows_set_tabs_position_cmd_callback);

  g_signal_connect_object (group->pika->displays, "add",
                           G_CALLBACK (windows_actions_display_add),
                           group, 0);
  g_signal_connect_object (group->pika->displays, "remove",
                           G_CALLBACK (windows_actions_display_remove),
                           group, 0);
  g_signal_connect_object (group->pika->displays, "reorder",
                           G_CALLBACK (windows_actions_display_reorder),
                           group, 0);

  for (list = pika_get_display_iter (group->pika);
       list;
       list = g_list_next (list))
    {
      PikaDisplay *display = list->data;

      windows_actions_display_add (group->pika->displays, display, group);
    }

  g_signal_connect_object (pika_dialog_factory_get_singleton (), "dock-window-added",
                           G_CALLBACK (windows_actions_dock_window_added),
                           group, 0);
  g_signal_connect_object (pika_dialog_factory_get_singleton (), "dock-window-removed",
                           G_CALLBACK (windows_actions_dock_window_removed),
                           group, 0);

  for (list = pika_dialog_factory_get_open_dialogs (pika_dialog_factory_get_singleton ());
       list;
       list = g_list_next (list))
    {
      PikaDockWindow *dock_window = list->data;

      if (PIKA_IS_DOCK_WINDOW (dock_window))
        windows_actions_dock_window_added (pika_dialog_factory_get_singleton (),
                                           dock_window,
                                           group);
    }

  g_signal_connect_object (global_recent_docks, "add",
                           G_CALLBACK (windows_actions_recent_add),
                           group, 0);
  g_signal_connect_object (global_recent_docks, "remove",
                           G_CALLBACK (windows_actions_recent_remove),
                           group, 0);

  for (list = PIKA_LIST (global_recent_docks)->queue->head;
       list;
       list = g_list_next (list))
    {
      PikaSessionInfo *info = list->data;

      windows_actions_recent_add (global_recent_docks, info, group);
    }

  g_signal_connect_object (group->pika->config, "notify::single-window-mode",
                           G_CALLBACK (windows_actions_single_window_mode_notify),
                           group, 0);
}

void
windows_actions_update (PikaActionGroup *group,
                        gpointer         data)
{
  PikaGuiConfig *config = PIKA_GUI_CONFIG (group->pika->config);
  const gchar   *action = NULL;

#define SET_ACTIVE(action,condition) \
        pika_action_group_set_action_active (group, action, (condition) != 0)

  SET_ACTIVE ("windows-use-single-window-mode", config->single_window_mode);
  SET_ACTIVE ("windows-hide-docks", config->hide_docks);
  SET_ACTIVE ("windows-show-tabs", config->show_tabs);

  switch (config->tabs_position)
    {
    case PIKA_POSITION_TOP:
      action = "windows-tabs-position-top";
      break;
    case PIKA_POSITION_BOTTOM:
      action = "windows-tabs-position-bottom";
      break;
    case PIKA_POSITION_LEFT:
      action = "windows-tabs-position-left";
      break;
    case PIKA_POSITION_RIGHT:
      action = "windows-tabs-position-right";
      break;
    default:
      action = "windows-tabs-position-top";
      break;
    }

  pika_action_group_set_action_active (group, action, TRUE);
  pika_action_group_set_action_sensitive (group, "windows-tab-position", config->single_window_mode,
                                          _("Single-window mode disabled"));
  pika_action_group_set_action_sensitive (group, "windows-show-tabs", config->single_window_mode,
                                          _("Single-window mode disabled"));

#undef SET_ACTIVE
}

gchar *
windows_actions_dock_window_to_action_name (PikaDockWindow *dock_window)
{
  return g_strdup_printf ("windows-dock-%04d",
                          pika_dock_window_get_id (dock_window));
}


/*  private functions  */

static void
windows_actions_display_add (PikaContainer   *container,
                             PikaDisplay     *display,
                             PikaActionGroup *group)
{
  PikaDisplayShell *shell = pika_display_get_shell (display);

  g_signal_connect_object (display, "notify::image",
                           G_CALLBACK (windows_actions_image_notify),
                           group, 0);

  g_signal_connect_object (shell, "notify::title",
                           G_CALLBACK (windows_actions_title_notify),
                           group, 0);

  windows_actions_image_notify (display, NULL, group);
}

static void
windows_actions_display_remove (PikaContainer   *container,
                                PikaDisplay     *display,
                                PikaActionGroup *group)
{
  PikaDisplayShell *shell = pika_display_get_shell (display);
  PikaAction       *action;
  gchar            *action_name;

  if (shell)
    g_signal_handlers_disconnect_by_func (shell,
                                          windows_actions_title_notify,
                                          group);

  action_name = pika_display_get_action_name (display);
  action = pika_action_group_get_action (group, action_name);
  g_free (action_name);

  if (action)
    pika_action_group_remove_action_and_accel (group, action);

  windows_actions_update_display_accels (group);
}

static void
windows_actions_display_reorder (PikaContainer   *container,
                                 PikaDisplay     *display,
                                 gint             new_index,
                                 PikaActionGroup *group)
{
  windows_actions_update_display_accels (group);
}

static void
windows_actions_image_notify (PikaDisplay      *display,
                              const GParamSpec *unused,
                              PikaActionGroup  *group)
{
  PikaImage  *image = pika_display_get_image (display);
  PikaAction *action;
  gchar      *action_name;

  action_name = pika_display_get_action_name (display);

  action = pika_action_group_get_action (group, action_name);

  if (! action)
    {
      PikaActionEntry entry = { 0 };

      entry.name        = action_name;
      entry.icon_name   = PIKA_ICON_IMAGE;
      entry.label       = "";
      entry.tooltip     = NULL;
      entry.callback    = windows_show_display_cmd_callback;
      entry.help_id     = NULL;

      pika_action_group_add_actions (group, NULL, &entry, 1);

      action = pika_action_group_get_action (group, action_name);

      g_object_set_data (G_OBJECT (action), "display", display);
    }

  g_free (action_name);

  if (image)
    {
      const gchar *display_name;
      gchar       *escaped;
      gchar       *title;

      display_name = pika_image_get_display_name (image);
      escaped = pika_escape_uline (display_name);

      /* TRANSLATORS: label for an action allowing to show (i.e. raise the image
       * tab or window above others) specific images or views of image. The part
       * between quotes is the image name and other view identifiers.
       */
      title = g_strdup_printf (_("Show \"%s-%d.%d\""), escaped,
                               pika_image_get_id (image),
                               pika_display_get_instance (display));

      g_object_set (action,
                    "visible",     TRUE,
                    "label",       title,
                    "short-label", escaped,
                    "tooltip",     pika_image_get_display_path (image),
                    "viewable",    image,
                    NULL);

      g_free (title);
      g_free (escaped);

      windows_actions_update_display_accels (group);
    }
  else
    {
      g_object_set (action,
                    "visible",  FALSE,
                    "viewable", NULL,
                    NULL);
    }
}

static void
windows_actions_title_notify (PikaDisplayShell *shell,
                              const GParamSpec *unused,
                              PikaActionGroup  *group)
{
  windows_actions_image_notify (shell->display, NULL, group);
}

static void
windows_actions_update_display_accels (PikaActionGroup *group)
{
  GList *list;
  gint   i;

  for (list = pika_get_display_iter (group->pika), i = 0;
       list && i < 10;
       list = g_list_next (list), i++)
    {
      PikaDisplay *display = list->data;
      PikaImage   *image   = pika_display_get_image (display);
      PikaAction  *action;
      gchar       *action_name;

      if (image == NULL)
        break;

      action_name = pika_display_get_action_name (display);

      action = pika_action_group_get_action (group, action_name);
      g_free (action_name);

      if (action)
        {
          const gchar *ntooltip;
          gchar       *tooltip;
          gchar       *accel;

          if (i < 9)
            accel = gtk_accelerator_name (GDK_KEY_1 + i, GDK_MOD1_MASK);
          else
            accel = gtk_accelerator_name (GDK_KEY_0 + i, GDK_MOD1_MASK);

          pika_action_set_accels (action, (const gchar*[]) { accel, NULL });
          g_free (accel);

          /* TRANSLATORS: the first argument (%1$s) is the image name, the
           * second (%2$d) is its tab order in the graphical interface.
           */
          ntooltip = ngettext ("Switch to the first image view: %1$s",
                               "Switch to image view %2$d: %1$s",
                               i + 1);
          tooltip = g_strdup_printf (ntooltip, pika_image_get_display_path (image), i + 1);
          pika_action_set_tooltip (action, tooltip);
          g_free (tooltip);
        }
    }
}

static void
windows_actions_dock_window_added (PikaDialogFactory *factory,
                                   PikaDockWindow    *dock_window,
                                   PikaActionGroup   *group)
{
  PikaAction      *action;
  PikaActionEntry  entry       = { 0 };
  gchar           *action_name = windows_actions_dock_window_to_action_name (dock_window);

  entry.name        = action_name;
  entry.icon_name   = NULL;
  entry.label       = "";
  entry.tooltip     = NULL;
  entry.callback    = windows_show_dock_cmd_callback;
  entry.help_id     = PIKA_HELP_WINDOWS_SHOW_DOCK;

  pika_action_group_add_actions (group, NULL, &entry, 1);

  action = pika_action_group_get_action (group, action_name);

  g_object_set (action,
                "ellipsize", PANGO_ELLIPSIZE_END,
                NULL);

  g_object_set_data (G_OBJECT (action), "dock-window", dock_window);

  g_free (action_name);

  g_signal_connect_object (dock_window, "notify::title",
                           G_CALLBACK (windows_actions_dock_window_notify),
                           group, 0);

  if (gtk_window_get_title (GTK_WINDOW (dock_window)))
    windows_actions_dock_window_notify (dock_window, NULL, group);
}

static void
windows_actions_dock_window_removed (PikaDialogFactory *factory,
                                     PikaDockWindow    *dock_window,
                                     PikaActionGroup   *group)
{
  PikaAction *action;
  gchar      *action_name;

  action_name = windows_actions_dock_window_to_action_name (dock_window);
  action = pika_action_group_get_action (group, action_name);
  g_free (action_name);

  if (action)
    pika_action_group_remove_action_and_accel (group, action);
}

static void
windows_actions_dock_window_notify (PikaDockWindow   *dock_window,
                                    const GParamSpec *pspec,
                                    PikaActionGroup  *group)
{
  PikaAction *action;
  gchar      *action_name;

  action_name = windows_actions_dock_window_to_action_name (dock_window);
  action = pika_action_group_get_action (group, action_name);
  g_free (action_name);

  if (action)
    g_object_set (action,
                  "label",   gtk_window_get_title (GTK_WINDOW (dock_window)),
                  "tooltip", gtk_window_get_title (GTK_WINDOW (dock_window)),
                  NULL);
}

static void
windows_actions_recent_add (PikaContainer   *container,
                            PikaSessionInfo *info,
                            PikaActionGroup *group)
{
  PikaAction      *action;
  PikaActionEntry  entry = { 0 };
  gint             info_id;
  static gint      info_id_counter = 1;
  gchar           *action_name;

  info_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (info),
                                                "recent-action-id"));

  if (! info_id)
    {
      info_id = info_id_counter++;

      g_object_set_data (G_OBJECT (info), "recent-action-id",
                         GINT_TO_POINTER (info_id));
    }

  action_name = g_strdup_printf ("windows-recent-%04d", info_id);

  entry.name        = action_name;
  entry.icon_name   = NULL;
  entry.label       = pika_object_get_name (info);
  entry.tooltip     = pika_object_get_name (info);
  entry.callback    = windows_open_recent_cmd_callback;
  entry.help_id     = PIKA_HELP_WINDOWS_OPEN_RECENT_DOCK;

  pika_action_group_add_actions (group, NULL, &entry, 1);

  action = pika_action_group_get_action (group, action_name);

  g_object_set (action,
                "ellipsize",       PANGO_ELLIPSIZE_END,
                "max-width-chars", 30,
                NULL);

  g_object_set_data (G_OBJECT (action), "info", info);

  g_free (action_name);
}

static void
windows_actions_recent_remove (PikaContainer   *container,
                               PikaSessionInfo *info,
                               PikaActionGroup *group)
{
  PikaAction *action;
  gint        info_id;
  gchar      *action_name;

  info_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (info),
                                                "recent-action-id"));

  action_name = g_strdup_printf ("windows-recent-%04d", info_id);
  action = pika_action_group_get_action (group, action_name);
  g_free (action_name);

  if (action)
    pika_action_group_remove_action_and_accel (group, action);
}

static void
windows_actions_single_window_mode_notify (PikaDisplayConfig *config,
                                           GParamSpec        *pspec,
                                           PikaActionGroup   *group)
{
  pika_action_group_set_action_active (group,
                                       "windows-use-single-window-mode",
                                       PIKA_GUI_CONFIG (config)->single_window_mode);
}
