/* 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
 *
 * Screenshot plug-in
 * Copyright 1998-2007 Sven Neumann <sven@gimp.org>
 * Copyright 2003      Henrik Brix Andersen <brix@gimp.org>
 * Copyright 2012      Simone Karin Lehmann - OS X patches
 * Copyright 2016      Michael Natterer <mitch@gimp.org>
 *
 * 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 <libpika/pika.h>
#include <libpika/pikaui.h>

#include "screenshot.h"
#include "screenshot-freedesktop.h"
#include "screenshot-osx.h"
#include "screenshot-x11.h"
#include "screenshot-win32.h"

#include "libpika/stdplugins-intl.h"


/* Defines */

#define PLUG_IN_PROC   "plug-in-screenshot"
#define PLUG_IN_BINARY "screenshot"
#define PLUG_IN_ROLE   "pika-screenshot"

typedef struct _Screenshot      Screenshot;
typedef struct _ScreenshotClass ScreenshotClass;

struct _Screenshot
{
  PikaPlugIn      parent_instance;
};

struct _ScreenshotClass
{
  PikaPlugInClass parent_class;
};

#define SCREENSHOT_TYPE  (screenshot_get_type ())
#define SCREENSHOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SCREENSHOT_TYPE, Screenshot))

GType                   screenshot_get_type         (void) G_GNUC_CONST;

static GList          * screenshot_query_procedures (PikaPlugIn           *plug_in);
static PikaProcedure  * screenshot_create_procedure (PikaPlugIn           *plug_in,
                                                     const gchar          *name);

static PikaValueArray * screenshot_run              (PikaProcedure        *procedure,
                                                     PikaProcedureConfig  *config,
                                                     gpointer              run_data);

static PikaPDBStatusType   shoot               (GdkMonitor           *monitor,
                                                PikaImage           **image,
                                                PikaProcedureConfig  *config,
                                                GError              **error);

static gboolean            shoot_dialog        (PikaProcedure        *procedure,
                                                PikaProcedureConfig  *config,
                                                GdkMonitor          **monitor);
static gboolean            shoot_quit_timeout  (gpointer              data);
static gboolean            shoot_delay_timeout (gpointer              data);


G_DEFINE_TYPE (Screenshot, screenshot, PIKA_TYPE_PLUG_IN)

PIKA_MAIN (SCREENSHOT_TYPE)
DEFINE_STD_SET_I18N


static ScreenshotBackend       backend      = SCREENSHOT_BACKEND_NONE;
static ScreenshotCapabilities  capabilities = 0;

static void
screenshot_class_init (ScreenshotClass *klass)
{
  PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = screenshot_query_procedures;
  plug_in_class->create_procedure = screenshot_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
screenshot_init (Screenshot *screenshot)
{
}

static GList *
screenshot_query_procedures (PikaPlugIn *plug_in)
{
  return g_list_append (NULL, g_strdup (PLUG_IN_PROC));
}

static PikaProcedure *
screenshot_create_procedure (PikaPlugIn  *plug_in,
                             const gchar *name)
{
  PikaProcedure *procedure = NULL;

  if (! strcmp (name, PLUG_IN_PROC))
    {
      procedure = pika_procedure_new (plug_in, name,
                                      PIKA_PDB_PROC_TYPE_PLUGIN,
                                      screenshot_run, NULL, NULL);

      pika_procedure_set_menu_label (procedure, _("_Screenshot..."));
      pika_procedure_add_menu_path (procedure, "<Image>/File/Create");

      pika_procedure_set_documentation
        (procedure,
         _("Create an image from an area of the screen"),
         "The plug-in takes screenshots of an "
         "interactively selected window or of the desktop, "
         "either the whole desktop or an interactively "
         "selected region. When called non-interactively, it "
         "may grab the root window or use the window-id "
         "passed as a parameter.  The last four parameters "
         "are optional and can be used to specify the corners "
         "of the region to be grabbed."
         "On Mac OS X, "
         "when called non-interactively, the plug-in"
         "only can take screenshots of the entire root window."
         "Grabbing a window or a region is not supported"
         "non-interactively. To grab a region or a particular"
         "window, you need to use the interactive mode.",
         name);

      pika_procedure_set_attribution (procedure,
                                      "Sven Neumann <sven@gimp.org>, "
                                      "Henrik Brix Andersen <brix@gimp.org>,"
                                      "Simone Karin Lehmann",
                                      "1998 - 2008",
                                      "v1.1 (2008/04)");

      pika_procedure_set_icon_pixbuf (procedure,
                                      gdk_pixbuf_new_from_resource ("/technology.heckin/screenshot-icons/screenshot-icon.png", NULL));

      PIKA_PROC_ARG_ENUM (procedure, "run-mode",
                          "Run mode",
                          "The run mode",
                          PIKA_TYPE_RUN_MODE,
                          PIKA_RUN_NONINTERACTIVE,
                          G_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "shoot-type",
                         _("Shoot _area"),
                         "The shoot type { SHOOT-WINDOW (0), SHOOT-ROOT (1), "
                         "SHOOT-REGION (2) }",
                         0, 2, SHOOT_WINDOW,
                         G_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "x1",
                         "X1",
                         "Region left x coord for SHOOT-WINDOW",
                         G_MININT, G_MAXINT, 0,
                         G_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "y1",
                         "Y1",
                         "Region top y coord for SHOOT-WINDOW",
                         G_MININT, G_MAXINT, 0,
                         G_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "x2",
                         "X2",
                         "Region right x coord for SHOOT-WINDOW",
                         G_MININT, G_MAXINT, 0,
                         G_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "y2",
                         "Y2",
                         "Region bottom y coord for SHOOT-WINDOW",
                         G_MININT, G_MAXINT, 0,
                         G_PARAM_READWRITE);

      PIKA_PROC_ARG_BOOLEAN (procedure, "include-pointer",
                             "Include _mouse pointer",
                             "Your pointing device's cursor will be part of the image",
                             FALSE,
                             G_PARAM_READWRITE);

      /* Since no backends allow window screenshot non-interactively so far, no
       * need to expose this argument to the API.
       */
      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "include-decoration",
                                 "Include window _decoration",
                                 "Title bar, window borders and shadow will be part of the image",
#ifdef PLATFORM_OSX
                                 /* on OS X, this just means shoot the shadow, default to nope */
                                 FALSE,
#else
                                 TRUE,
#endif
                                 G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_INT     (procedure, "selection-delay",
                                 "Selection d_elay",
                                 "Delay before selection of the window or the region",
                                 0, 20, 0,
                                 G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_INT     (procedure, "screenshot-delay",
                                 "Screenshot dela_y",
                                 "Delay before snapping the screenshot",
                                 0, 20, 0,
                                 G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_INT     (procedure, "color-profile",
                                 _("Color _Profile"),
                                 "{ SCREENSHOT_PROFILE_POLICY_MONITOR, (0), "
                                 "SCREENSHOT_PROFILE_POLICY_MONITOR, (1) } ",
                                 0, 1, SCREENSHOT_PROFILE_POLICY_MONITOR,
                                 G_PARAM_READWRITE);

      PIKA_PROC_VAL_IMAGE (procedure, "image",
                           "Image",
                           "Output image",
                           FALSE,
                           G_PARAM_READWRITE);
    }

  return procedure;
}

static PikaValueArray *
screenshot_run (PikaProcedure        *procedure,
                PikaProcedureConfig  *config,
                gpointer              run_data)
{
  PikaValueArray    *return_vals;
  PikaPDBStatusType  status = PIKA_PDB_SUCCESS;
  PikaRunMode        run_mode;
  GdkMonitor        *monitor = NULL;
  PikaImage         *image   = NULL;
  GError            *error   = NULL;

  ShootType               shoot_type;
  ScreenshotProfilePolicy profile_policy;

  gegl_init (NULL, NULL);

  g_object_get (config,
                "run-mode",   &run_mode,
                "shoot-type", &shoot_type,
                NULL);

  if (! gdk_init_check (NULL, NULL))
    {
      g_set_error_literal (&error, PIKA_PLUG_IN_ERROR, 0, _("GDK initialization failed."));
      return pika_procedure_new_return_values (procedure,
                                               PIKA_PDB_EXECUTION_ERROR,
                                               error);
    }

#ifdef PLATFORM_OSX
  if (! backend && screenshot_osx_available ())
    {
      backend      = SCREENSHOT_BACKEND_OSX;
      capabilities = screenshot_osx_get_capabilities ();
    }
#endif

#ifdef G_OS_WIN32
  if (! backend && screenshot_win32_available ())
    {
      backend      = SCREENSHOT_BACKEND_WIN32;
      capabilities = screenshot_win32_get_capabilities ();
    }
#endif

#ifdef GDK_WINDOWING_X11
  if (! backend && screenshot_x11_available ())
    {
      backend      = SCREENSHOT_BACKEND_X11;
      capabilities = screenshot_x11_get_capabilities ();
    }
#endif
  if (! backend && screenshot_freedesktop_available ())
    {
      backend      = SCREENSHOT_BACKEND_FREEDESKTOP;
      capabilities = screenshot_freedesktop_get_capabilities ();
    }

  if (backend == SCREENSHOT_BACKEND_NONE)
    {
      g_set_error_literal (&error, PIKA_PLUG_IN_ERROR, 0,
                           _("No screenshot backends available."));
      return pika_procedure_new_return_values (procedure,
                                               PIKA_PDB_EXECUTION_ERROR,
                                               error);
    }
  /* how are we running today? */
  switch (run_mode)
    {
    case PIKA_RUN_INTERACTIVE:
      pika_ui_init (PLUG_IN_BINARY);

      /* In interactive mode, coords are always reset. */
      g_object_set (config,
                    "x1", 0, "y1", 0,
                    "x2", 0, "y2", 0,
                    NULL);

      if ((shoot_type == SHOOT_WINDOW &&
           ! (capabilities & SCREENSHOT_CAN_SHOOT_WINDOW)) ||
          (shoot_type == SHOOT_REGION &&
           ! (capabilities & SCREENSHOT_CAN_SHOOT_REGION)))
        {
          /* Shoot root is the only type of shoot which is definitely
           * shared by all screenshot backends (basically just snap the
           * whole display setup).
           */
          g_object_set (config, "shoot-type", SHOOT_ROOT, NULL);
        }

      /* Get information from the dialog. Freedesktop portal comes with
       * its own dialog.
       */
      if (backend != SCREENSHOT_BACKEND_FREEDESKTOP)
        {
          if (! shoot_dialog (procedure, config, &monitor))
            status = PIKA_PDB_CANCEL;
        }
      else
        {
          /* This is ugly but in reality we have no idea on which monitor
           * a screenshot was taken from with portals. It's like a
           * better-than-nothing trick for easy single-display cases.
           */
          monitor = gdk_display_get_monitor (gdk_display_get_default (), 0);
        }
      break;

    case PIKA_RUN_NONINTERACTIVE:
      if (backend == SCREENSHOT_BACKEND_FREEDESKTOP)
        {
          /* With portals, even the basic full screenshot is interactive. */
          status = PIKA_PDB_CALLING_ERROR;
        }
      else if (! (capabilities & SCREENSHOT_CAN_PICK_NONINTERACTIVELY))
        {
          if (shoot_type == SHOOT_WINDOW ||
              shoot_type == SHOOT_REGION)
            status = PIKA_PDB_CALLING_ERROR;
        }
      break;

    case PIKA_RUN_WITH_LAST_VALS:
      break;

    default:
      break;
    }

  if (status == PIKA_PDB_SUCCESS)
    status = shoot (monitor, &image, config, &error);

  if (status == PIKA_PDB_SUCCESS)
    {
      gchar *comment = pika_get_default_comment ();

      pika_image_undo_disable (image);

      g_object_get (config,
                    "color-profile", &profile_policy,
                    NULL);

      if (run_mode == PIKA_RUN_NONINTERACTIVE)
        profile_policy = SCREENSHOT_PROFILE_POLICY_MONITOR;

      if (profile_policy == SCREENSHOT_PROFILE_POLICY_SRGB)
        {
          PikaColorProfile *srgb_profile = pika_color_profile_new_rgb_srgb ();

          pika_image_convert_color_profile (image,
                                            srgb_profile,
                                            PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
                                            TRUE);
          g_object_unref (srgb_profile);
        }

      if (comment)
        {
          PikaParasite *parasite;

          parasite = pika_parasite_new ("pika-comment",
                                        PIKA_PARASITE_PERSISTENT,
                                        strlen (comment) + 1, comment);

          pika_image_attach_parasite (image, parasite);
          pika_parasite_free (parasite);

          g_free (comment);
        }

      pika_image_undo_enable (image);

      if (run_mode == PIKA_RUN_INTERACTIVE)
        {
          guint select_delay;

          pika_display_new (image);

          g_object_get (config,
                        "selection-delay", &select_delay,
                        NULL);
          /* Give some sort of feedback that the shot is done */
          if (select_delay > 0)
            {
              gdk_display_beep (gdk_monitor_get_display (monitor));
              /* flush so the beep makes it to the server */
              gdk_display_flush (gdk_monitor_get_display (monitor));
            }
        }
    }

  return_vals = pika_procedure_new_return_values (procedure, status, error);

  if (status == PIKA_PDB_SUCCESS)
    PIKA_VALUES_SET_IMAGE (return_vals, 1, image);

  return return_vals;
}


/* The main Screenshot function */

static PikaPDBStatusType
shoot (GdkMonitor           *monitor,
       PikaImage           **image,
       PikaProcedureConfig  *config,
       GError              **error)
{
  ShootType shoot_type;
  gboolean  decorate;
  guint     select_delay;
  guint     screenshot_delay;
  gboolean  show_cursor;

  g_object_get (config,
                "shoot-type",         &shoot_type,
                "screenshot-delay",   &screenshot_delay,
                "selection-delay",    &select_delay,
                "include-decoration", &decorate,
                "include-pointer",    &show_cursor,
                NULL);

#ifdef PLATFORM_OSX
  if (backend == SCREENSHOT_BACKEND_OSX)
    return screenshot_osx_shoot (shoot_type,
                                 select_delay, screenshot_delay,
                                 decorate, show_cursor,
                                 image, error);
#endif

#ifdef G_OS_WIN32
  if (backend == SCREENSHOT_BACKEND_WIN32)
    return screenshot_win32_shoot (shoot_type, screenshot_delay, show_cursor,
                                   monitor, image, error);
#endif

  if (backend == SCREENSHOT_BACKEND_FREEDESKTOP)
    return screenshot_freedesktop_shoot (monitor, image, error);

#ifdef GDK_WINDOWING_X11
  if (backend == SCREENSHOT_BACKEND_X11)
    return screenshot_x11_shoot (config, monitor, image, error);
#endif

  return PIKA_PDB_CALLING_ERROR; /* silence compiler */
}


/*  Screenshot dialog  */

static gboolean
shoot_dialog (PikaProcedure        *procedure,
              PikaProcedureConfig  *config,
              GdkMonitor          **monitor)
{
  GtkWidget      *dialog;
  GtkListStore   *store;
  PikaValueArray *values;
  GValue          value = G_VALUE_INIT;
  gboolean        run;

  dialog = pika_procedure_dialog_new (procedure,
                                      PIKA_PROCEDURE_CONFIG (config),
                                      _("Screenshot"));
  pika_procedure_dialog_set_ok_label (PIKA_PROCEDURE_DIALOG (dialog), _("S_nap"));

  store = pika_int_store_new (_("Take a screenshot of a single window"),   SHOOT_WINDOW,
                              _("Take a screenshot of the entire screen"), SHOOT_ROOT,
                              _("Select a region to grab"),                SHOOT_REGION,
                              NULL);
  pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog),
                                       "shoot-type", PIKA_INT_STORE (store));

  if (capabilities & SCREENSHOT_CAN_SHOOT_POINTER ||
      capabilities & SCREENSHOT_CAN_SHOOT_DECORATIONS)
    {
      if (! (capabilities & SCREENSHOT_CAN_SHOOT_POINTER))
        pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog),
                                        "contents-box",
                                        "include-decoration",
                                        NULL);
      else if (! (capabilities & SCREENSHOT_CAN_SHOOT_DECORATIONS))
        pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog),
                                        "contents-box",
                                        "include-pointer",
                                        NULL);
      else
        pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog),
                                        "contents-box",
                                        "include-decoration",
                                        "include-pointer",
                                        NULL);

      pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog),
                                       "contents-frame-title",
                                       _("Contents"), FALSE, FALSE);
      pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog),
                                        "contents-frame", "contents-frame-title",
                                        FALSE, "contents-box");

      if (capabilities & SCREENSHOT_CAN_SHOOT_DECORATIONS)
        {
          values = pika_value_array_new (1);
          g_value_init (&value, G_TYPE_INT);
          g_value_set_int (&value, SHOOT_WINDOW);
          pika_value_array_append (values, &value);
          g_value_unset (&value);
          pika_procedure_dialog_set_sensitive_if_in (PIKA_PROCEDURE_DIALOG (dialog),
                                                     "include-decoration",
                                                     NULL, "shoot-type",
                                                     values, TRUE);
        }
      if (capabilities & SCREENSHOT_CAN_SHOOT_POINTER)
        {
          values = pika_value_array_new (1);
          g_value_init (&value, G_TYPE_INT);
          g_value_set_int (&value, SHOOT_REGION);
          pika_value_array_append (values, &value);
          g_value_unset (&value);
          pika_procedure_dialog_set_sensitive_if_in (PIKA_PROCEDURE_DIALOG (dialog),
                                                     "include-pointer",
                                                     NULL, "shoot-type",
                                                     values, FALSE);
        }
    }

  if (capabilities & SCREENSHOT_CAN_DELAY_WINDOW_SHOT)
    {
      if (capabilities & SCREENSHOT_CAN_PICK_WINDOW)
        {
          pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog),
                                          "delay-box",
                                          "selection-delay",
                                          "screenshot-delay",
                                          NULL);

          values = pika_value_array_new (1);
          g_value_init (&value, G_TYPE_INT);
          g_value_set_int (&value, SHOOT_ROOT);
          pika_value_array_append (values, &value);
          g_value_unset (&value);
          pika_procedure_dialog_set_sensitive_if_in (PIKA_PROCEDURE_DIALOG (dialog),
                                                     "selection-delay",
                                                     NULL, "shoot-type",
                                                     values, FALSE);
        }
      else
        {
          pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog),
                                          "delay-box", "screenshot-delay", NULL);
        }

      pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog),
                                       "delay-frame-title",
                                       _("Delay"), FALSE, FALSE);
      pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog),
                                        "delay-frame", "delay-frame-title",
                                        FALSE, "delay-box");
    }

  pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "shoot-type", NULL);

  store = pika_int_store_new (_("Tag image with monitor profile"), SCREENSHOT_PROFILE_POLICY_MONITOR,
                              _("Convert image with sRGB"),        SCREENSHOT_PROFILE_POLICY_SRGB,
                              NULL);
  pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog),
                                       "color-profile", PIKA_INT_STORE (store));

  if ((capabilities & SCREENSHOT_CAN_SHOOT_POINTER) ||
      (capabilities & SCREENSHOT_CAN_SHOOT_DECORATIONS))
    pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "contents-frame", NULL);

  if (capabilities & SCREENSHOT_CAN_DELAY_WINDOW_SHOT)
    pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "delay-frame", NULL);

  pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "color-profile", NULL);
  run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));

  gtk_widget_destroy (dialog);

  if (run)
    {
      /*  A short timeout to give the server a chance to
       *  redraw the area that was obscured by our dialog.
       */
      g_timeout_add (100, shoot_quit_timeout, NULL);
      gtk_main ();
    }

  return run;
}

static gboolean
shoot_quit_timeout (gpointer data)
{
  gtk_main_quit ();

  return FALSE;
}


static gboolean
shoot_delay_timeout (gpointer data)
{
  gint *seconds_left = data;

  (*seconds_left)--;

  if (!*seconds_left)
    gtk_main_quit ();

  return *seconds_left;
}


/*  public functions  */

void
screenshot_wait_delay (gint seconds)
{
  g_timeout_add (1000, shoot_delay_timeout, &seconds);
  gtk_main ();
}
