/* Copyright (C) 1989, 1995, 1997, 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: idict.h,v 1.2.6.1.2.1 2003/01/17 00:49:04 giles Exp $ */
/* Interfaces for Ghostscript dictionary package */

#ifndef idict_INCLUDED
#  define idict_INCLUDED

#include "iddstack.h"

/*
 * Contrary to our usual practice, we expose the (first-level)
 * representation of a dictionary in the interface file,
 * because it is so important that access checking go fast.
 * The access attributes for the dictionary are stored in
 * the values ref.
 */
struct dict_s {
    ref values;			/* t_array, values */
    ref keys;			/* t_shortarray or t_array, keys */
    ref count;			/* t_integer, count of occupied entries */
    /* (length) */
    ref maxlength;		/* t_integer, maxlength as seen by client. */
    ref memory;			/* foreign t_struct, the allocator that */
    /* created this dictionary */
#define dict_memory(pdict) r_ptr(&(pdict)->memory, gs_ref_memory_t)
};

/*
 * Define the maximum size of a dictionary.
 */
extern const uint dict_max_size;

/*
 * Define whether dictionaries expand automatically when full.  Note that
 * if dict_auto_expand is true, dict_put, dict_copy, dict_resize, and
 * dict_grow cannot return e_dictfull; however, they can return e_VMerror.
 * (dict_find can return e_dictfull even if dict_auto_expand is true.)
 */
extern bool dict_auto_expand;

/*
 * Create a dictionary.
 */
#ifndef gs_ref_memory_DEFINED
#  define gs_ref_memory_DEFINED
typedef struct gs_ref_memory_s gs_ref_memory_t;
#endif
int dict_alloc(P3(gs_ref_memory_t *, uint maxlength, ref * pdref));

#define dict_create(maxlen, pdref)\
  dict_alloc(iimemory, maxlen, pdref)

/*
 * Return a pointer to a ref that holds the access attributes
 * for a dictionary.
 */
#define dict_access_ref(pdref) (&(pdref)->value.pdict->values)
/*
 * Check a dictionary for read or write permission.
 * Note: this does NOT check the type of its operand!
 */
#define check_dict_read(dref) check_read(*dict_access_ref(&dref))
#define check_dict_write(dref) check_write(*dict_access_ref(&dref))

/*
 * Look up a key in a dictionary.  Store a pointer to the value slot
 * where found, or to the (value) slot for inserting.
 * The caller is responsible for checking that the dictionary is readable.
 * Return 1 if found, 0 if not and there is room for a new entry,
 * Failure returns:
 *      e_typecheck if the key is null;
 *      e_invalidaccess if the key is a string lacking read access;
 *      e_VMerror or e_limitcheck if the key is a string and the corresponding
 *        error occurs from attempting to convert it to a name;
 *      e_dictfull if the dictionary is full and the key is missing.
 */
int dict_find(P3(const ref * pdref, const ref * key, ref ** ppvalue));

/*
 * Look up a (constant) C string in a dictionary.
 * Return 1 if found, <= 0 if not.
 */
int dict_find_string(P3(const ref * pdref, const char *kstr, ref ** ppvalue));

/*
 * Enter a key-value pair in a dictionary.
 * The caller is responsible for checking that the dictionary is writable.
 * Return 1 if this was a new entry, 0 if this replaced an existing entry.
 * Failure returns are as for dict_find, except that e_dictfull doesn't
 * occur if the dictionary is full but expandable, plus:
 *      e_invalidaccess for an attempt to store a younger key or value into
 *        an older dictionary, or as described just below;
 *      e_VMerror if a VMerror occurred while trying to expand the
 *        dictionary.
 * Note that this procedure, and all procedures that may change the
 * contents of a dictionary, take a pointer to a dictionary stack,
 * so they can update the cached 'top' values and also update the cached
 * value pointer in names.  A NULL pointer for the dictionary stack is
 * allowed, but in this case, if the dictionary is present on any dictionary
 * stack, an e_invalidaccess error will occur if cached values need updating.
 * THIS ERROR CHECK IS NOT IMPLEMENTED YET.
 */
int dict_put(P4(ref * pdref, const ref * key, const ref * pvalue,
		dict_stack_t *pds));

/*
 * Enter a key-value pair where the key is a (constant) C string.
 */
int dict_put_string(P4(ref * pdref, const char *kstr, const ref * pvalue,
		       dict_stack_t *pds));

/*
 * Remove a key-value pair from a dictionary.
 * Return 0 or e_undefined.
 */
int dict_undef(P3(ref * pdref, const ref * key, dict_stack_t *pds));

/*
 * Return the number of elements in a dictionary.
 */
uint dict_length(P1(const ref * pdref));

/*
 * Return the capacity of a dictionary.
 */
uint dict_maxlength(P1(const ref * pdref));

/*
 * Return the maximum index of a slot within a dictionary.
 * Note that this may be greater than maxlength.
 */
uint dict_max_index(P1(const ref * pdref));

/*
 * Copy one dictionary into another.
 * Return 0 or e_dictfull.
 * If new_only is true, only copy entries whose keys
 * aren't already present in the destination.
 */
int dict_copy_entries(P4(const ref * dfrom, ref * dto, bool new_only,
			 dict_stack_t *pds));

#define dict_copy(dfrom, dto, pds) dict_copy_entries(dfrom, dto, false, pds)
#define dict_copy_new(dfrom, dto, pds) dict_copy_entries(dfrom, dto, true, pds)

/*
 * Grow or shrink a dictionary.
 * Return 0, e_dictfull, or e_VMerror.
 */
int dict_resize(P3(ref * pdref, uint newmaxlength, dict_stack_t *pds));

/*
 * Grow a dictionary in the same way as dict_put does.
 * We export this for some special-case code in zop_def.
 */
int dict_grow(P2(ref * pdref, dict_stack_t *pds));

/*
 * Ensure that a dictionary uses the unpacked representation for keys.
 * (This is of no interest to ordinary clients.)
 */
int dict_unpack(P2(ref * pdref, dict_stack_t *pds));

/*
 * Prepare to enumerate a dictionary.
 * Return an integer suitable for the first call to dict_next.
 */
int dict_first(P1(const ref * pdref));

/*
 * Enumerate the next element of a dictionary.
 * index is initially the result of a call on dict_first.
 * Either store a key and value at eltp[0] and eltp[1]
 * and return an updated index, or return -1
 * to signal that there are no more elements in the dictionary.
 */
int dict_next(P3(const ref * pdref, int index, ref * eltp));

/*
 * Given a value pointer return by dict_find, return an index that
 * identifies the entry within the dictionary. (This may, but need not,
 * be the same as the index returned by dict_next.)
 * The index is in the range [0..max_index-1].
 */
int dict_value_index(P2(const ref * pdref, const ref * pvalue));

/*
 * Given an index in [0..max_index-1], as returned by dict_value_index,
 * return the key and value, as returned by dict_next.
 * If the index designates an unoccupied entry, return e_undefined.
 */
int dict_index_entry(P3(const ref * pdref, int index, ref * eltp));

/*
 * The following are some internal details that are used in both the
 * implementation and some high-performance clients.
 */

/* On machines with reasonable amounts of memory, we round up dictionary
 * sizes to the next power of 2 whenever possible, to allow us to use
 * masking rather than division for computing the hash index.
 * Unfortunately, if we required this, it would cut the maximum size of a
 * dictionary in half.  Therefore, on such machines, we distinguish
 * "huge" dictionaries (dictionaries whose size is larger than the largest
 * power of 2 less than dict_max_size) as a special case:
 *
 *      - If the top dictionary on the stack is huge, we set the dtop
 *      parameters so that the fast inline lookup will always fail.
 *
 *      - For general lookup, we use the slower hash_mod algorithm for
 *      huge dictionaries.
 */
#define dict_max_non_huge ((uint)(max_array_size / 2 + 1))

/* Define the hashing function for names. */
/* We don't have to scramble the index, because */
/* indices are assigned in a scattered order (see name_ref in iname.c). */
#define dict_name_index_hash(nidx) (nidx)

/* Hash an arbitrary non-negative or unsigned integer into a dictionary. */
#define dict_hash_mod_rem(hash, size) ((hash) % (size))
#define dict_hash_mod_mask(hash, size) ((hash) & ((size) - 1))
#define dict_hash_mod_small(hash, size) dict_hash_mod_rem(hash, size)
#define dict_hash_mod_inline_small(hash, size) dict_hash_mod_rem(hash, size)
#define dict_hash_mod_large(hash, size)\
  (size > dict_max_non_huge ? dict_hash_mod_rem(hash, size) :\
   dict_hash_mod_mask(hash, size))
#define dict_hash_mod_inline_large(hash, size) dict_hash_mod_mask(hash, size)
/* Round up the requested size of a dictionary.  Return 0 if too big. */
uint dict_round_size_small(P1(uint rsize));
uint dict_round_size_large(P1(uint rsize));

/* Choose the algorithms depending on the size of memory. */
#if arch_small_memory
#  define dict_hash_mod(h, s) dict_hash_mod_small(h, s)
#  define dict_hash_mod_inline(h, s) dict_hash_mod_inline_small(h, s)
#  define dict_round_size(s) dict_round_size_small(s)
#else
#  ifdef DEBUG
#    define dict_hash_mod(h, s)\
       (gs_debug_c('.') ? dict_hash_mod_small(h, s) :\
	dict_hash_mod_large(h, s))
#    define dict_hash_mod_inline(h, s)\
       (gs_debug_c('.') ? dict_hash_mod_inline_small(h, s) :\
	dict_hash_mod_inline_large(h, s))
#    define dict_round_size(s)\
       (gs_debug_c('.') ? dict_round_size_small(s) :\
	dict_round_size_large(s))
#  else
#    define dict_hash_mod(h, s) dict_hash_mod_large(h, s)
#    define dict_hash_mod_inline(h, s) dict_hash_mod_inline_large(h, s)
#    define dict_round_size(s) dict_round_size_large(s)
#  endif
#endif

#endif /* idict_INCLUDED */
