/* Copyright (C) 1997, 2000 artofcode LLC.  All rights reserved.
  
  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 2 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, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gsfunc3.c,v 1.4.6.1.2.1 2003/01/17 00:49:02 giles Exp $ */
/* Implementation of LL3 Functions */
#include "math_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsfunc3.h"
#include "gsparam.h"
#include "gxfunc.h"

/* ---------------- Utilities ---------------- */

#define MASK1 ((uint)(~0) / 3)

/*
 * Free an array of subsidiary Functions.  Note that this may be called
 * before the Functions array has been fully initialized.  Note also that
 * its argument conforms to the Functions array in the parameter structure,
 * but it (necessarily) deconstifies it.
 */
private void
fn_free_functions(const gs_function_t *const * Functions, int count,
		  gs_memory_t * mem)
{
    int i;

    for (i = count; --i >= 0;)
	if (Functions[i])
	    gs_function_free((gs_function_t *)Functions[i], true, mem);
    gs_free_const_object(mem, Functions, "Functions");
}

/* ---------------- Exponential Interpolation functions ---------------- */

typedef struct gs_function_ElIn_s {
    gs_function_head_t head;
    gs_function_ElIn_params_t params;
} gs_function_ElIn_t;

private_st_function_ElIn();

/* Evaluate an Exponential Interpolation function. */
private int
fn_ElIn_evaluate(const gs_function_t * pfn_common, const float *in, float *out)
{
    const gs_function_ElIn_t *const pfn =
	(const gs_function_ElIn_t *)pfn_common;
    double arg = in[0], raised;
    int i;

    if (arg < pfn->params.Domain[0])
	arg = pfn->params.Domain[0];
    else if (arg > pfn->params.Domain[1])
	arg = pfn->params.Domain[1];
    raised = pow(arg, pfn->params.N);
    for (i = 0; i < pfn->params.n; ++i) {
	float v0 = (pfn->params.C0 == 0 ? 0.0 : pfn->params.C0[i]);
	float v1 = (pfn->params.C1 == 0 ? 1.0 : pfn->params.C1[i]);
	double value = v0 + raised * (v1 - v0);

	if (pfn->params.Range) {
	    float r0 = pfn->params.Range[2 * i],
		r1 = pfn->params.Range[2 * i + 1];

	    if (value < r0)
		value = r0;
	    else if (value > r1)
		value = r1;
	}
	out[i] = value;
	if_debug3('~', "[~]ElIn %g => [%d]%g\n", arg, i, out[i]);
    }
    return 0;
}

/* Test whether an Exponential function is monotonic.  (They always are.) */
private int
fn_ElIn_is_monotonic(const gs_function_t * pfn_common,
		     const float *lower, const float *upper,
		     gs_function_effort_t effort)
{
    const gs_function_ElIn_t *const pfn =
	(const gs_function_ElIn_t *)pfn_common;
    int i, result;

    if (lower[0] > pfn->params.Domain[1] ||
	upper[0] < pfn->params.Domain[0]
	)
	return_error(gs_error_rangecheck);
    for (i = 0, result = 0; i < pfn->params.n; ++i) {
	double diff =
	    (pfn->params.C1 == 0 ? 1.0 : pfn->params.C1[i]) -
	    (pfn->params.C0 == 0 ? 0.0 : pfn->params.C0[i]);

	if (pfn->params.N < 0)
	    diff = -diff;
	else if (pfn->params.N == 0)
	    diff = 0;
	result |=
	    (diff < 0 ? FN_MONOTONIC_DECREASING :
	     diff > 0 ? FN_MONOTONIC_INCREASING :
	     FN_MONOTONIC_DECREASING | FN_MONOTONIC_INCREASING) <<
	    (2 * i);
    }
    return result;
}

/* Write Exponential Interpolation function parameters on a parameter list. */
private int
fn_ElIn_get_params(const gs_function_t *pfn_common, gs_param_list *plist)
{
    const gs_function_ElIn_t *const pfn =
	(const gs_function_ElIn_t *)pfn_common;
    int ecode = fn_common_get_params(pfn_common, plist);
    int code;

    if (pfn->params.C0) {
	if ((code = param_write_float_values(plist, "C0", pfn->params.C0,
					     pfn->params.n, false)) < 0)
	    ecode = code;
    }
    if (pfn->params.C1) {
	if ((code = param_write_float_values(plist, "C1", pfn->params.C1,
					     pfn->params.n, false)) < 0)
	    ecode = code;
    }
    if ((code = param_write_float(plist, "N", &pfn->params.N)) < 0)
	ecode = code;
    return ecode;
}

/* Free the parameters of an Exponential Interpolation function. */
void
gs_function_ElIn_free_params(gs_function_ElIn_params_t * params,
			     gs_memory_t * mem)
{
    gs_free_const_object(mem, params->C1, "C1");
    gs_free_const_object(mem, params->C0, "C0");
    fn_common_free_params((gs_function_params_t *) params, mem);
}

/* Allocate and initialize an Exponential Interpolation function. */
int
gs_function_ElIn_init(gs_function_t ** ppfn,
		      const gs_function_ElIn_params_t * params,
		      gs_memory_t * mem)
{
    static const gs_function_head_t function_ElIn_head = {
	function_type_ExponentialInterpolation,
	{
	    (fn_evaluate_proc_t) fn_ElIn_evaluate,
	    (fn_is_monotonic_proc_t) fn_ElIn_is_monotonic,
	    gs_function_get_info_default,
	    (fn_get_params_proc_t) fn_ElIn_get_params,
	    (fn_free_params_proc_t) gs_function_ElIn_free_params,
	    fn_common_free
	}
    };
    int code;

    *ppfn = 0;			/* in case of error */
    code = fn_check_mnDR((const gs_function_params_t *)params, 1, params->n);
    if (code < 0)
	return code;
    if ((params->C0 == 0 || params->C1 == 0) && params->n != 1)
	return_error(gs_error_rangecheck);
    if (params->N != floor(params->N)) {
	/* Non-integral exponent, all inputs must be non-negative. */
	if (params->Domain[0] < 0)
	    return_error(gs_error_rangecheck);
    }
    if (params->N < 0) {
	/* Negative exponent, input must not be zero. */
	if (params->Domain[0] <= 0 && params->Domain[1] >= 0)
	    return_error(gs_error_rangecheck);
    } {
	gs_function_ElIn_t *pfn =
	    gs_alloc_struct(mem, gs_function_ElIn_t, &st_function_ElIn,
			    "gs_function_ElIn_init");

	if (pfn == 0)
	    return_error(gs_error_VMerror);
	pfn->params = *params;
	pfn->params.m = 1;
	pfn->head = function_ElIn_head;
	pfn->head.is_monotonic =
	    fn_domain_is_monotonic((gs_function_t *)pfn, EFFORT_MODERATE);
	*ppfn = (gs_function_t *) pfn;
    }
    return 0;
}

/* ---------------- 1-Input Stitching functions ---------------- */

typedef struct gs_function_1ItSg_s {
    gs_function_head_t head;
    gs_function_1ItSg_params_t params;
} gs_function_1ItSg_t;

private_st_function_1ItSg();

/* Evaluate a 1-Input Stitching function. */
private int
fn_1ItSg_evaluate(const gs_function_t * pfn_common, const float *in, float *out)
{
    const gs_function_1ItSg_t *const pfn =
	(const gs_function_1ItSg_t *)pfn_common;
    float arg = in[0], b0, b1, e0, encoded;
    int k = pfn->params.k;
    int i;

    if (arg < pfn->params.Domain[0]) {
	arg = pfn->params.Domain[0];
	i = 0;
    } else if (arg > pfn->params.Domain[1]) {
	arg = pfn->params.Domain[1];
	i = k - 1;
    } else {
	for (i = 0; i < k - 1; ++i)
	    if (arg <= pfn->params.Bounds[i])
		break;
    }
    b0 = (i == 0 ? pfn->params.Domain[0] : pfn->params.Bounds[i - 1]);
    b1 = (i == k - 1 ? pfn->params.Domain[1] : pfn->params.Bounds[i]);
    e0 = pfn->params.Encode[2 * i];
    encoded =
	(arg - b0) * (pfn->params.Encode[2 * i + 1] - e0) / (b1 - b0) + e0;
    if_debug3('~', "[~]1ItSg %g in %d => %g\n", arg, i, encoded);
    return gs_function_evaluate(pfn->params.Functions[i], &encoded, out);
}

/* Test whether a 1-Input Stitching function is monotonic. */
private int
fn_1ItSg_is_monotonic(const gs_function_t * pfn_common,
		      const float *lower, const float *upper,
		      gs_function_effort_t effort)
{
    const gs_function_1ItSg_t *const pfn =
	(const gs_function_1ItSg_t *)pfn_common;
    float v0 = lower[0], v1 = upper[0];
    float d0 = pfn->params.Domain[0], d1 = pfn->params.Domain[1];
    int k = pfn->params.k;
    int i;
    int result = 0;

    if (v0 > d1 || v1 < d0)
	return_error(gs_error_rangecheck);
    if (v0 < d0)
	v0 = d0;
    if (v1 > d1)
	v1 = d1;
    for (i = 0; i < pfn->params.k; ++i) {
	float b0 = (i == 0 ? d0 : pfn->params.Bounds[i - 1]);
	float b1 = (i == k - 1 ? d1 : pfn->params.Bounds[i]);
	float e0, e1;
	float w0, w1;
	int code;

	if (v0 >= b1 || v1 <= b0)
	    continue;
	e0 = pfn->params.Encode[2 * i];
	e1 = pfn->params.Encode[2 * i + 1];
	w0 = (max(v0, b0) - b0) * (e1 - e0) / (b1 - b0) + e0;
	w1 = (min(v1, b1) - b0) * (e1 - e0) / (b1 - b0) + e0;
	/* Note that w0 > w1 is now possible if e0 > e1. */
	if (w0 > w1) {
	    code = gs_function_is_monotonic(pfn->params.Functions[i],
					    &w1, &w0, effort);
	    if (code <= 0)
		return code;
	    /* Swap the INCREASING and DECREASING flags. */
	    code = ((code & MASK1) << 1) | ((code & (MASK1 << 1)) >> 1);
	} else {
	    code = gs_function_is_monotonic(pfn->params.Functions[i],
					    &w0, &w1, effort);
	    if (code <= 0)
		return code;
	}
	if (result == 0)
	    result = code;
	else {
	    result &= code;
	    /* Check that result is still monotonic in every position. */
	    code = result | ((result & MASK1) << 1) |
		((result & (MASK1 << 1)) >> 1);
	    if (code != (1 << (2 * pfn->params.n)) - 1)
		return 0;
	}
    }
    return result;
}

/* Return 1-Input Stitching function information. */
private void
fn_1ItSg_get_info(const gs_function_t *pfn_common, gs_function_info_t *pfi)
{
    const gs_function_1ItSg_t *const pfn =
	(const gs_function_1ItSg_t *)pfn_common;

    gs_function_get_info_default(pfn_common, pfi);
    pfi->Functions = pfn->params.Functions;
    pfi->num_Functions = pfn->params.k;
}

/* Write 1-Input Stitching function parameters on a parameter list. */
private int
fn_1ItSg_get_params(const gs_function_t *pfn_common, gs_param_list *plist)
{
    const gs_function_1ItSg_t *const pfn =
	(const gs_function_1ItSg_t *)pfn_common;
    int ecode = fn_common_get_params(pfn_common, plist);
    int code;

    if ((code = param_write_float_values(plist, "Bounds", pfn->params.Bounds,
					 pfn->params.k - 1, false)) < 0)
	ecode = code;
    if ((code = param_write_float_values(plist, "Encode", pfn->params.Encode,
					 2 * pfn->params.k, false)) < 0)
	ecode = code;
    return ecode;
}

/* Free the parameters of a 1-Input Stitching function. */
void
gs_function_1ItSg_free_params(gs_function_1ItSg_params_t * params,
			      gs_memory_t * mem)
{
    gs_free_const_object(mem, params->Encode, "Encode");
    gs_free_const_object(mem, params->Bounds, "Bounds");
    fn_free_functions(params->Functions, params->k, mem);
    fn_common_free_params((gs_function_params_t *) params, mem);
}

/* Allocate and initialize a 1-Input Stitching function. */
int
gs_function_1ItSg_init(gs_function_t ** ppfn,
	       const gs_function_1ItSg_params_t * params, gs_memory_t * mem)
{
    static const gs_function_head_t function_1ItSg_head = {
	function_type_1InputStitching,
	{
	    (fn_evaluate_proc_t) fn_1ItSg_evaluate,
	    (fn_is_monotonic_proc_t) fn_1ItSg_is_monotonic,
	    (fn_get_info_proc_t) fn_1ItSg_get_info,
	    (fn_get_params_proc_t) fn_1ItSg_get_params,
	    (fn_free_params_proc_t) gs_function_1ItSg_free_params,
	    fn_common_free
	}
    };
    int n = (params->Range == 0 ? 0 : params->n);
    float prev = params->Domain[0];
    int i;

    *ppfn = 0;			/* in case of error */
    for (i = 0; i < params->k; ++i) {
	const gs_function_t *psubfn = params->Functions[i];

	if (psubfn->params.m != 1)
	    return_error(gs_error_rangecheck);
	if (n == 0)
	    n = psubfn->params.n;
	else if (psubfn->params.n != n)
	    return_error(gs_error_rangecheck);
	/* There are only k - 1 Bounds, not k. */
	if (i < params->k - 1) {
	    if (params->Bounds[i] <= prev)
		return_error(gs_error_rangecheck);
	    prev = params->Bounds[i];
	}
    }
    if (params->Domain[1] < prev)
	return_error(gs_error_rangecheck);
    fn_check_mnDR((const gs_function_params_t *)params, 1, n);
    {
	gs_function_1ItSg_t *pfn =
	    gs_alloc_struct(mem, gs_function_1ItSg_t, &st_function_1ItSg,
			    "gs_function_1ItSg_init");

	if (pfn == 0)
	    return_error(gs_error_VMerror);
	pfn->params = *params;
	pfn->params.m = 1;
	pfn->params.n = n;
	pfn->head = function_1ItSg_head;
	pfn->head.is_monotonic =
	    fn_domain_is_monotonic((gs_function_t *)pfn, EFFORT_MODERATE);
	*ppfn = (gs_function_t *) pfn;
    }
    return 0;
}

/* ---------------- Arrayed Output functions ---------------- */

typedef struct gs_function_AdOt_s {
    gs_function_head_t head;
    gs_function_AdOt_params_t params;
} gs_function_AdOt_t;

private_st_function_AdOt();

/* Evaluate an Arrayed Output function. */
private int
fn_AdOt_evaluate(const gs_function_t * pfn_common, const float *in, float *out)
{
    const gs_function_AdOt_t *const pfn =
	(const gs_function_AdOt_t *)pfn_common;
    int i;

    for (i = 0; i < pfn->params.n; ++i) {
	int code =
	    gs_function_evaluate(pfn->params.Functions[i], in, out + i);

	if (code < 0)
	    return code;
    }
    return 0;
}

/* Test whether an Arrayed Output function is monotonic. */
private int
fn_AdOt_is_monotonic(const gs_function_t * pfn_common,
		     const float *lower, const float *upper,
		     gs_function_effort_t effort)
{
    const gs_function_AdOt_t *const pfn =
	(const gs_function_AdOt_t *)pfn_common;
    int i, result;

    for (i = 0, result = 0; i < pfn->params.n; ++i) {
	int code =
	    gs_function_is_monotonic(pfn->params.Functions[i], lower, upper,
				     effort);

	if (code <= 0)
	    return code;
	result |= code << (2 * i);
    }
    return result;
}

/* Free the parameters of an Arrayed Output function. */
void
gs_function_AdOt_free_params(gs_function_AdOt_params_t * params,
			     gs_memory_t * mem)
{
    fn_free_functions(params->Functions, params->n, mem);
    fn_common_free_params((gs_function_params_t *) params, mem);
}

/* Allocate and initialize an Arrayed Output function. */
int
gs_function_AdOt_init(gs_function_t ** ppfn,
		const gs_function_AdOt_params_t * params, gs_memory_t * mem)
{
    static const gs_function_head_t function_AdOt_head = {
	function_type_ArrayedOutput,
	{
	    (fn_evaluate_proc_t) fn_AdOt_evaluate,
	    (fn_is_monotonic_proc_t) fn_AdOt_is_monotonic,
	    gs_function_get_info_default, /****** WRONG ******/
	    fn_common_get_params,	/****** WHAT TO DO ABOUT THIS? ******/
	    (fn_free_params_proc_t) gs_function_AdOt_free_params,
	    fn_common_free
	}
    };
    int m = params->m, n = params->n;
    int i;
    int is_monotonic = 0;	/* initialize to pacify compiler */

    *ppfn = 0;			/* in case of error */
    if (m <= 0 || n <= 0)
	return_error(gs_error_rangecheck);
    for (i = 0; i < n; ++i) {
	const gs_function_t *psubfn = params->Functions[i];
	int sub_mono;

	if (psubfn->params.m != m || psubfn->params.n != 1)
	    return_error(gs_error_rangecheck);
	sub_mono = fn_domain_is_monotonic(psubfn, EFFORT_MODERATE);
	if (i == 0 || sub_mono < 0)
	    is_monotonic = sub_mono;
	else if (is_monotonic >= 0)
	    is_monotonic &= sub_mono;
    }
    {
	gs_function_AdOt_t *pfn =
	    gs_alloc_struct(mem, gs_function_AdOt_t, &st_function_AdOt,
			    "gs_function_AdOt_init");

	if (pfn == 0)
	    return_error(gs_error_VMerror);
	pfn->params = *params;
	pfn->params.Domain = 0;
	pfn->params.Range = 0;
	pfn->head = function_AdOt_head;
	pfn->head.is_monotonic = is_monotonic;
	*ppfn = (gs_function_t *) pfn;
    }
    return 0;
}
