/* Copyright (C) 1995, 1998, 1999 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: gsparam.c,v 1.4.6.1.2.1 2003/01/17 00:49:03 giles Exp $ */
/* Support for parameter lists */
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsparam.h"
#include "gsstruct.h"

/* GC procedures */
ENUM_PTRS_WITH(gs_param_typed_value_enum_ptrs, gs_param_typed_value *pvalue) return 0;
    case 0:
    switch (pvalue->type) {
    case gs_param_type_string:
	return ENUM_STRING(&pvalue->value.s);
    case gs_param_type_name:
	return ENUM_STRING(&pvalue->value.n);
    case gs_param_type_int_array:
	return ENUM_OBJ(pvalue->value.ia.data);
    case gs_param_type_float_array:
	return ENUM_OBJ(pvalue->value.fa.data);
    case gs_param_type_string_array:
	return ENUM_OBJ(pvalue->value.sa.data);
    case gs_param_type_name_array:
	return ENUM_OBJ(pvalue->value.na.data);
    default:
	return ENUM_OBJ(0);	/* don't stop early */
    }
ENUM_PTRS_END
RELOC_PTRS_WITH(gs_param_typed_value_reloc_ptrs, gs_param_typed_value *pvalue) {
    switch (pvalue->type) {
    case gs_param_type_string:
    case gs_param_type_name: {
	gs_const_string str;

	str.data = pvalue->value.s.data; /* n == s */
	str.size = pvalue->value.s.size;
	RELOC_CONST_STRING_VAR(str);
	pvalue->value.s.data = str.data;
	break;
    }
    case gs_param_type_int_array:
	RELOC_VAR(pvalue->value.ia.data);
	break;
    case gs_param_type_float_array:
	RELOC_VAR(pvalue->value.fa.data);
	break;
    case gs_param_type_string_array:
	RELOC_VAR(pvalue->value.sa.data);
	break;
    case gs_param_type_name_array:
	RELOC_VAR(pvalue->value.na.data);
	break;
    default:
	break;
    }
}
RELOC_PTRS_END

/* Internal procedure to initialize the common part of a parameter list. */
void
gs_param_list_init(gs_param_list *plist, const gs_param_list_procs *procs,
		   gs_memory_t *mem)
{
    plist->procs = procs;
    plist->memory = mem;
    plist->persistent_keys = true;
}

/* Set whether the keys for param_write_XXX are persistent. */
void
gs_param_list_set_persistent_keys(gs_param_list *plist, bool persistent)
{
    plist->persistent_keys = persistent;
}

/* Reset a gs_param_key_t enumerator to its initial state */
void
param_init_enumerator(gs_param_enumerator_t * enumerator)
{
    memset(enumerator, 0, sizeof(*enumerator));
}

/* Transfer a collection of parameters. */
private const byte xfer_item_sizes[] = {
    GS_PARAM_TYPE_SIZES(0)
};
int
gs_param_read_items(gs_param_list * plist, void *obj,
		    const gs_param_item_t * items)
{
    const gs_param_item_t *pi;
    int ecode = 0;

    for (pi = items; pi->key != 0; ++pi) {
	const char *key = pi->key;
	void *pvalue = (void *)((char *)obj + pi->offset);
	gs_param_typed_value typed;
	int code;

	typed.type = pi->type;
	code = param_read_requested_typed(plist, key, &typed);
	switch (code) {
	    default:		/* < 0 */
		ecode = code;
	    case 1:
		break;
	    case 0:
		if (typed.type != pi->type)	/* shouldn't happen! */
		    ecode = gs_note_error(gs_error_typecheck);
		else
		    memcpy(pvalue, &typed.value, xfer_item_sizes[pi->type]);
	}
    }
    return ecode;
}
int
gs_param_write_items(gs_param_list * plist, const void *obj,
		     const void *default_obj, const gs_param_item_t * items)
{
    const gs_param_item_t *pi;
    int ecode = 0;

    for (pi = items; pi->key != 0; ++pi) {
	const char *key = pi->key;
	const void *pvalue = (const void *)((const char *)obj + pi->offset);
	int size = xfer_item_sizes[pi->type];
	gs_param_typed_value typed;
	int code;

	if (default_obj != 0 &&
	    !memcmp((const void *)((const char *)default_obj + pi->offset),
		    pvalue, size)
	    )
	    continue;
	memcpy(&typed.value, pvalue, size);
	typed.type = pi->type;
	code = (*plist->procs->xmit_typed) (plist, key, &typed);
	if (code < 0)
	    ecode = code;
    }
    return ecode;
}

/* Read a value, with coercion if requested, needed, and possible. */
/* If mem != 0, we can coerce int arrays to float arrays. */
int
param_coerce_typed(gs_param_typed_value * pvalue, gs_param_type req_type,
		   gs_memory_t * mem)
{
    if (req_type == gs_param_type_any || pvalue->type == req_type)
	return 0;
    /*
     * Look for coercion opportunities.  It would be wonderful if we
     * could convert int/float arrays and name/string arrays, but
     * right now we can't.  However, a 0-length heterogenous array
     * will satisfy a request for any specific type.
     */
    switch (pvalue->type /* actual type */ ) {
	case gs_param_type_int:
	    switch (req_type) {
		case gs_param_type_long:
		    pvalue->value.l = pvalue->value.i;
		    goto ok;
		case gs_param_type_float:
		    pvalue->value.f = (float)pvalue->value.l;
		    goto ok;
		default:
		    break;
	    }
	    break;
	case gs_param_type_long:
	    switch (req_type) {
		case gs_param_type_int:
#if arch_sizeof_int < arch_sizeof_long
		    if (pvalue->value.l != (int)pvalue->value.l)
			return_error(gs_error_rangecheck);
#endif
		    pvalue->value.i = (int)pvalue->value.l;
		    goto ok;
		case gs_param_type_float:
		    pvalue->value.f = (float)pvalue->value.l;
		    goto ok;
		default:
		    break;
	    }
	    break;
	case gs_param_type_string:
	    if (req_type == gs_param_type_name)
		goto ok;
	    break;
	case gs_param_type_name:
	    if (req_type == gs_param_type_string)
		goto ok;
	    break;
	case gs_param_type_int_array:
	    switch (req_type) {
		case gs_param_type_float_array:{
			uint size = pvalue->value.ia.size;
			float *fv;
			uint i;

			if (mem == 0)
			    break;
			fv = (float *)gs_alloc_byte_array(mem, size, sizeof(float),
						"int array => float array");

			if (fv == 0)
			    return_error(gs_error_VMerror);
			for (i = 0; i < size; ++i)
			    fv[i] = pvalue->value.ia.data[i];
			pvalue->value.fa.data = fv;
			pvalue->value.fa.persistent = false;
			goto ok;
		    }
		default:
		    break;
	    }
	    break;
	case gs_param_type_string_array:
	    if (req_type == gs_param_type_name_array)
		goto ok;
	    break;
	case gs_param_type_name_array:
	    if (req_type == gs_param_type_string_array)
		goto ok;
	    break;
	case gs_param_type_array:
	    if (pvalue->value.d.size == 0 &&
		(req_type == gs_param_type_int_array ||
		 req_type == gs_param_type_float_array ||
		 req_type == gs_param_type_string_array ||
		 req_type == gs_param_type_name_array)
		)
		goto ok;
	    break;
	default:
	    break;
    }
    return_error(gs_error_typecheck);
  ok:pvalue->type = req_type;
    return 0;
}
int
param_read_requested_typed(gs_param_list * plist, gs_param_name pkey,
			   gs_param_typed_value * pvalue)
{
    gs_param_type req_type = pvalue->type;
    int code = (*plist->procs->xmit_typed) (plist, pkey, pvalue);

    if (code != 0)
	return code;
    return param_coerce_typed(pvalue, req_type, plist->memory);
}


/* ---------------- Fixed-type reading procedures ---------------- */

#define RETURN_READ_TYPED(alt, ptype)\
  gs_param_typed_value typed;\
  int code;\
\
  typed.type = ptype;\
  code = param_read_requested_typed(plist, pkey, &typed);\
  if ( code == 0 )\
    *pvalue = typed.value.alt;\
  return code

int
param_read_null(gs_param_list * plist, gs_param_name pkey)
{
    gs_param_typed_value typed;

    typed.type = gs_param_type_null;
    return param_read_requested_typed(plist, pkey, &typed);
}
int
param_read_bool(gs_param_list * plist, gs_param_name pkey, bool * pvalue)
{
    RETURN_READ_TYPED(b, gs_param_type_bool);
}
int
param_read_int(gs_param_list * plist, gs_param_name pkey, int *pvalue)
{
    RETURN_READ_TYPED(i, gs_param_type_int);
}
int
param_read_long(gs_param_list * plist, gs_param_name pkey, long *pvalue)
{
    RETURN_READ_TYPED(l, gs_param_type_long);
}
int
param_read_float(gs_param_list * plist, gs_param_name pkey, float *pvalue)
{
    RETURN_READ_TYPED(f, gs_param_type_float);
}
int
param_read_string(gs_param_list * plist, gs_param_name pkey,
		  gs_param_string * pvalue)
{
    RETURN_READ_TYPED(s, gs_param_type_string);
}
int
param_read_name(gs_param_list * plist, gs_param_name pkey,
		gs_param_string * pvalue)
{
    RETURN_READ_TYPED(n, gs_param_type_string);
}
int
param_read_int_array(gs_param_list * plist, gs_param_name pkey,
		     gs_param_int_array * pvalue)
{
    RETURN_READ_TYPED(ia, gs_param_type_int_array);
}
int
param_read_float_array(gs_param_list * plist, gs_param_name pkey,
		       gs_param_float_array * pvalue)
{
    RETURN_READ_TYPED(fa, gs_param_type_float_array);
}
int
param_read_string_array(gs_param_list * plist, gs_param_name pkey,
			gs_param_string_array * pvalue)
{
    RETURN_READ_TYPED(sa, gs_param_type_string_array);
}
int
param_read_name_array(gs_param_list * plist, gs_param_name pkey,
		      gs_param_string_array * pvalue)
{
    RETURN_READ_TYPED(na, gs_param_type_name_array);
}

#undef RETURN_READ_TYPED

/* ---------------- Default writing procedures ---------------- */

#define RETURN_WRITE_TYPED(alt, ptype)\
  gs_param_typed_value typed;\
\
  typed.value.alt = *pvalue;\
  typed.type = ptype;\
  return param_write_typed(plist, pkey, &typed)

int
param_write_null(gs_param_list * plist, gs_param_name pkey)
{
    gs_param_typed_value typed;

    typed.type = gs_param_type_null;
    return param_write_typed(plist, pkey, &typed);
}
int
param_write_bool(gs_param_list * plist, gs_param_name pkey, const bool * pvalue)
{
    RETURN_WRITE_TYPED(b, gs_param_type_bool);
}
int
param_write_int(gs_param_list * plist, gs_param_name pkey, const int *pvalue)
{
    RETURN_WRITE_TYPED(i, gs_param_type_int);
}
int
param_write_long(gs_param_list * plist, gs_param_name pkey, const long *pvalue)
{
    RETURN_WRITE_TYPED(l, gs_param_type_long);
}
int
param_write_float(gs_param_list * plist, gs_param_name pkey,
		  const float *pvalue)
{
    RETURN_WRITE_TYPED(f, gs_param_type_float);
}
int
param_write_string(gs_param_list * plist, gs_param_name pkey,
		   const gs_param_string * pvalue)
{
    RETURN_WRITE_TYPED(s, gs_param_type_string);
}
int
param_write_name(gs_param_list * plist, gs_param_name pkey,
		 const gs_param_string * pvalue)
{
    RETURN_WRITE_TYPED(n, gs_param_type_name);
}
int
param_write_int_array(gs_param_list * plist, gs_param_name pkey,
		      const gs_param_int_array * pvalue)
{
    RETURN_WRITE_TYPED(ia, gs_param_type_int_array);
}
int
param_write_int_values(gs_param_list * plist, gs_param_name pkey,
		       const int *values, uint size, bool persistent)
{
    gs_param_int_array ia;

    ia.data = values, ia.size = size, ia.persistent = persistent;
    return param_write_int_array(plist, pkey, &ia);
}
int
param_write_float_array(gs_param_list * plist, gs_param_name pkey,
			const gs_param_float_array * pvalue)
{
    RETURN_WRITE_TYPED(fa, gs_param_type_float_array);
}
int
param_write_float_values(gs_param_list * plist, gs_param_name pkey,
			 const float *values, uint size, bool persistent)
{
    gs_param_float_array fa;

    fa.data = values, fa.size = size, fa.persistent = persistent;
    return param_write_float_array(plist, pkey, &fa);
}
int
param_write_string_array(gs_param_list * plist, gs_param_name pkey,
			 const gs_param_string_array * pvalue)
{
    RETURN_WRITE_TYPED(sa, gs_param_type_string_array);
}
int
param_write_name_array(gs_param_list * plist, gs_param_name pkey,
		       const gs_param_string_array * pvalue)
{
    RETURN_WRITE_TYPED(na, gs_param_type_name_array);
}

#undef RETURN_WRITE_TYPED

/* ---------------- Default request implementation ---------------- */

int
gs_param_request_default(gs_param_list * plist, gs_param_name pkey)
{
    return 0;
}

int
gs_param_requested_default(const gs_param_list * plist, gs_param_name pkey)
{
    return -1;			/* requested by default */
}
