/* Copyright (C) 1998 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: gsparam2.c,v 1.2.6.1.2.1 2003/01/17 00:49:03 giles Exp $ */
/* Serialize and unserialize parameter lists */

/* Initial version 2/1/98 by John Desrosiers (soho@crl.com) */
/* 8/8/98 L. Peter Deutsch (ghost@aladdin.com) Rewritten to use streams */

#include "gx.h"
#include "memory_.h"
#include "gserrors.h"
#include "gsparams.h"

#define MAX_PARAM_KEY 255

/* ---------------- Serializer ---------------- */

/* Forward references */
private int sput_word(P2(stream *dest, uint value));
private int sput_bytes(P3(stream *dest, const byte *data, uint size));

/*
 * Serialize the contents of a gs_param_list, including sub-dictionaries,
 * onto a stream.  The list must be in READ mode.
 */
int
gs_param_list_puts(stream *dest, gs_param_list *list)
{
    int code = 0;
    gs_param_enumerator_t key_enum;
    gs_param_key_t key;

    param_init_enumerator(&key_enum);

    /* Each item is serialized as ("word" means compressed word):
     *  word: key sizeof + 1, or 0 if end of list/dict
     *  word: data type(gs_param_type_xxx)
     *  byte[]: key, including trailing \0 
     *  (if simple type)
     *    byte[]: unpacked representation of data
     *  (if simple array or string)
     *    word: # of elements
     *    byte[]: data associated with array contents
     *  (if string/name array)
     *    word: # of elements
     *    { word: length of string
     *      byte[]: string data
     *    } for each string in array
     *  (if dict/dict_int_keys/hetero_array)
     *    word: # of entries in collection
     *    entries follow immediately
     */
    /* Enumerate all the keys; use keys to get their typed values */
    while ((code = param_get_next_key(list, &key_enum, &key)) == 0) {
	int value_top_sizeof;
	int value_base_sizeof;
	const void *data;
	uint size;

	/* Get next datum & put its type & key to stream */
	gs_param_typed_value value;
	char string_key[MAX_PARAM_KEY + 1];

	if (sizeof(string_key) < key.size + 1) {
	    code = gs_note_error(gs_error_rangecheck);
	    break;
	}
	memcpy(string_key, key.data, key.size);
	string_key[key.size] = 0;
	if ((code = param_read_typed(list, string_key, &value)) != 0) {
	    if (code > 0)
		code = gs_note_error(gs_error_unknownerror);
	    break;
	}
	sput_word(dest, (unsigned)key.size + 1);
	sput_word(dest, (unsigned)value.type);
	sput_bytes(dest, (byte *)string_key, key.size + 1);

	/* Put value & its size to stream */
	value_top_sizeof = gs_param_type_sizes[value.type];
	value_base_sizeof = gs_param_type_base_sizes[value.type];
	switch (value.type) {
	    case gs_param_type_bool:
	    case gs_param_type_int:
	    case gs_param_type_long:
	    case gs_param_type_float:
		sput_bytes(dest, (byte *)&value.value, value_top_sizeof);
	    case gs_param_type_null:
		break;

	    case gs_param_type_string:
		data = value.value.s.data, size = value.value.s.size;
		goto scalar_array;
	    case gs_param_type_name:
		data = value.value.n.data, size = value.value.n.size;
		goto scalar_array;
	    case gs_param_type_int_array:
		data = value.value.ia.data, size = value.value.ia.size;
		goto scalar_array;
	    case gs_param_type_float_array:
		data = value.value.fa.data, size = value.value.fa.size;
scalar_array:	sput_word(dest, size);
		sput_bytes(dest, data, value_base_sizeof * size);
		break;

	    case gs_param_type_string_array:
		data = value.value.sa.data, size = value.value.sa.size;
		goto string_array;
	    case gs_param_type_name_array:
		data = value.value.na.data, size = value.value.na.size;
string_array:	sput_word(dest, size);
		{
		    uint count;
		    const gs_param_string *sa;

		    for (count = size, sa = data; count-- > 0; ++sa) {
			sput_word(dest, sa->size);
			sput_bytes(dest, sa->data, sa->size);
		    }
		}
		break;

	    case gs_param_type_dict:
	    case gs_param_type_dict_int_keys:
	    case gs_param_type_array:
		sput_word(dest, value.value.d.size);
		code = gs_param_list_puts(dest, value.value.d.list);
		{
		    int end_code =
			param_end_read_dict(list, key.data, &value.value.d);

		    if (code >= 0)
			code = end_code;
		}
		break;

	    default:
		code = gs_note_error(gs_error_unknownerror);
		break;
	}
	if (code < 0)
	    break;
    }

    /* Write end marker, which is an (illegal) 0 key length */
    return (code < 0 ? code : sput_word(dest, 0));
}

/* Put a variable-length value on a stream. */
private int
sput_word(stream *dest, uint value)
{
    int code = 0;

    do {
	byte chunk = value & 0x7f;

	if ((value >>= 7) != 0)
	    chunk |= 0x80;
	if ((code = sputc(dest, chunk)) < 0)
	    break;
    }
    while (value != 0);
    return code;
}

/* Put bytes on a stream. */
private int
sput_bytes(stream *dest, const byte *data, uint size)
{
    uint ignore_count;

    return sputs(dest, data, size, &ignore_count);
}

/* ---------------- Unserializer ---------------- */

/* Forward references */
private int sget_word(P2(stream *src, uint *pvalue));
private int sget_bytes(P3(stream *src, byte *data, uint size));

/*
 * Unserialize a parameter list from a stream.  The list must be in WRITE
 * mode.
 */
int
gs_param_list_gets(stream *src, gs_param_list *list, gs_memory_t *mem)
{
    int code = 0;

    do {
	gs_param_typed_value typed;
	uint key_sizeof;
	int value_top_sizeof;
	int value_base_sizeof;
	uint temp;
	void *data;
	uint size;
	gs_param_type type;
	char string_key[MAX_PARAM_KEY + 1];

	/* key length 0 indicates end of data */
	if ((code = sget_word(src, &key_sizeof)) < 0 ||
	    key_sizeof == 0 ||
	    /* data type */
	    (code = sget_word(src, &temp)) < 0)
	    break;

	if (key_sizeof > sizeof(string_key)) {
	    code = gs_note_error(gs_error_rangecheck);
	    break;
	}
	/* key */
	code = sget_bytes(src, (byte *)string_key, key_sizeof);
	if (code < 0)
	    break;

	/* Data values */
	type = (gs_param_type)temp;
	value_top_sizeof = gs_param_type_sizes[type];
	value_base_sizeof = gs_param_type_base_sizes[type];
	typed.type = type;
	switch (type) {
	    case gs_param_type_bool:
	    case gs_param_type_int:
	    case gs_param_type_long:
	    case gs_param_type_float:
		code = sget_bytes(src, (byte *)&typed.value, value_top_sizeof);
	    case gs_param_type_null:
		goto put;
	    default:
		;
	}
	/* All other data values start with a size. */
	code = sget_word(src, &size);
	if (code < 0)
	    break;

	switch (type) {
	    case gs_param_type_string:
	    case gs_param_type_name:
	    case gs_param_type_int_array:
	    case gs_param_type_float_array:
		data =
		    (value_base_sizeof == 1 ?
		     gs_alloc_string(mem, size, "param string/name") :
		     gs_alloc_byte_array(mem, size, value_base_sizeof,
					 "param scalar array"));
		if (data == 0) {
		    code = gs_note_error(gs_error_VMerror);
		    break;
		}
		typed.value.s.data = data;
		typed.value.s.persistent = false;
		typed.value.s.size = size;
		code = sget_bytes(src, data, size * value_base_sizeof);
		break;

	    case gs_param_type_string_array:
	    case gs_param_type_name_array:
		/****** SHOULD BE STRUCT ARRAY ******/
		data = gs_alloc_byte_array(mem, size, value_top_sizeof,
					   "param string/name array");
		if (data == 0) {
		    code = gs_note_error(gs_error_VMerror);
		    break;
		}
		typed.value.sa.data = data;
		typed.value.sa.persistent = false;
		typed.value.sa.size = size;
		{
		    gs_param_string *sa = data;
		    byte *str_data;
		    uint index, str_size;

		    /* Clean pointers in case we bail out. */
		    for (index = 0; index < size; ++index)
			sa[index].data = 0, sa[index].size = 0;
		    for (index = 0; index < size; ++index, ++sa) {
			code = sget_word(src, &str_size);
			if (code < 0)
			    break;
			str_data = gs_alloc_string(mem, str_size,
						"param string/name element");
			if (str_data == 0) {
			    code = gs_note_error(gs_error_VMerror);
			    break;
			}
			code = sget_bytes(src, str_data, str_size);
			if (code < 0)
			    break;
		    }
		}
		break;

	    case gs_param_type_dict:
	    case gs_param_type_dict_int_keys:
	    case gs_param_type_array:
		typed.value.d.size = size;
		code = param_begin_write_collection
		    (list, string_key, &typed.value.d,
		     type - gs_param_type_dict);
		if (code < 0)
		    break;
		code = gs_param_list_gets(src, typed.value.d.list, mem);
		{
		    int end_code =
			param_end_write_collection(list, string_key,
						   &typed.value.d);

		    if (code >= 0)
			code = end_code;
		}
		break;

	    default:
		code = gs_note_error(gs_error_unknownerror);
		break;
	}
put:	if (code < 0)
	    break;
	if (typed.type != gs_param_type_dict &&
	    typed.type != gs_param_type_dict_int_keys &&
	    typed.type != gs_param_type_array
	    )
	    code = param_write_typed(list, string_key, &typed);
    }
    while (code >= 0);

    return code;
}


/* ---------- Utility functions -------- */

/* Get a value stored with sput_word */
private int
sget_word(stream *src, uint *pvalue)
{
    uint value = 0;
    int chunk;
    uint shift = 0;

    do {
	chunk = sgetc(src);
	if (chunk < 0)
	    return chunk;
	value |= (chunk & 0x7f) << shift;
	shift += 7;
    }
    while (chunk & 0x80);

    *pvalue = value;
    return 0;
}

/* Get bytes from a stream */
private int
sget_bytes(stream *src, byte *data, uint size)
{
    uint ignore_count;

    return sgets(src, data, size, &ignore_count);
}
