/* pam.c - pam (portable alpha map) utility library
**
** Copyright (C) 1989, 1991 by Jef Poskanzer.
** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by
**                                Stefan Schneider.
** © 2009-2013 by Kornel Lesinski.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include <stdlib.h>
#include <string.h>

#include "libimagequant.h"
#include "pam.h"
#include "mempool.h"

/* *INDENT-OFF* */
LIQ_PRIVATE bool
pam_computeacolorhash (struct acolorhash_table *acht,
    const rgba_pixel * const pixels[], unsigned int cols, unsigned int rows,
    const unsigned char *importance_map)
/* *INDENT-ON* */
{
  const unsigned int maxacolors = acht->maxcolors, ignorebits =
      acht->ignorebits;
  const unsigned int channel_mask = 255U >> ignorebits << ignorebits;
  const unsigned int channel_hmask = (255U >> ignorebits) ^ 0xFFU;
  const unsigned int posterize_mask =
      channel_mask << 24 | channel_mask << 16 | channel_mask << 8 |
      channel_mask;
  const unsigned int posterize_high_mask =
      channel_hmask << 24 | channel_hmask << 16 | channel_hmask << 8 |
      channel_hmask;
  struct acolorhist_arr_head *const buckets = acht->buckets;

  unsigned int colors = acht->colors;
  const unsigned int hash_size = acht->hash_size;

  const unsigned int stacksize =
      sizeof (acht->freestack) / sizeof (acht->freestack[0]);
  struct acolorhist_arr_item **freestack = acht->freestack;
  unsigned int freestackp = acht->freestackp;

  /* Go through the entire image, building a hash table of colors. */
  for (unsigned int row = 0; row < rows; ++row) {

    float boost = 1.0;
    for (unsigned int col = 0; col < cols; ++col) {
      union rgba_as_int px = { pixels[row][col] };
      unsigned int hash;
      struct acolorhist_arr_head *achl;

      if (importance_map) {
        boost = 0.5f + (double) *importance_map++ / 255.f;
      }
      // RGBA color is casted to long for easier hasing/comparisons
      if (!px.rgba.a) {
        // "dirty alpha" has different RGBA values that end up being the same fully transparent color
        px.l = 0;
        hash = 0;
      } else {
        // mask posterizes all 4 channels in one go
        px.l =
            (px.l & posterize_mask) | ((px.l & posterize_high_mask) >> (8 -
                ignorebits));
        // fancier hashing algorithms didn't improve much
        hash = px.l % hash_size;
      }

      /* head of the hash function stores first 2 colors inline (achl->used = 1..2),
         to reduce number of allocations of achl->other_items.
       */
      achl = &buckets[hash];
      if (achl->inline1.color.l == px.l && achl->used) {
        achl->inline1.perceptual_weight += boost;
        continue;
      }
      if (achl->used) {
        if (achl->used > 1) {
          struct acolorhist_arr_item *other_items;
          unsigned int i = 0;
          struct acolorhist_arr_item *new_items;
          unsigned int capacity;

          if (achl->inline2.color.l == px.l) {
            achl->inline2.perceptual_weight += boost;
            continue;
          }
          // other items are stored as an array (which gets reallocated if needed)
          other_items = achl->other_items;
          for (i = 0; i < achl->used - 2; i++) {
            if (other_items[i].color.l == px.l) {
              other_items[i].perceptual_weight += boost;
              goto continue_outer_loop;
            }
          }

          // the array was allocated with spare items
          if (i < achl->capacity) {
            other_items[i] = (struct acolorhist_arr_item) {
            .color = px,.perceptual_weight = boost,};
            achl->used++;
            ++colors;
            continue;
          }

          if (++colors > maxacolors) {
            acht->colors = colors;
            acht->freestackp = freestackp;
            return false;
          }

          if (!other_items) {   // there was no array previously, alloc "small" array
            capacity = 8;
            if (freestackp <= 0) {
              // estimate how many colors are going to be + headroom
              const int mempool_size =
                  ((acht->rows + rows - row) * 2 * colors / (acht->rows + row +
                      1) + 1024) * sizeof (struct acolorhist_arr_item);
              new_items =
                  mempool_alloc (&acht->mempool,
                  sizeof (struct acolorhist_arr_item) * capacity, mempool_size);
            } else {
              // freestack stores previously freed (reallocated) arrays that can be reused
              // (all pesimistically assumed to be capacity = 8)
              new_items = freestack[--freestackp];
            }
          } else {
            // simply reallocs and copies array to larger capacity
            capacity = achl->capacity * 2 + 16;
            if (freestackp < stacksize - 1) {
              freestack[freestackp++] = other_items;
            }

            {
              const int mempool_size =
                  ((acht->rows + rows - row) * 2 * colors / (acht->rows + row +
                      1) + 32 * capacity) * sizeof (struct acolorhist_arr_item);
              new_items =
                  mempool_alloc (&acht->mempool,
                  sizeof (struct acolorhist_arr_item) * capacity, mempool_size);
            }
            if (!new_items)
              return false;
            memcpy (new_items, other_items,
                sizeof (other_items[0]) * achl->capacity);
          }

          achl->other_items = new_items;
          achl->capacity = capacity;
          new_items[i] = (struct acolorhist_arr_item) {
          .color = px,.perceptual_weight = boost,};
          achl->used++;
        } else {
          // these are elses for first checks whether first and second inline-stored colors are used
          achl->inline2.color.l = px.l;
          achl->inline2.perceptual_weight = boost;
          achl->used = 2;
          ++colors;
        }
      } else {
        achl->inline1.color.l = px.l;
        achl->inline1.perceptual_weight = boost;
        achl->used = 1;
        ++colors;
      }

    continue_outer_loop:;
    }

  }
  acht->colors = colors;
  acht->cols = cols;
  acht->rows += rows;
  acht->freestackp = freestackp;
  return true;
}

LIQ_PRIVATE struct acolorhash_table *
pam_allocacolorhash (unsigned int maxcolors, unsigned int surface,
    unsigned int ignorebits, void *(*malloc) (size_t), void (*free) (void *))
{
  const unsigned int estimated_colors =
      MIN (maxcolors, surface / (ignorebits + (surface > 512 * 512 ? 5 : 4)));
  const unsigned int hash_size =
      estimated_colors < 66000 ? 6673 : (estimated_colors <
      200000 ? 12011 : 24019);

  mempool m = NULL;
  const unsigned int buckets_size =
      hash_size * sizeof (struct acolorhist_arr_head);
  const unsigned int mempool_size =
      sizeof (struct acolorhash_table) + buckets_size +
      estimated_colors * sizeof (struct acolorhist_arr_item);
  struct acolorhash_table *t =
      mempool_create (&m, sizeof (*t) + buckets_size, mempool_size, malloc,
      free);
  if (!t)
    return NULL;
  *t = (struct acolorhash_table) {
  .mempool = m,.hash_size = hash_size,.maxcolors = maxcolors,.ignorebits =
        ignorebits,};
  memset (t->buckets, 0, hash_size * sizeof (struct acolorhist_arr_head));
  return t;
}

#define PAM_ADD_TO_HIST(entry) { \
    hist->achv[j].acolor = to_f(gamma_lut, entry.color.rgba); \
    total_weight += hist->achv[j].adjusted_weight = hist->achv[j].perceptual_weight = MIN(entry.perceptual_weight, max_perceptual_weight); \
    ++j; \
}

LIQ_PRIVATE histogram *
pam_acolorhashtoacolorhist (const struct acolorhash_table * acht,
    const double gamma, void *(*malloc) (size_t), void (*free) (void *))
{
  histogram *hist = malloc (sizeof (hist[0]));
  float gamma_lut[256];
  float max_perceptual_weight;
  double total_weight;
  unsigned int i, j, k;

  if (!hist || !acht)
    return NULL;
  *hist = (histogram) {
  .achv = malloc (acht->colors * sizeof (hist->achv[0])),.size =
        acht->colors,.free = free,.ignorebits = acht->ignorebits,};
  if (!hist->achv)
    return NULL;

  to_f_set_gamma (gamma_lut, gamma);

  /* Limit perceptual weight to 1/10th of the image surface area to prevent
     a single color from dominating all others. */
  max_perceptual_weight = 0.1f * acht->cols * acht->rows;
  total_weight = 0;

  for (j = 0, i = 0; i < acht->hash_size; ++i) {
    const struct acolorhist_arr_head *const achl = &acht->buckets[i];
    if (achl->used) {
      PAM_ADD_TO_HIST (achl->inline1);

      if (achl->used > 1) {
        PAM_ADD_TO_HIST (achl->inline2);

        for (k = 0; k < achl->used - 2; k++) {
          PAM_ADD_TO_HIST (achl->other_items[k]);
        }
      }
    }
  }

  hist->total_perceptual_weight = total_weight;
  return hist;
}


LIQ_PRIVATE void
pam_freeacolorhash (struct acolorhash_table *acht)
{
  mempool_destroy (acht->mempool);
}

LIQ_PRIVATE void
pam_freeacolorhist (histogram * hist)
{
  hist->free (hist->achv);
  hist->free (hist);
}

LIQ_PRIVATE colormap *
pam_colormap (unsigned int colors, void *(*malloc) (size_t),
    void (*free) (void *))
{
  const size_t colors_size = colors * sizeof (colormap_item);
  colormap *map;

  assert (colors > 0 && colors < 65536);

  map = malloc (sizeof (colormap) + colors_size);
  if (!map)
    return NULL;
  *map = (colormap) {
  .malloc = malloc,.free = free,.subset_palette = NULL,.colors = colors,};
  memset (map->palette, 0, colors_size);
  return map;
}

LIQ_PRIVATE colormap *
pam_duplicate_colormap (colormap * map)
{
  colormap *dupe = pam_colormap (map->colors, map->malloc, map->free);
  for (unsigned int i = 0; i < map->colors; i++) {
    dupe->palette[i] = map->palette[i];
  }
  if (map->subset_palette) {
    dupe->subset_palette = pam_duplicate_colormap (map->subset_palette);
  }
  return dupe;
}

LIQ_PRIVATE void
pam_freecolormap (colormap * c)
{
  if (c->subset_palette)
    pam_freecolormap (c->subset_palette);
  c->free (c);
}

LIQ_PRIVATE void
to_f_set_gamma (float gamma_lut[], const double gamma)
{
  for (int i = 0; i < 256; i++) {
    gamma_lut[i] = pow ((double) i / 255.0, internal_gamma / gamma);
  }
}