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

#include "libpikabase/pikabase.h"
#include "libpikacolor/pikacolor.h"

#include "core-types.h"

#include "pikachannel.h"
#include "pikacontainer.h"
#include "pikacontext.h"
#include "pikagradient.h"
#include "pikaimage.h"
#include "pikaimage-colormap.h"
#include "pikapalette.h"
#include "pikapalette-import.h"
#include "pikapalette-load.h"
#include "pikapickable.h"

#include "pika-intl.h"


#define MAX_IMAGE_COLORS (10000 * 2)


/*  create a palette from a gradient  ****************************************/

PikaPalette *
pika_palette_import_from_gradient (PikaGradient                *gradient,
                                   PikaContext                 *context,
                                   gboolean                     reverse,
                                   PikaGradientBlendColorSpace  blend_color_space,
                                   const gchar                 *palette_name,
                                   gint                         n_colors)
{
  PikaPalette         *palette;
  PikaGradientSegment *seg = NULL;
  gdouble              dx, cur_x;
  PikaRGB              color;
  gint                 i;

  g_return_val_if_fail (PIKA_IS_GRADIENT (gradient), NULL);
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
  g_return_val_if_fail (palette_name != NULL, NULL);
  g_return_val_if_fail (n_colors > 1, NULL);

  palette = PIKA_PALETTE (pika_palette_new (context, palette_name));

  dx = 1.0 / (n_colors - 1);

  for (i = 0, cur_x = 0; i < n_colors; i++, cur_x += dx)
    {
      seg = pika_gradient_get_color_at (gradient, context,
                                        seg, cur_x, reverse, blend_color_space,
                                        &color);
      pika_palette_add_entry (palette, -1, NULL, &color);
    }

  return palette;
}


/*  create a palette from a non-indexed image  *******************************/

typedef struct _ImgColors ImgColors;

struct _ImgColors
{
  guint  count;
  guint  r_adj;
  guint  g_adj;
  guint  b_adj;
  guchar r;
  guchar g;
  guchar b;
};

static gint count_color_entries = 0;

static GHashTable *
pika_palette_import_store_colors (GHashTable *table,
                                  guchar     *colors,
                                  guchar     *colors_real,
                                  gint        n_colors)
{
  gpointer   found_color = NULL;
  ImgColors *new_color;
  guint      key_colors = colors[0] * 256 * 256 + colors[1] * 256 + colors[2];

  if (table == NULL)
    {
      table = g_hash_table_new (g_direct_hash, g_direct_equal);
      count_color_entries = 0;
    }
  else
    {
      found_color = g_hash_table_lookup (table, GUINT_TO_POINTER (key_colors));
    }

  if (found_color == NULL)
    {
      if (count_color_entries > MAX_IMAGE_COLORS)
        {
          /* Don't add any more new ones */
          return table;
        }

      count_color_entries++;

      new_color = g_slice_new (ImgColors);

      new_color->count = 1;
      new_color->r_adj = 0;
      new_color->g_adj = 0;
      new_color->b_adj = 0;
      new_color->r     = colors[0];
      new_color->g     = colors[1];
      new_color->b     = colors[2];

      g_hash_table_insert (table, GUINT_TO_POINTER (key_colors), new_color);
    }
  else
    {
      new_color = found_color;

      if (new_color->count < (G_MAXINT - 1))
        new_color->count++;

      /* Now do the adjustments ...*/
      new_color->r_adj += (colors_real[0] - colors[0]);
      new_color->g_adj += (colors_real[1] - colors[1]);
      new_color->b_adj += (colors_real[2] - colors[2]);

      /* Boundary conditions */
      if(new_color->r_adj > (G_MAXINT - 255))
        new_color->r_adj /= new_color->count;

      if(new_color->g_adj > (G_MAXINT - 255))
        new_color->g_adj /= new_color->count;

      if(new_color->b_adj > (G_MAXINT - 255))
        new_color->b_adj /= new_color->count;
    }

  return table;
}

static void
pika_palette_import_create_list (gpointer key,
                                 gpointer value,
                                 gpointer user_data)
{
  GSList    **list      = user_data;
  ImgColors  *color_tab = value;

  *list = g_slist_prepend (*list, color_tab);
}

static gint
pika_palette_import_sort_colors (gconstpointer a,
                                 gconstpointer b)
{
  const ImgColors *s1 = a;
  const ImgColors *s2 = b;

  if(s1->count > s2->count)
    return -1;
  if(s1->count < s2->count)
    return 1;

  return 0;
}

static void
pika_palette_import_create_image_palette (gpointer data,
                                          gpointer user_data)
{
  PikaPalette *palette   = user_data;
  ImgColors   *color_tab = data;
  gint         n_colors;
  gchar       *lab;
  PikaRGB      color;

  n_colors = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (palette),
                                                 "import-n-colors"));

  if (pika_palette_get_n_colors (palette) >= n_colors)
    return;

  /* TRANSLATORS: the "%s" is an item title and "%u" is the number of
     occurrences for this item. */
  lab = g_strdup_printf (_("%s (occurs %u)"),
                         _("Untitled"),
                         color_tab->count);

  /* Adjust the colors to the mean of the the sample */
  pika_rgba_set_uchar
    (&color,
     (guchar) color_tab->r + (color_tab->r_adj / color_tab->count),
     (guchar) color_tab->g + (color_tab->g_adj / color_tab->count),
     (guchar) color_tab->b + (color_tab->b_adj / color_tab->count),
     255);

  pika_palette_add_entry (palette, -1, lab, &color);

  g_free (lab);
}

static PikaPalette *
pika_palette_import_make_palette (GHashTable  *table,
                                  const gchar *palette_name,
                                  PikaContext *context,
                                  gint         n_colors)
{
  PikaPalette *palette;
  GSList      *list = NULL;
  GSList      *iter;

  palette = PIKA_PALETTE (pika_palette_new (context, palette_name));

  if (! table)
    return palette;

  g_hash_table_foreach (table, pika_palette_import_create_list, &list);
  list = g_slist_sort (list, pika_palette_import_sort_colors);

  g_object_set_data (G_OBJECT (palette), "import-n-colors",
                     GINT_TO_POINTER (n_colors));

  g_slist_foreach (list, pika_palette_import_create_image_palette, palette);

  g_object_set_data (G_OBJECT (palette), "import-n-colors", NULL);

  /*  Free up used memory
   *  Note the same structure is on both the hash list and the sorted
   *  list. So only delete it once.
   */
  g_hash_table_destroy (table);

  for (iter = list; iter; iter = iter->next)
    g_slice_free (ImgColors, iter->data);

  g_slist_free (list);

  return palette;
}

static GHashTable *
pika_palette_import_extract (PikaImage     *image,
                             GHashTable    *colors,
                             PikaPickable  *pickable,
                             gint           pickable_off_x,
                             gint           pickable_off_y,
                             gboolean       selection_only,
                             gint           x,
                             gint           y,
                             gint           width,
                             gint           height,
                             gint           n_colors,
                             gint           threshold)
{
  GeglBuffer         *buffer;
  GeglBufferIterator *iter;
  GeglRectangle      *mask_roi = NULL;
  GeglRectangle       rect     = { x, y, width, height };
  const Babl         *format;
  gint                bpp;
  gint                mask_bpp = 0;

  buffer = pika_pickable_get_buffer (pickable);
  format = babl_format ("R'G'B'A u8");

  iter = gegl_buffer_iterator_new (buffer, &rect, 0, format,
                                   GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
  bpp = babl_format_get_bytes_per_pixel (format);

  if (selection_only &&
      ! pika_channel_is_empty (pika_image_get_mask (image)))
    {
      PikaDrawable *mask = PIKA_DRAWABLE (pika_image_get_mask (image));

      rect.x = x + pickable_off_x;
      rect.y = y + pickable_off_y;

      buffer = pika_drawable_get_buffer (mask);
      format = babl_format ("Y u8");

      gegl_buffer_iterator_add (iter, buffer, &rect, 0, format,
                                GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
      mask_roi = &iter->items[1].roi;
      mask_bpp = babl_format_get_bytes_per_pixel (format);
    }

  while (gegl_buffer_iterator_next (iter))
    {
      const guchar *data      = iter->items[0].data;
      const guchar *mask_data = NULL;
      gint          length    = iter->length;

      if (mask_roi)
        mask_data = iter->items[1].data;

      while (length--)
        {
          /*  ignore unselected, and completely transparent pixels  */
          if ((! mask_data || *mask_data) && data[ALPHA])
            {
              guchar rgba[MAX_CHANNELS]     = { 0, };
              guchar rgb_real[MAX_CHANNELS] = { 0, };

              memcpy (rgba, data, 4);
              memcpy (rgb_real, rgba, 4);

              rgba[0] = (rgba[0] / threshold) * threshold;
              rgba[1] = (rgba[1] / threshold) * threshold;
              rgba[2] = (rgba[2] / threshold) * threshold;

              colors = pika_palette_import_store_colors (colors,
                                                         rgba, rgb_real,
                                                         n_colors);
            }

          data += bpp;

          if (mask_data)
            mask_data += mask_bpp;
        }
    }

  return colors;
}

PikaPalette *
pika_palette_import_from_image (PikaImage   *image,
                                PikaContext *context,
                                const gchar *palette_name,
                                gint         n_colors,
                                gint         threshold,
                                gboolean     selection_only)
{
  GHashTable *colors;
  gint        x, y;
  gint        width, height;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
  g_return_val_if_fail (palette_name != NULL, NULL);
  g_return_val_if_fail (n_colors > 1, NULL);
  g_return_val_if_fail (threshold > 0, NULL);

  pika_pickable_flush (PIKA_PICKABLE (image));

  if (selection_only)
    {
      pika_item_bounds (PIKA_ITEM (pika_image_get_mask (image)),
                        &x, &y, &width, &height);
    }
  else
    {
      x      = 0;
      y      = 0;
      width  = pika_image_get_width  (image);
      height = pika_image_get_height (image);
    }

  colors = pika_palette_import_extract (image,
                                        NULL,
                                        PIKA_PICKABLE (image),
                                        0, 0,
                                        selection_only,
                                        x, y, width, height,
                                        n_colors, threshold);

  return pika_palette_import_make_palette (colors, palette_name, context,
                                           n_colors);
}


/*  create a palette from an indexed image  **********************************/

PikaPalette *
pika_palette_import_from_indexed_image (PikaImage   *image,
                                        PikaContext *context,
                                        const gchar *palette_name)
{
  PikaPalette  *palette;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
  g_return_val_if_fail (pika_image_get_base_type (image) == PIKA_INDEXED, NULL);
  g_return_val_if_fail (palette_name != NULL, NULL);

  palette = PIKA_PALETTE (pika_data_duplicate (PIKA_DATA (pika_image_get_colormap_palette (image))));

  pika_object_set_name (PIKA_OBJECT (palette), palette_name);

  return palette;
}


/*  create a palette from a drawable  ****************************************/

PikaPalette *
pika_palette_import_from_drawables (GList       *drawables,
                                    PikaContext *context,
                                    const gchar *palette_name,
                                    gint         n_colors,
                                    gint         threshold,
                                    gboolean     selection_only)
{
  GHashTable *colors = NULL;
  GList      *iter;
  gint        x, y;
  gint        width, height;
  gint        off_x, off_y;

  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
  g_return_val_if_fail (palette_name != NULL, NULL);
  g_return_val_if_fail (n_colors > 1, NULL);
  g_return_val_if_fail (threshold > 0, NULL);

  for (iter = drawables; iter; iter = iter->next)
    {
      PikaDrawable *drawable = iter->data;

      g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL);
      g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (drawable)), NULL);

      if (selection_only)
        {
          if (! pika_item_mask_intersect (PIKA_ITEM (drawable),
                                          &x, &y, &width, &height))
            return NULL;
        }
      else
        {
          x      = 0;
          y      = 0;
          width  = pika_item_get_width  (PIKA_ITEM (drawable));
          height = pika_item_get_height (PIKA_ITEM (drawable));
        }

      pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);

      colors =
        pika_palette_import_extract (pika_item_get_image (PIKA_ITEM (drawable)),
                                     colors,
                                     PIKA_PICKABLE (drawable),
                                     off_x, off_y,
                                     selection_only,
                                     x, y, width, height,
                                     n_colors, threshold);
    }

  return pika_palette_import_make_palette (colors, palette_name, context,
                                           n_colors);
}


/*  create a palette from a file  **********************************/

PikaPalette *
pika_palette_import_from_file (PikaContext  *context,
                               GFile        *file,
                               const gchar  *palette_name,
                               GError      **error)
{
  GList        *palette_list = NULL;
  GInputStream *input;
  GError       *my_error = NULL;

  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
  g_return_val_if_fail (G_IS_FILE (file), NULL);
  g_return_val_if_fail (palette_name != NULL, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error));
  if (! input)
    {
      g_set_error (error, PIKA_DATA_ERROR, PIKA_DATA_ERROR_OPEN,
                   _("Could not open '%s' for reading: %s"),
                   pika_file_get_utf8_name (file), my_error->message);
      g_clear_error (&my_error);
      return NULL;
    }

  switch (pika_palette_load_detect_format (file, input))
    {
    case PIKA_PALETTE_FILE_FORMAT_GPL:
      palette_list = pika_palette_load (context, file, input, error);
      break;

    case PIKA_PALETTE_FILE_FORMAT_ACT:
      palette_list = pika_palette_load_act (context, file, input, error);
      break;

    case PIKA_PALETTE_FILE_FORMAT_RIFF_PAL:
      palette_list = pika_palette_load_riff (context, file, input, error);
      break;

    case PIKA_PALETTE_FILE_FORMAT_PSP_PAL:
      palette_list = pika_palette_load_psp (context, file, input, error);
      break;

    case PIKA_PALETTE_FILE_FORMAT_ACO:
      palette_list = pika_palette_load_aco (context, file, input, error);
      break;

    case PIKA_PALETTE_FILE_FORMAT_ACB:
      palette_list = pika_palette_load_acb (context, file, input, error);
      break;

    case PIKA_PALETTE_FILE_FORMAT_ASE:
      palette_list = pika_palette_load_ase (context, file, input, error);
      break;

    case PIKA_PALETTE_FILE_FORMAT_CSS:
      palette_list = pika_palette_load_css (context, file, input, error);
      break;

    case PIKA_PALETTE_FILE_FORMAT_SBZ:
      palette_list = pika_palette_load_sbz (context, file, input, error);
      break;

    default:
      g_set_error (error,
                   PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
                   _("Unknown type of palette file: %s"),
                   pika_file_get_utf8_name (file));
      break;
    }

  g_object_unref (input);

  if (palette_list)
    {
      PikaPalette *palette = g_object_ref (palette_list->data);

      pika_object_set_name (PIKA_OBJECT (palette), palette_name);

      g_list_free_full (palette_list, (GDestroyNotify) g_object_unref);

      return palette;
    }

  return NULL;
}
