/* Copyright (C) 2001-2023 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 the license contained in the file LICENSE in this distribution.

   Refer to licensing information at http://www.artifex.com or contact
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
   CA 94129, USA, for further information.
*/


/* CIE color rendering dictionary creation */
#include "math_.h"
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gsdevice.h"
#include "gserrors.h"
#include "gsmatrix.h"		/* for gscolor2.h */
#include "gsstruct.h"
#include "gxcspace.h"
#include "gscolor2.h"		/* for gs_set/currentcolorrendering */
#include "gscrdp.h"
#include "gxarith.h"

/* ---------------- Writing ---------------- */

/* Internal procedures for writing parameter values. */
static void
store_vector3(float *p, const gs_vector3 * pvec)
{
    p[0] = pvec->u, p[1] = pvec->v, p[2] = pvec->w;
}
static int
write_floats(gs_param_list * plist, gs_param_name key,
             const float *values, int size, gs_memory_t * mem)
{
    float *p = (float *)
        gs_alloc_byte_array(mem, size, sizeof(float), "write_floats");
    gs_param_float_array fa;

    if (p == 0)
        return_error(gs_error_VMerror);
    memcpy(p, values, size * sizeof(float));

    fa.data = p;
    fa.size = size;
    fa.persistent = true;
    return param_write_float_array(plist, key, &fa);
}
static int
write_vector3(gs_param_list * plist, gs_param_name key,
              const gs_vector3 * pvec, gs_memory_t * mem)
{
    float values[3];

    store_vector3(values, pvec);
    return write_floats(plist, key, values, 3, mem);
}
static int
write_matrix3(gs_param_list * plist, gs_param_name key,
              const gs_matrix3 * pmat, gs_memory_t * mem)
{
    float values[9];
    if(matrix_equal(pmat, &Matrix3_default))
        return 0;
    store_vector3(values, &pmat->cu);
    store_vector3(values + 3, &pmat->cv);
    store_vector3(values + 6, &pmat->cw);
    return write_floats(plist, key, values, 9, mem);
}
static int
write_range3(gs_param_list * plist, gs_param_name key,
             const gs_range3 * prange, gs_memory_t * mem)
{
    float values[6];

    if (range_equal(prange, &Range3_default))
        return 0;
    values[0] = prange->ranges[0].rmin, values[1] = prange->ranges[0].rmax;
    values[2] = prange->ranges[1].rmin, values[3] = prange->ranges[1].rmax;
    values[4] = prange->ranges[2].rmin, values[5] = prange->ranges[2].rmax;
    return write_floats(plist, key, values, 6, mem);
}

static bool
render_proc3_equal(const gs_cie_render_proc3 *p1, const gs_cie_render_proc3 *p2)
{
    int k;

    for (k = 0; k < 3; k++) {
        if (p1->procs[k] != p2->procs[k])
            return false;
    }
    return true;
}

static int
write_proc3(gs_param_list * plist, gs_param_name key,
            const gs_cie_render * pcrd, const gs_cie_render_proc3 * procs,
            const gs_range3 * domain, gs_memory_t * mem)
{
    float *values;
    uint size = gx_cie_cache_size;
    gs_param_float_array fa;
    int i;

    if (render_proc3_equal(procs, &Encode_default))
        return 0;
    values = (float *)gs_alloc_byte_array(mem, size * 3, sizeof(float),
                                          "write_proc3");

    if (values == 0)
        return_error(gs_error_VMerror);
    for (i = 0; i < 3; ++i) {
        double base = domain->ranges[i].rmin;
        double scale = (domain->ranges[i].rmax - base) / (size - 1);
        int j;

        for (j = 0; j < size; ++j)
            values[i * size + j] =
                (*procs->procs[i]) (j * scale + base, pcrd);
    }
    fa.data = values;
    fa.size = size * 3;
    fa.persistent = true;
    return param_write_float_array(plist, key, &fa);
}

/* Write a CRD as a device parameter. */
int
param_write_cie_render1(gs_param_list * plist, gs_param_name key,
                        gs_cie_render * pcrd, gs_memory_t * mem)
{
    gs_param_dict dict;
    int code, dcode;

    dict.size = 20;
    if ((code = param_begin_write_dict(plist, key, &dict, false)) < 0)
        return code;
    code = param_put_cie_render1(dict.list, pcrd, mem);
    dcode = param_end_write_dict(plist, key, &dict);
    return (code < 0 ? code : dcode);
}

/* Write a CRD directly to a parameter list. */
int
param_put_cie_render1(gs_param_list * plist, gs_cie_render * pcrd,
                      gs_memory_t * mem)
{
    int crd_type = GX_DEVICE_CRD1_TYPE;
    int code = gs_cie_render_sample(pcrd); /* we need RenderTableT_is_id' */

    if (code < 0)
        return code;
    if (pcrd->TransformPQR.proc_name) {
        gs_param_string pn, pd;

        param_string_from_string(pn, pcrd->TransformPQR.proc_name);
        pn.size++;		/* include terminating null */
        pd.data = pcrd->TransformPQR.proc_data.data;
        pd.size = pcrd->TransformPQR.proc_data.size;
        pd.persistent = true;  /****** WRONG ******/
        if ((code = param_write_name(plist, "TransformPQRName", &pn)) < 0 ||
            (code = param_write_string(plist, "TransformPQRData", &pd)) < 0
            )
            return code;
    }
    else if (pcrd->TransformPQR.proc != TransformPQR_default.proc) {
        /* We have no way to represent the procedure, so return an error. */
        return_error(gs_error_rangecheck);
    }
    if ((code = param_write_int(plist, "ColorRenderingType", &crd_type)) < 0 ||
        (code = write_vector3(plist, "WhitePoint", &pcrd->points.WhitePoint, mem)) < 0
        )
        return code;
    if (!vector_equal(&pcrd->points.BlackPoint, &BlackPoint_default)) {
        if ((code = write_vector3(plist, "BlackPoint", &pcrd->points.BlackPoint, mem)) < 0)
            return code;
    }
    if ((code = write_matrix3(plist, "MatrixPQR", &pcrd->MatrixPQR, mem)) < 0 ||
        (code = write_range3(plist, "RangePQR", &pcrd->RangePQR, mem)) < 0 ||
    /* TransformPQR is handled separately */
    (code = write_matrix3(plist, "MatrixLMN", &pcrd->MatrixLMN, mem)) < 0 ||
        (code = write_proc3(plist, "EncodeLMNValues", pcrd,
                            &pcrd->EncodeLMN, &pcrd->DomainLMN, mem)) < 0 ||
        (code = write_range3(plist, "RangeLMN", &pcrd->RangeLMN, mem)) < 0 ||
    (code = write_matrix3(plist, "MatrixABC", &pcrd->MatrixABC, mem)) < 0 ||
        (code = write_proc3(plist, "EncodeABCValues", pcrd,
                            &pcrd->EncodeABC, &pcrd->DomainABC, mem)) < 0 ||
        (code = write_range3(plist, "RangeABC", &pcrd->RangeABC, mem)) < 0
        )
        return code;
    if (pcrd->RenderTable.lookup.table) {
        int n = pcrd->RenderTable.lookup.n;
        int m = pcrd->RenderTable.lookup.m;
        int na = pcrd->RenderTable.lookup.dims[0];
        int *size = (int *)
            gs_alloc_byte_array(mem, n + 1, sizeof(int), "RenderTableSize");

        /*
         * In principle, we should use gs_alloc_struct_array with a
         * type descriptor for gs_param_string.  However, it is widely
         * assumed that parameter lists are transient, and don't require
         * accurate GC information; so we can get away with allocating
         * the string table as bytes.
         */
        gs_param_string *table =
            (gs_param_string *)
            gs_alloc_byte_array(mem, na, sizeof(gs_param_string),
                                "RenderTableTable");
        gs_param_int_array ia;

        if (size == 0 || table == 0)
            code = gs_note_error(gs_error_VMerror);
        else {
            memcpy(size, pcrd->RenderTable.lookup.dims, sizeof(int) * n);

            size[n] = m;
            ia.data = size;
            ia.size = n + 1;
            ia.persistent = true;
            code = param_write_int_array(plist, "RenderTableSize", &ia);
        }
        if (code >= 0) {
            gs_param_string_array sa;
            int a;

            for (a = 0; a < na; ++a)
                table[a].data = pcrd->RenderTable.lookup.table[a].data,
                    table[a].size = pcrd->RenderTable.lookup.table[a].size,
                    table[a].persistent = true;
            sa.data = table;
            sa.size = na;
            sa.persistent = true;
            code = param_write_string_array(plist, "RenderTableTable", &sa);
            if (code >= 0 && !pcrd->caches.RenderTableT_is_identity) {
                /****** WRITE RenderTableTValues LIKE write_proc3 ******/
                uint size = gx_cie_cache_size;
                float *values =
                    (float *)gs_alloc_byte_array(mem, size * m,
                                                 sizeof(float),
                                                 "write_proc3");
                gs_param_float_array fa;
                int i;

                if (values == 0)
                    return_error(gs_error_VMerror);
                for (i = 0; i < m; ++i) {
                    double scale = 255.0 / (size - 1);
                    int j;

                    for (j = 0; j < size; ++j)
                        values[i * size + j] =
                            frac2float((*pcrd->RenderTable.T.procs[i])
                                       ((byte)(j * scale), pcrd));
                }
                fa.data = values;
                fa.size = size * m;
                fa.persistent = true;
                code = param_write_float_array(plist, "RenderTableTValues",
                                               &fa);
            }
        }
        if (code < 0) {
            gs_free_object(mem, table, "RenderTableTable");
            gs_free_object(mem, size, "RenderTableSize");
            return code;
        }
    }
    return code;
}

/* ---------------- Reading ---------------- */

/* Internal procedures for reading parameter values. */
static void
load_vector3(gs_vector3 * pvec, const float *p)
{
    pvec->u = p[0], pvec->v = p[1], pvec->w = p[2];
}
static int
read_floats(gs_param_list * plist, gs_param_name key, float *values, int count)
{
    gs_param_float_array fa;
    int code = param_read_float_array(plist, key, &fa);

    if (code)
        return code;
    if (fa.size != count)
        return_error(gs_error_rangecheck);
    memcpy(values, fa.data, sizeof(float) * count);

    return 0;
}
static int
read_vector3(gs_param_list * plist, gs_param_name key,
             gs_vector3 * pvec, const gs_vector3 * dflt)
{
    float values[3];
    int code = read_floats(plist, key, values, 3);

    switch (code) {
        case 1:		/* not defined */
            if (dflt)
                *pvec = *dflt;
            break;
        case 0:
            load_vector3(pvec, values);
        default:		/* error */
            break;
    }
    return code;
}
static int
read_matrix3(gs_param_list * plist, gs_param_name key, gs_matrix3 * pmat)
{
    float values[9];
    int code = read_floats(plist, key, values, 9);

    switch (code) {
        case 1:		/* not defined */
            *pmat = Matrix3_default;
            break;
        case 0:
            load_vector3(&pmat->cu, values);
            load_vector3(&pmat->cv, values + 3);
            load_vector3(&pmat->cw, values + 6);
        default:		/* error */
            break;
    }
    return code;
}
static int
read_range3(gs_param_list * plist, gs_param_name key, gs_range3 * prange)
{
    float values[6];
    int code = read_floats(plist, key, values, 6);

    switch (code) {
        case 1:		/* not defined */
            *prange = Range3_default;
            break;
        case 0:
            prange->ranges[0].rmin = values[0];
            prange->ranges[0].rmax = values[1];
            prange->ranges[1].rmin = values[2];
            prange->ranges[1].rmax = values[3];
            prange->ranges[2].rmin = values[4];
            prange->ranges[2].rmax = values[5];
        default:		/* error */
            break;
    }
    return code;
}
static int
read_proc3(gs_param_list * plist, gs_param_name key,
           float values[gx_cie_cache_size * 3])
{
    return read_floats(plist, key, values, gx_cie_cache_size * 3);
}

/* Read a CRD from a device parameter. */
int
gs_cie_render1_param_initialize(gs_cie_render * pcrd, gs_param_list * plist,
                                gs_param_name key, gx_device * dev)
{
    gs_param_dict dict;
    int code = param_begin_read_dict(plist, key, &dict, false);
    int dcode;

    if (code < 0)
        return code;
    code = param_get_cie_render1(pcrd, dict.list, dev);
    dcode = param_end_read_dict(plist, key, &dict);
    if (code < 0)
        return code;
    if (dcode < 0)
        return dcode;
    gs_cie_render_init(pcrd);
    gs_cie_render_sample(pcrd);
    return gs_cie_render_complete(pcrd);
}

/* Define the structure for passing Encode values as "client data". */
typedef struct encode_data_s {
    float lmn[gx_cie_cache_size * 3]; /* EncodeLMN */
    float abc[gx_cie_cache_size * 3]; /* EncodeABC */
    float t[gx_cie_cache_size * 4]; /* RenderTable.T */
} encode_data_t;

/* Define procedures that retrieve the Encode values read from the list. */
static float
encode_from_data(double v, const float values[gx_cie_cache_size],
                 const gs_range * range)
{
    return (v <= range->rmin ? values[0] :
            v >= range->rmax ? values[gx_cie_cache_size - 1] :
            values[(int)((v - range->rmin) / (range->rmax - range->rmin) *
                         (gx_cie_cache_size - 1) + 0.5)]);
}
/*
 * The repetitive boilerplate in the next 10 procedures really sticks in
 * my craw, but I've got a mandate not to use macros....
 */
static float
encode_lmn_0_from_data(double v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->lmn[0],
                            &pcrd->DomainLMN.ranges[0]);
}
static float
encode_lmn_1_from_data(double v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->lmn[gx_cie_cache_size],
                            &pcrd->DomainLMN.ranges[1]);
}
static float
encode_lmn_2_from_data(double v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->lmn[gx_cie_cache_size * 2],
                            &pcrd->DomainLMN.ranges[2]);
}
static float
encode_abc_0_from_data(double v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->abc[0],
                            &pcrd->DomainABC.ranges[0]);
}
static float
encode_abc_1_from_data(double v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->abc[gx_cie_cache_size],
                            &pcrd->DomainABC.ranges[1]);
}
static float
encode_abc_2_from_data(double v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->abc[gx_cie_cache_size * 2],
                            &pcrd->DomainABC.ranges[2]);
}
static frac
render_table_t_0_from_data(byte v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return float2frac(encode_from_data(v / 255.0,
                                       &data->t[0],
                                       &Range3_default.ranges[0]));
}
static frac
render_table_t_1_from_data(byte v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return float2frac(encode_from_data(v / 255.0,
                                       &data->t[gx_cie_cache_size],
                                       &Range3_default.ranges[0]));
}
static frac
render_table_t_2_from_data(byte v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return float2frac(encode_from_data(v / 255.0,
                                       &data->t[gx_cie_cache_size * 2],
                                       &Range3_default.ranges[0]));
}
static frac
render_table_t_3_from_data(byte v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return float2frac(encode_from_data(v / 255.0,
                                       &data->t[gx_cie_cache_size * 3],
                                       &Range3_default.ranges[0]));
}
static const gs_cie_render_proc3 EncodeLMN_from_data = {
    {encode_lmn_0_from_data, encode_lmn_1_from_data, encode_lmn_2_from_data}
};
static const gs_cie_render_proc3 EncodeABC_from_data = {
    {encode_abc_0_from_data, encode_abc_1_from_data, encode_abc_2_from_data}
};
static const gs_cie_render_table_procs RenderTableT_from_data = {
    {render_table_t_0_from_data, render_table_t_1_from_data,
     render_table_t_2_from_data, render_table_t_3_from_data
    }
};

/* Read a CRD directly from a parameter list. */
int
param_get_cie_render1(gs_cie_render * pcrd, gs_param_list * plist,
                      gx_device * dev)
{
    encode_data_t data;
    gs_param_int_array rt_size;
    int crd_type;
    int code, code_lmn, code_abc, code_rt, code_t;
    gs_param_string pname, pdata;

    /* Reset the status to invalidate cached information. */
    pcrd->status = CIE_RENDER_STATUS_BUILT;
    if ((code = param_read_int(plist, "ColorRenderingType", &crd_type)) < 0 ||
        crd_type != GX_DEVICE_CRD1_TYPE ||
        (code = read_vector3(plist, "WhitePoint", &pcrd->points.WhitePoint,
                             NULL)) < 0 ||
        (code = read_vector3(plist, "BlackPoint", &pcrd->points.BlackPoint,
                             &BlackPoint_default)) < 0 ||
        (code = read_matrix3(plist, "MatrixPQR", &pcrd->MatrixPQR)) < 0 ||
        (code = read_range3(plist, "RangePQR", &pcrd->RangePQR)) < 0 ||
        /* TransformPQR is handled specially below. */
        (code = read_matrix3(plist, "MatrixLMN", &pcrd->MatrixLMN)) < 0 ||
        (code_lmn = code =
         read_proc3(plist, "EncodeLMNValues", data.lmn)) < 0 ||
        (code = read_range3(plist, "RangeLMN", &pcrd->RangeLMN)) < 0 ||
        (code = read_matrix3(plist, "MatrixABC", &pcrd->MatrixABC)) < 0 ||
        (code_abc = code =
         read_proc3(plist, "EncodeABCValues", data.abc)) < 0 ||
        (code = read_range3(plist, "RangeABC", &pcrd->RangeABC)) < 0
        )
        return code;
    /* Handle the sampled functions. */
    switch (code = param_read_string(plist, "TransformPQRName", &pname)) {
        default:		/* error */
            return code;
        case 1:			/* missing */
            pcrd->TransformPQR = TransformPQR_default;
            break;
        case 0:			/* specified */
            /* The procedure name must be null-terminated: */
            /* see param_put_cie_render1 above. */
            if (pname.size < 1 || pname.data[pname.size - 1] != 0)
                return_error(gs_error_rangecheck);
            pcrd->TransformPQR.proc = TransformPQR_lookup_proc_name;
            pcrd->TransformPQR.proc_name = (const char *)pname.data;
            switch (code = param_read_string(plist, "TransformPQRData", &pdata)) {
                default:	/* error */
                    return code;
                case 1:		/* missing */
                    pcrd->TransformPQR.proc_data.data = 0;
                    pcrd->TransformPQR.proc_data.size = 0;
                    break;
                case 0:
                    pcrd->TransformPQR.proc_data.data = pdata.data;
                    pcrd->TransformPQR.proc_data.size = pdata.size;
            }
            pcrd->TransformPQR.driver_name = gs_devicename(dev);
            break;
    }
    pcrd->client_data = &data;
    if (code_lmn > 0)
        pcrd->EncodeLMN = Encode_default;
    else
        pcrd->EncodeLMN = EncodeLMN_from_data;
    if (code_abc > 0)
        pcrd->EncodeABC = Encode_default;
    else
        pcrd->EncodeABC = EncodeABC_from_data;
    code_rt = param_read_int_array(plist, "RenderTableSize", &rt_size);
    if (code_rt == 1) {
        if (pcrd->RenderTable.lookup.table) {
            gs_free_object(pcrd->rc.memory,
                (void *)pcrd->RenderTable.lookup.table, /* break const */
                "param_get_cie_render1(RenderTable)");
            pcrd->RenderTable.lookup.table = 0;
        }
        pcrd->RenderTable.T = RenderTableT_default;
        code_t = 1;
    } else if (code_rt < 0)
        return code_rt;
    else if (rt_size.size != 4)
        return_error(gs_error_rangecheck);
    else {
        gs_param_string_array rt_values;
        gs_const_string *table;
        int n, m, j;

        for (j = 0; j < rt_size.size; ++j)
            if (rt_size.data[j] < 1)
                return_error(gs_error_rangecheck);
        code = param_read_string_array(plist, "RenderTableTable", &rt_values);
        if (code < 0)
            return code;
        if (code > 0 || rt_values.size != rt_size.data[0])
            return_error(gs_error_rangecheck);
        /* Note: currently n = 3 (rt_size.size = 4) always. */
        for (j = 0; j < rt_values.size; ++j)
            if (rt_values.data[j].size !=
                rt_size.data[1] * rt_size.data[2] * rt_size.data[3])
                return_error(gs_error_rangecheck);
        pcrd->RenderTable.lookup.n = n = rt_size.size - 1;
        pcrd->RenderTable.lookup.m = m = rt_size.data[n];
        if (n > 4 || m > 4)
            return_error(gs_error_rangecheck);
        memcpy(pcrd->RenderTable.lookup.dims, rt_size.data, n * sizeof(int));
        table =
            gs_alloc_struct_array(pcrd->rc.memory,
                                  pcrd->RenderTable.lookup.dims[0],
                                  gs_const_string, &st_const_string_element,
                                  "RenderTable table");
        if (table == 0)
            return_error(gs_error_VMerror);
        for (j = 0; j < pcrd->RenderTable.lookup.dims[0]; ++j) {
            table[j].data = rt_values.data[j].data;
            table[j].size = rt_values.data[j].size;
        }
        pcrd->RenderTable.lookup.table = table;
        pcrd->RenderTable.T = RenderTableT_from_data;
        code_t = code = read_floats(plist, "RenderTableTValues", data.t,
                                    gx_cie_cache_size * m);
        if (code > 0)
            pcrd->RenderTable.T = RenderTableT_default;
        else if (code == 0)
            pcrd->RenderTable.T = RenderTableT_from_data;
    }
    if ((code = gs_cie_render_init(pcrd)) >= 0 &&
        (code = gs_cie_render_sample(pcrd)) >= 0
        )
        code = gs_cie_render_complete(pcrd);
    /* Clean up before exiting. */
    pcrd->client_data = 0;
    if (code_lmn == 0)
        pcrd->EncodeLMN = EncodeLMN_from_cache;
    if (code_abc == 0)
        pcrd->EncodeABC = EncodeABC_from_cache;
    if (code_t == 0)
        pcrd->RenderTable.T = RenderTableT_from_cache;
    return code;
}
