/* LIBPIKA - The PIKA Library
 * Copyright (C) 1995-2000 Peter Mattis and Spencer Kimball
 *
 * pikaimage.c
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "pika.h"

#include "libpikabase/pikawire.h" /* FIXME kill this include */

#include "pikapixbuf.h"
#include "pikaplugin-private.h"
#include "pikaprocedure-private.h"


enum
{
  PROP_0,
  PROP_ID,
  N_PROPS
};

struct _PikaImage
{
  GObject parent_instance;
  gint    id;
};


static void   pika_image_set_property  (GObject      *object,
                                        guint         property_id,
                                        const GValue *value,
                                        GParamSpec   *pspec);
static void   pika_image_get_property  (GObject      *object,
                                        guint         property_id,
                                        GValue       *value,
                                        GParamSpec   *pspec);


G_DEFINE_TYPE (PikaImage, pika_image, G_TYPE_OBJECT)

#define parent_class pika_image_parent_class

static GParamSpec *props[N_PROPS] = { NULL, };


static void
pika_image_class_init (PikaImageClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->set_property = pika_image_set_property;
  object_class->get_property = pika_image_get_property;

  props[PROP_ID] =
    g_param_spec_int ("id",
                      "The image id",
                      "The image id for internal use",
                      0, G_MAXINT32, 0,
                      PIKA_PARAM_READWRITE |
                      G_PARAM_CONSTRUCT_ONLY);

  g_object_class_install_properties (object_class, N_PROPS, props);
}

static void
pika_image_init (PikaImage *image)
{
}

static void
pika_image_set_property (GObject      *object,
                         guint         property_id,
                         const GValue *value,
                         GParamSpec   *pspec)
{
  PikaImage *image = PIKA_IMAGE (object);

  switch (property_id)
    {
    case PROP_ID:
      image->id = g_value_get_int (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_image_get_property (GObject    *object,
                         guint       property_id,
                         GValue     *value,
                         GParamSpec *pspec)
{
  PikaImage *image = PIKA_IMAGE (object);

  switch (property_id)
    {
    case PROP_ID:
      g_value_set_int (value, image->id);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}


/* Public API */

/**
 * pika_image_get_id:
 * @image: The image.
 *
 * Returns: the image ID.
 *
 * Since: 3.0
 **/
gint32
pika_image_get_id (PikaImage *image)
{
  return image ? image->id : -1;
}

/**
 * pika_image_get_by_id:
 * @image_id: The image id.
 *
 * Returns: (nullable) (transfer none): a #PikaImage for @image_id or
 *          %NULL if @image_id does not represent a valid image.
 *          The object belongs to libpika and you must not modify
 *          or unref it.
 *
 * Since: 3.0
 **/
PikaImage *
pika_image_get_by_id (gint32 image_id)
{
  if (image_id > 0)
    {
      PikaPlugIn    *plug_in   = pika_get_plug_in ();
      PikaProcedure *procedure = _pika_plug_in_get_procedure (plug_in);

      return _pika_procedure_get_image (procedure, image_id);
    }

  return NULL;
}

/**
 * pika_image_is_valid:
 * @image: The image to check.
 *
 * Returns TRUE if the image is valid.
 *
 * This procedure checks if the given image is valid and refers to
 * an existing image.
 *
 * Returns: Whether the image is valid.
 *
 * Since: 2.4
 **/
gboolean
pika_image_is_valid (PikaImage *image)
{
  return pika_image_id_is_valid (pika_image_get_id (image));
}

/**
 * pika_list_images:
 *
 * Returns the list of images currently open.
 *
 * This procedure returns the list of images currently open in PIKA.
 *
 * Returns: (element-type PikaImage) (transfer container):
 *          The list of images currently open.
 *          The returned list must be freed with g_list_free(). Image
 *          elements belong to libpika and must not be freed.
 *
 * Since: 3.0
 **/
GList *
pika_list_images (void)
{
  PikaImage **images;
  gint        num_images;
  GList      *list = NULL;
  gint        i;

  images = pika_get_images (&num_images);

  for (i = 0; i < num_images; i++)
    list = g_list_prepend (list, images[i]);

  g_free (images);

  return g_list_reverse (list);
}

/**
 * pika_image_list_layers:
 * @image: The image.
 *
 * Returns the list of layers contained in the specified image.
 *
 * This procedure returns the list of layers contained in the specified
 * image. The order of layers is from topmost to bottommost.
 *
 * Returns: (element-type PikaLayer) (transfer container):
 *          The list of layers contained in the image.
 *          The returned list must be freed with g_list_free(). Layer
 *          elements belong to libpika and must not be freed.
 *
 * Since: 3.0
 **/
GList *
pika_image_list_layers (PikaImage *image)
{
  PikaLayer **layers;
  gint        num_layers;
  GList      *list = NULL;
  gint        i;

  layers = pika_image_get_layers (image, &num_layers);

  for (i = 0; i < num_layers; i++)
    list = g_list_prepend (list, layers[i]);

  g_free (layers);

  return g_list_reverse (list);
}

/**
 * pika_image_list_selected_layers:
 * @image: The image.
 *
 * Returns the list of layers selected in the specified image.
 *
 * This procedure returns the list of layers selected in the specified
 * image.
 *
 * Returns: (element-type PikaLayer) (transfer container):
 *          The list of selected layers in the image.
 *          The returned list must be freed with g_list_free(). Layer
 *          elements belong to libpika and must not be freed.
 *
 * Since: 3.0
 **/
GList *
pika_image_list_selected_layers (PikaImage *image)
{
  PikaLayer **layers;
  gint        num_layers;
  GList      *list = NULL;
  gint        i;

  layers = pika_image_get_selected_layers (image, &num_layers);

  for (i = 0; i < num_layers; i++)
    list = g_list_prepend (list, layers[i]);

  g_free (layers);

  return g_list_reverse (list);
}

/**
 * pika_image_take_selected_layers:
 * @image: The image.
 * @layers: (transfer container) (element-type PikaLayer): The list of layers to select.
 *
 * The layers are set as the selected layers in the image. Any previous
 * selected layers or channels are unselected. An exception is a previously
 * existing floating selection, in which case this procedure will return an
 * execution error.
 *
 * Returns: TRUE on success.
 *
 * Since: 3.0
 **/
gboolean
pika_image_take_selected_layers (PikaImage *image,
                                 GList     *layers)
{
  PikaLayer **sel_layers;
  GList      *list;
  gboolean    success;
  gint        i;

  sel_layers = g_new0 (PikaLayer *, g_list_length (layers));
  for (list = layers, i = 0; list; list = list->next, i++)
    sel_layers[i] = list->data;

  success = pika_image_set_selected_layers (image, g_list_length (layers),
                                            (const PikaLayer **) sel_layers);
  g_list_free (layers);
  g_free (sel_layers);

  return success;
}

/**
 * pika_image_list_selected_channels:
 * @image: The image.
 *
 * Returns the list of channels selected in the specified image.
 *
 * This procedure returns the list of channels selected in the specified
 * image.
 *
 * Returns: (element-type PikaChannel) (transfer container):
 *          The list of selected channels in the image.
 *          The returned list must be freed with g_list_free(). Layer
 *          elements belong to libpika and must not be freed.
 *
 * Since: 3.0
 **/
GList *
pika_image_list_selected_channels (PikaImage *image)
{
  PikaChannel **channels;
  gint          num_channels;
  GList        *list = NULL;
  gint          i;

  channels = pika_image_get_selected_channels (image, &num_channels);

  for (i = 0; i < num_channels; i++)
    list = g_list_prepend (list, channels[i]);

  g_free (channels);

  return g_list_reverse (list);
}

/**
 * pika_image_take_selected_channels:
 * @image: The image.
 * @channels: (transfer container) (element-type PikaChannel): The list of channels to select.
 *
 * The channels are set as the selected channels in the image. Any previous
 * selected layers or channels are unselected. An exception is a previously
 * existing floating selection, in which case this procedure will return an
 * execution error.
 *
 * Returns: TRUE on success.
 *
 * Since: 3.0
 **/
gboolean
pika_image_take_selected_channels (PikaImage *image,
                                   GList     *channels)
{
  PikaChannel **sel_channels;
  GList        *list;
  gboolean      success;
  gint          i;

  sel_channels = g_new0 (PikaChannel *, g_list_length (channels));
  for (list = channels, i = 0; list; list = list->next, i++)
    sel_channels[i] = list->data;

  success = pika_image_set_selected_channels (image, g_list_length (channels),
                                              (const PikaChannel **) sel_channels);
  g_list_free (channels);

  return success;
}

/**
 * pika_image_list_selected_vectors:
 * @image: The image.
 *
 * Returns the list of paths selected in the specified image.
 *
 * This procedure returns the list of paths selected in the specified
 * image.
 *
 * Returns: (element-type PikaVectors) (transfer container):
 *          The list of selected paths in the image.
 *          The returned list must be freed with g_list_free().
 *          Path elements belong to libpika and must not be freed.
 *
 * Since: 3.0
 **/
GList *
pika_image_list_selected_vectors (PikaImage *image)
{
  PikaVectors **vectors;
  gint          num_vectors;
  GList        *list = NULL;
  gint          i;

  vectors = pika_image_get_selected_vectors (image, &num_vectors);

  for (i = 0; i < num_vectors; i++)
    list = g_list_prepend (list, vectors[i]);

  g_free (vectors);

  return g_list_reverse (list);
}

/**
 * pika_image_take_selected_vectors:
 * @image: The image.
 * @vectors: (transfer container) (element-type PikaVectors): The list of paths to select.
 *
 * The paths are set as the selected paths in the image. Any previous
 * selected paths are unselected.
 *
 * Returns: TRUE on success.
 *
 * Since: 3.0
 **/
gboolean
pika_image_take_selected_vectors (PikaImage *image,
                                  GList     *vectors)
{
  PikaVectors **sel_vectors;
  GList        *list;
  gboolean      success;
  gint          i;

  sel_vectors = g_new0 (PikaVectors *, g_list_length (vectors));
  for (list = vectors, i = 0; list; list = list->next, i++)
    sel_vectors[i] = list->data;

  success = pika_image_set_selected_vectors (image, g_list_length (vectors),
                                             (const PikaVectors **) sel_vectors);
  g_list_free (vectors);

  return success;
}

/**
 * pika_image_list_channels:
 * @image: The image.
 *
 * Returns the list of channels contained in the specified image.
 *
 * This procedure returns the list of channels contained in the
 * specified image. This does not include the selection mask, or layer
 * masks. The order is from topmost to bottommost. Note that
 * "channels" are custom channels and do not include the image's
 * color components.
 *
 * Returns: (element-type PikaChannel) (transfer container):
 *          The list of channels contained in the image.
 *          The returned list must be freed with g_list_free(). Channel
 *          elements belong to libpika and must not be freed.
 *
 * Since: 3.0
 **/
GList *
pika_image_list_channels (PikaImage *image)
{
  PikaChannel **channels;
  gint          num_channels;
  GList        *list = NULL;
  gint          i;

  channels = pika_image_get_channels (image, &num_channels);

  for (i = 0; i < num_channels; i++)
    list = g_list_prepend (list, channels[i]);

  g_free (channels);

  return g_list_reverse (list);
}

/**
 * pika_image_list_vectors:
 * @image: The image.
 *
 * Returns the list of vectors contained in the specified image.
 *
 * This procedure returns the list of vectors contained in the
 * specified image.
 *
 * Returns: (element-type PikaVectors) (transfer container):
 *          The list of vectors contained in the image.
 *          The returned value must be freed with g_list_free(). Vectors
 *          elements belong to libpika and must not be freed.
 *
 * Since: 3.0
 **/
GList *
pika_image_list_vectors (PikaImage *image)
{
  PikaVectors **vectors;
  gint          num_vectors;
  GList        *list = NULL;
  gint          i;

  vectors = pika_image_get_vectors (image, &num_vectors);

  for (i = 0; i < num_vectors; i++)
    list = g_list_prepend (list, vectors[i]);

  g_free (vectors);

  return g_list_reverse (list);
}

/**
 * pika_image_list_selected_drawables:
 * @image: The image.
 *
 * Returns the list of drawables selected in the specified image.
 *
 * This procedure returns the list of drawables selected in the specified
 * image.
 * These can be either a list of layers or a list of channels (a list mixing
 * layers and channels is not possible), or it can be a layer mask (a list
 * containing only a layer mask as single item), if a layer mask is in edit
 * mode.
 *
 * Returns: (element-type PikaItem) (transfer container):
 *          The list of selected drawables in the image.
 *          The returned list must be freed with g_list_free(). Layer
 *          elements belong to libpika and must not be freed.
 *
 * Since: 3.0
 **/
GList *
pika_image_list_selected_drawables (PikaImage *image)
{
  PikaItem **drawables;
  gint       num_drawables;
  GList     *list = NULL;
  gint       i;

  drawables = pika_image_get_selected_drawables (image, &num_drawables);

  for (i = 0; i < num_drawables; i++)
    list = g_list_prepend (list, drawables[i]);

  g_free (drawables);

  return g_list_reverse (list);
}

/**
 * pika_image_get_colormap:
 * @image:      The image.
 * @colormap_len: (out) (optional): The size (in bytes) of the returned colormap
 * @num_colors: (out) (optional): Returns the number of colors in the colormap array.
 *
 * Returns the image's colormap
 *
 * This procedure returns an actual pointer to the image's colormap, as
 * well as the number of colors contained in the colormap. If the image
 * is not of base type INDEXED, this pointer will be NULL.
 *
 * Returns: (array length=colormap_len): The image's colormap.
 */
guchar *
pika_image_get_colormap (PikaImage *image,
                         gint      *colormap_len,
                         gint      *num_colors)
{
  GBytes *bytes;
  gsize   num_bytes;
  guchar *cmap;

  bytes = _pika_image_get_colormap (image);
  cmap = g_bytes_unref_to_data (bytes, &num_bytes);

  if (colormap_len)
    *colormap_len = num_bytes;
  if (num_colors)
    *num_colors = num_bytes / 3;

  return cmap;
}

/**
 * pika_image_set_colormap:
 * @image:      The image.
 * @colormap: (array): The new colormap values.
 * @num_colors: Number of colors in the colormap array.
 *
 * Sets the entries in the image's colormap.
 *
 * This procedure sets the entries in the specified image's colormap.
 * The number of colors is specified by the "num_colors" parameter
 * and corresponds to the number of INT8 triples that must be contained
 * in the "cmap" array.
 *
 * Returns: TRUE on success.
 */
gboolean
pika_image_set_colormap (PikaImage    *image,
                         const guchar *colormap,
                         gint          num_colors)
{
  GBytes *bytes;
  gboolean ret;

  bytes = g_bytes_new_static (colormap, num_colors * 3);
  ret = _pika_image_set_colormap (image, bytes);
  g_bytes_unref (bytes);

  return ret;
}

/**
 * pika_image_get_thumbnail_data:
 * @image:  The image.
 * @width:  (inout): The requested thumbnail width.
 * @height: (inout): The requested thumbnail height.
 * @bpp:    (out): The previews bpp.
 *
 * Get a thumbnail of an image.
 *
 * This function gets data from which a thumbnail of an image preview
 * can be created. Maximum x or y dimension is 1024 pixels. The pixels
 * are returned in RGB[A] or GRAY[A] format. The bpp return value
 * gives the number of bytes per pixel in the image.
 *
 * Returns: (transfer full): the thumbnail data.
 **/
GBytes *
pika_image_get_thumbnail_data (PikaImage *image,
                               gint      *width,
                               gint      *height,
                               gint      *bpp)
{
  GBytes *image_bytes;

  _pika_image_thumbnail (image,
                         *width,
                         *height,
                         width,
                         height,
                         bpp,
                         &image_bytes);

  return image_bytes;
}

/**
 * pika_image_get_thumbnail:
 * @image:  the #PikaImage
 * @width:  the requested thumbnail width  (<= 1024 pixels)
 * @height: the requested thumbnail height (<= 1024 pixels)
 * @alpha:  how to handle an alpha channel
 *
 * Retrieves a thumbnail pixbuf for @image.
 * The thumbnail will be not larger than the requested size.
 *
 * Returns: (transfer full): a new #GdkPixbuf
 *
 * Since: 2.2
 **/
GdkPixbuf *
pika_image_get_thumbnail (PikaImage              *image,
                          gint                    width,
                          gint                    height,
                          PikaPixbufTransparency  alpha)
{
  gint    thumb_width  = width;
  gint    thumb_height = height;
  gint    thumb_bpp;
  GBytes *data;
  gsize   data_size;

  g_return_val_if_fail (width  > 0 && width  <= 1024, NULL);
  g_return_val_if_fail (height > 0 && height <= 1024, NULL);

  data = pika_image_get_thumbnail_data (image,
                                        &thumb_width,
                                        &thumb_height,
                                        &thumb_bpp);
  if (data)
    return _pika_pixbuf_from_data (g_bytes_unref_to_data (data, &data_size),
                                   thumb_width, thumb_height, thumb_bpp,
                                   alpha);
  else
    return NULL;
}

/**
 * pika_image_get_metadata:
 * @image: The image.
 *
 * Returns the image's metadata.
 *
 * Returns exif/iptc/xmp metadata from the image.
 *
 * Returns: (nullable) (transfer full): The exif/ptc/xmp metadata,
 *          or %NULL if there is none.
 *
 * Since: 2.10
 **/
PikaMetadata *
pika_image_get_metadata (PikaImage *image)
{
  PikaMetadata *metadata = NULL;
  gchar        *metadata_string;

  metadata_string = _pika_image_get_metadata (image);
  if (metadata_string)
    {
      metadata = pika_metadata_deserialize (metadata_string);
      g_free (metadata_string);
    }

  return metadata;
}

/**
 * pika_image_set_metadata:
 * @image:    The image.
 * @metadata: The exif/ptc/xmp metadata.
 *
 * Set the image's metadata.
 *
 * Sets exif/iptc/xmp metadata on the image, or deletes it if
 * @metadata is %NULL.
 *
 * Returns: TRUE on success.
 *
 * Since: 2.10
 **/
gboolean
pika_image_set_metadata (PikaImage    *image,
                         PikaMetadata *metadata)
{
  gchar    *metadata_string = NULL;
  gboolean  success;

  if (metadata)
    metadata_string = pika_metadata_serialize (metadata);

  success = _pika_image_set_metadata (image, metadata_string);

  if (metadata_string)
    g_free (metadata_string);

  return success;
}
