/* Copyright (C) 2001-2007 Artifex Software, Inc.
   All Rights Reserved.

   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied, modified
   or distributed except as expressly authorized under the terms of that
   license.  Refer to licensing information at http://www.artifex.com/
   or contact Artifex Software, Inc.,  7 Mt. Lassen Drive - Suite A-134,
   San Rafael, CA  94903, U.S.A., +1(415)492-9861, for further information.
*/

/* $Id$ */
/* A color index cache. */
#include "gx.h"
#include "gserrors.h"
#include "gsccolor.h"
#include "gxcspace.h"
#include "gxdcolor.h"
#include "gscicach.h"
#include "memory_.h"

#define COLOR_INDEX_CACHE_SIZE 256
#define COLOR_INDEX_CACHE_CHAINS (COLOR_INDEX_CACHE_SIZE / 16)

typedef struct gs_color_index_cache_elem_s gs_color_index_cache_elem_t;

struct gs_color_index_cache_elem_s {
    gx_color_index cindex;
    uint chain;
    uint prev, next; /* NULL for unused. */
    uint touch_prev, touch_next;
    bool frac_values_done;
};

struct gs_color_index_cache_s {
    const gs_color_space *direct_space;
    gs_imager_state *pis;
    gx_device *dev;
    gx_device *trans_dev;
    int client_num_components;
    int device_num_components;
    gs_memory_t *memory;
    int used;
    gs_color_index_cache_elem_t *buf;
    uint recent_touch;
    float *paint_values;
    frac31 *frac_values;
    int chains[COLOR_INDEX_CACHE_CHAINS];
    /* Note : the 0th element of buf, paint_values, frac_values is never used,
       because we consider the index 0 as NULL
       just for a faster initialization. */
#   define MYNULL 0
};

gs_private_st_ptrs6(st_color_index_cache, gs_color_index_cache_t, "gs_color_index_cache_t",
                    gs_color_index_cache_elem_ptrs, gs_color_index_cache_reloc_ptrs,
                    direct_space, memory, buf, paint_values, frac_values, trans_dev);

gs_color_index_cache_t *
gs_color_index_cache_create(gs_memory_t *memory, const gs_color_space *direct_space, gx_device *dev,
                            gs_imager_state *pis, bool need_frac, gx_device *trans_dev)
{
    int client_num_components = cs_num_components(direct_space);
    int device_num_components = trans_dev->color_info.num_components;
    gs_color_index_cache_elem_t *buf = ( gs_color_index_cache_elem_t *)gs_alloc_byte_array(memory, COLOR_INDEX_CACHE_SIZE,
                    sizeof(gs_color_index_cache_elem_t), "gs_color_index_cache_create");
    float *paint_values = (float *)gs_alloc_byte_array(memory, COLOR_INDEX_CACHE_SIZE * client_num_components,
                    sizeof(float), "gs_color_index_cache_create");
    frac31 *frac_values = (need_frac ? (frac31 *)gs_alloc_byte_array(memory, COLOR_INDEX_CACHE_SIZE * device_num_components,
                                            sizeof(frac31), "gs_color_index_cache_create") : NULL);
    gs_color_index_cache_t *pcic = gs_alloc_struct(memory, gs_color_index_cache_t, &st_color_index_cache, "gs_color_index_cache_create");

    if (buf == NULL || paint_values == NULL || (need_frac && frac_values == NULL) || pcic == NULL) {
        gs_free_object(memory, buf, "gs_color_index_cache_create");
        gs_free_object(memory, paint_values, "gs_color_index_cache_create");
        gs_free_object(memory, frac_values, "gs_color_index_cache_create");
        gs_free_object(memory, pcic, "gs_color_index_cache_create");
        return NULL;
    }
    memset(pcic, 0, sizeof(*pcic));
    memset(buf, 0, COLOR_INDEX_CACHE_SIZE * sizeof(gs_color_index_cache_elem_t));
    pcic->direct_space = direct_space;
    pcic->pis = pis;
    pcic->dev = dev;
    pcic->trans_dev = trans_dev;
    pcic->device_num_components = device_num_components;
    pcic->client_num_components = client_num_components;
    pcic->memory = memory;
    pcic->used = 1; /* Never use the 0th element. */
    pcic->buf = buf;
    pcic->recent_touch = MYNULL;
    pcic->paint_values = paint_values;
    pcic->frac_values = frac_values;
    return pcic;
}

void
gs_color_index_cache_destroy(gs_color_index_cache_t *pcic)
{
    gs_free_object(pcic->memory, pcic->buf, "gs_color_index_cache_create");
    gs_free_object(pcic->memory, pcic->paint_values, "gs_color_index_cache_create");
    gs_free_object(pcic->memory, pcic->frac_values, "gs_color_index_cache_create");
    pcic->buf = NULL;
    pcic->paint_values = NULL;
    pcic->frac_values = NULL;
    gs_free_object(pcic->memory, pcic, "gs_color_index_cache_create");
}

static inline int
hash_paint_values(const gs_color_index_cache_t *this, const float *paint_values)
{
    int i;
    float v = 0;
    uint k = 0;
    const uint a_prime = 79;

    for (i = 0; i < this->client_num_components; i++)
        v = v * a_prime + paint_values[i];
    /* Don't know the range of v, so hash its bytes : */
    for(i = 0; i < sizeof(v); i++)
        k = k * a_prime + ((byte *)&v)[i];
    return k % COLOR_INDEX_CACHE_CHAINS;
}

static inline void
exclude_from_chain(gs_color_index_cache_t *this, uint i)
{
    uint co = this->buf[i].chain;
    uint ip = this->buf[i].prev, in = this->buf[i].next;

    this->buf[ip].next = in;
    this->buf[in].prev = ip;
    if (this->chains[co] == i)
        this->chains[co] = in;
}

static inline void
include_into_chain(gs_color_index_cache_t *this, uint i, uint c)
{
    if (this->chains[c] != MYNULL) {
        uint in = this->chains[c], ip = this->buf[in].prev;

        this->buf[i].next = in;
        this->buf[i].prev = ip;
        this->buf[in].prev = i;
        this->buf[ip].next = i;
    } else
        this->buf[i].prev = this->buf[i].next = i;
    this->chains[c] = i;
    this->buf[i].chain = c;
}

static inline void
exclude_from_touch_list(gs_color_index_cache_t *this, uint i)
{
    uint ip = this->buf[i].touch_prev, in = this->buf[i].touch_next;

    this->buf[ip].touch_next = in;
    this->buf[in].touch_prev = ip;
    if (this->recent_touch == i) {
        if (i == in)
            this->recent_touch = MYNULL;
        else
            this->recent_touch = in;
    }
}

static inline void
include_into_touch_list(gs_color_index_cache_t *this, uint i)
{
    if (this->recent_touch != MYNULL) {
        uint in = this->recent_touch, ip = this->buf[in].touch_prev;

        this->buf[i].touch_next = in;
        this->buf[i].touch_prev = ip;
        this->buf[in].touch_prev = i;
        this->buf[ip].touch_next = i;
    } else
        this->buf[i].touch_prev = this->buf[i].touch_next = i;
    this->recent_touch = i;
}

static int
get_color_index_cache_elem(gs_color_index_cache_t *this, const float *paint_values, uint *pi)
{
    int client_num_components = this->client_num_components;
    uint c = hash_paint_values(this, paint_values);
    uint i = this->chains[c], j;

    if (i != MYNULL) {
        uint tries = 16; /* Arbitrary. */

        if (!memcmp(paint_values, this->paint_values + i * client_num_components, sizeof(*paint_values) * client_num_components)) {
            if (this->recent_touch != i) {
                exclude_from_touch_list(this, i);
                include_into_touch_list(this, i);
            }
            *pi = i;
            return 1;
        }
        for (j = this->buf[i].next; tries -- && j != i; j = this->buf[j].next) {
            if (!memcmp(paint_values, this->paint_values + j * client_num_components, sizeof(*paint_values) * client_num_components)) {
                exclude_from_chain(this, j);
                include_into_chain(this, j, c);
                if (this->recent_touch != j) {
                    exclude_from_touch_list(this, j);
                    include_into_touch_list(this, j);
                }
                *pi = j;
                return 1;
            }
        }
        tries+=0;
    }
    if (this->used < COLOR_INDEX_CACHE_SIZE) {
        /* Use a new one */
        i = this->used++;
        include_into_touch_list(this, i);
    } else {
        i = this->recent_touch;
        this->recent_touch = this->buf[i].touch_prev; /* Assuming the cyclic list,
                                                      just move the head pointer to the last element. */
        exclude_from_chain(this, i);
    }
    include_into_chain(this, i, c);
    *pi = i;
    return 0;
}

static inline void
compute_frac_values(gs_color_index_cache_t *this, uint i)
{
    gx_color_index c = this->buf[i].cindex;
    const gx_device_color_info *cinfo = &this->trans_dev->color_info;
    int device_num_components = this->device_num_components;
    int j;

    for (j = 0; j < device_num_components; j++) {
            int shift = cinfo->comp_shift[j];
            int bits = cinfo->comp_bits[j];

            this->frac_values[i * device_num_components + j] = ((c >> shift) & ((1 << bits) - 1)) << (sizeof(frac31) * 8 - 1 - bits);
    }
    this->buf[i].frac_values_done = true;
}

int
gs_cached_color_index(gs_color_index_cache_t *this, const float *paint_values, gx_device_color *pdevc, frac31 *frac_values)
{
    /* Must return 2 if the color is not pure.
       See patch_color_to_device_color. */
    const gs_color_space *pcs = this->direct_space;
    int client_num_components = this->client_num_components;
    int device_num_components = this->device_num_components;
    uint i;
    int code;

    if (get_color_index_cache_elem(this, paint_values, &i)) {
        if (pdevc != NULL) {
            pdevc->colors.pure = this->buf[i].cindex;
            pdevc->type = &gx_dc_type_data_pure;
            memcpy(pdevc->ccolor.paint.values, paint_values, sizeof(*paint_values) * client_num_components);
            pdevc->ccolor_valid = true;
        }
        if (frac_values != NULL && !this->buf[i].frac_values_done)
            compute_frac_values(this, i);
    } else {
        gx_device_color devc_local;
        gs_client_color fcc;

        if (pdevc == NULL)
            pdevc = &devc_local;
        memcpy(this->paint_values + i * client_num_components, paint_values, sizeof(*paint_values) * client_num_components);
        memcpy(fcc.paint.values, paint_values, sizeof(*paint_values) * client_num_components);
        code = pcs->type->remap_color(&fcc, pcs, pdevc, this->pis, this->trans_dev,
                                      gs_color_select_texture);
        if (code < 0)
            return code;
        if (pdevc->type != &gx_dc_type_data_pure)
            return 2;
        this->buf[i].cindex = pdevc->colors.pure;

        if (frac_values != NULL)
            compute_frac_values(this, i);
        else
            this->buf[i].frac_values_done = false;
    }
    if (frac_values != NULL)
        memcpy(frac_values, this->frac_values + i * device_num_components, sizeof(*frac_values) * device_num_components);
    return 0;
}
