/***************************************************************************

    options.c

    Core options code code

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

***************************************************************************/

#include <stdarg.h>
#include <ctype.h>
#include "options.h"
#include "astring.h"



/***************************************************************************
    CONSTANTS
***************************************************************************/

#define MAX_ENTRY_NAMES		4



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/* forward references */
typedef struct _options_data options_data;


/* range parameter for minimum/maximum values */
typedef union _options_range_parameter options_range_parameter;
union _options_range_parameter
{
	float 					f;
	int 					i;
};


/* hash table entries */
typedef struct _options_hash_entry options_hash_entry;
struct _options_hash_entry
{
	options_hash_entry *	next;				/* link to the next entry */
	astring *				name;				/* name under the current link */
	options_data *			data;				/* link to associated options_data */
};


/* information about a single entry in the options */
struct _options_data
{
	options_hash_entry		links[MAX_ENTRY_NAMES]; /* array of hash table entries (one per name) */
	options_data *			next;				/* link to the next data */
	UINT32					flags;				/* flags from the entry */
	UINT32					seqid;				/* sequence ID; bumped on each change */
	int						error_reported;		/* have we reported an error on this option yet? */
	int						priority;			/* priority of the data set */
	astring *				data;				/* data for this item */
	astring *				defdata;			/* default data for this item */
	const char *			description;		/* description for this item */
	options_range_type		range_type;			/* the type of range to apply to this item */
	options_range_parameter	range_minimum;		/* the minimum of the range */
	options_range_parameter	range_maximum;		/* the maximum of the range */
	void					(*callback)(core_options *opts, const char *arg);	/* callback to be invoked when parsing */
};


/* structure holding information about a collection of options */
struct _core_options
{
	void (*output[OPTMSG_COUNT])(const char *s);/* output callbacks */

	/* linked list, for sequential iteration */
	options_data *			datalist;			/* linked list for sequential iteration */
	options_data **			datalist_nextptr;	/* pointer to pointer to tail entry */

	/* hashtable, for fast lookup */
	options_hash_entry *	hashtable[101];		/* hash table for fast lookup */
};


/* information about an in-progress options enumeration */
struct _options_enumerator
{
	options_data *			current;
};



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

static options_data *find_entry_data(core_options *opts, const char *string, int is_command_line);
static void update_data(core_options *opts, options_data *data, const char *newdata, int priority);
static int parse_option_name(core_options *opts, const char *srcstring, options_data *data);

static void message(core_options *opts, options_message msgtype, const char *format, ...) ATTR_PRINTF(3,4);
static UINT32 hash_value(core_options *opts, const char *str);
static void output_printf(void (*output)(const char *s), const char *format, ...) ATTR_PRINTF(2,3);



/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

const char *const option_unadorned[MAX_UNADORNED_OPTIONS] =
{
	"<UNADORNED0>",
	"<UNADORNED1>",
	"<UNADORNED2>",
	"<UNADORNED3>",
	"<UNADORNED4>",
	"<UNADORNED5>",
	"<UNADORNED6>",
	"<UNADORNED7>",
	"<UNADORNED8>",
	"<UNADORNED9>",
	"<UNADORNED10>",
	"<UNADORNED11>",
	"<UNADORNED12>",
	"<UNADORNED13>",
	"<UNADORNED14>",
	"<UNADORNED15>"
};



/***************************************************************************
    OPTIONS COLLECTION MANAGEMENT
***************************************************************************/

/*-------------------------------------------------
    options_create - creates a new instance of
    core options
-------------------------------------------------*/

core_options *options_create(void (*fail)(const char *message))
{
	/* allocate memory for the option block */
	core_options *opts = (core_options *)malloc(sizeof(*opts));
	if (opts == NULL)
		goto error;

	/* and set up the structure */
	memset(opts, 0, sizeof(*opts));
	opts->datalist_nextptr = &opts->datalist;
	return opts;

error:
	return NULL;
}


/*-------------------------------------------------
    options_free - frees an options object
-------------------------------------------------*/

void options_free(core_options *opts)
{
	options_data *data, *next;

	/* loop over data items and free them */
	for (data = opts->datalist; data != NULL; data = next)
	{
		int linknum;

		next = data->next;

		/* free names */
		for (linknum = 0; linknum < ARRAY_LENGTH(data->links); linknum++)
			if (data->links[linknum].name != NULL)
				astring_free(data->links[linknum].name);

		/* free strings */
		astring_free(data->data);
		astring_free(data->defdata);

		/* free the data itself */
		free(data);
	}

	/* free the options itself */
	free(opts);
}


/*-------------------------------------------------
    options_set_output_callback - installs a callback
    to be invoked when data is outputted in the
    process of outputting options
-------------------------------------------------*/

void options_set_output_callback(core_options *opts, options_message msgtype, void (*callback)(const char *s))
{
	opts->output[msgtype] = callback;
}


/*-------------------------------------------------
    options_revert - revert options at or below
    a certain priority back to their defaults
-------------------------------------------------*/

void options_revert(core_options *opts, int priority)
{
	options_data *data;

	/* iterate over options and revert to defaults if below the given priority */
	for (data = opts->datalist; data != NULL; data = data->next)
		if (data->priority <= priority)
		{
			astring_cpy(data->data, data->defdata);
			data->priority = OPTION_PRIORITY_DEFAULT;
		}
}


/*-------------------------------------------------
    options_copy - copy options from one core_options
    to another
-------------------------------------------------*/

int options_copy(core_options *dest_opts, core_options *src_opts)
{
	options_data *data;

	/* iterate over options in the destination */
	for (data = dest_opts->datalist; data != NULL; data = data->next)
		if (!(data->flags & OPTION_HEADER))
		{
			options_data *srcdata = find_entry_data(src_opts, astring_c(data->links[0].name), FALSE);

			/* if the option exists in the source, set it in the destination */
			if (srcdata != NULL)
				options_set_string(dest_opts, astring_c(srcdata->links[0].name), astring_c(srcdata->data), srcdata->priority);
		}

	return TRUE;
}


/*-------------------------------------------------
    options_equal - compare two sets of options
-------------------------------------------------*/

int options_equal(core_options *opts1, core_options *opts2)
{
	options_data *data;

	/* iterate over options in the first list */
	for (data = opts1->datalist; data != NULL; data = data->next)
		if (!(data->flags & OPTION_HEADER))
		{
			const char *value1 = options_get_string(opts1, astring_c(data->links[0].name));
			const char *value2 = options_get_string(opts2, astring_c(data->links[0].name));

			/* if the values differ, return false */
			if (strcmp(value1, value2) != 0)
				return FALSE;
		}

	return TRUE;
}



/***************************************************************************
    OPTION DEFINITIONS
***************************************************************************/

/*-------------------------------------------------
    options_add_entries - add entries to the
    current options sets
-------------------------------------------------*/

int options_add_entries(core_options *opts, const options_entry *entrylist)
{
	/* loop over entries until we hit a NULL name */
	for ( ; entrylist->name != NULL || (entrylist->flags & OPTION_HEADER); entrylist++)
	{
		options_data *match = NULL;
		int i;

		/* allocate a new item */
		options_data *data = (options_data *)malloc(sizeof(*data));
		if (data == NULL)
			return FALSE;
		memset(data, 0, sizeof(*data));

		/* parse the option name */
		if (entrylist->name != NULL)
			parse_option_name(opts, entrylist->name, data);

		/* do we match an existing entry? */
		for (i = 0; i < ARRAY_LENGTH(data->links) && match == NULL; i++)
			if (data->links[i].name != NULL)
				match = find_entry_data(opts, astring_c(data->links[i].name), FALSE);

		/* if so, throw away this entry and replace the data */
		if (match != NULL)
		{
			/* free what we've allocated so far */
			for (i = 0; i < ARRAY_LENGTH(data->links); i++)
				if (data->links[i].name != NULL)
					astring_free(data->links[i].name);
			free(data);

			/* use the matching entry as our data */
			data = match;
		}

		/* otherwise, finish making the new entry */
		else
		{
			/* allocate strings */
			data->data = astring_alloc();
			data->defdata = astring_alloc();

			/* copy the flags, and set the value equal to the default */
			data->flags = entrylist->flags;
			data->description = entrylist->description;

			/* add us to the end of the sequential list */
			*opts->datalist_nextptr = data;
			opts->datalist_nextptr = &data->next;

			/* add each name to the appropriate hash table with a link back to us */
			for (i = 0; i < ARRAY_LENGTH(data->links); i++)
				if (data->links[i].name != NULL)
				{
					int hash_entry = hash_value(opts, astring_c(data->links[i].name));

					/* set up link */
					data->links[i].data = data;
					data->links[i].next = opts->hashtable[hash_entry];
					opts->hashtable[hash_entry] = &data->links[i];
				}
		}

		/* copy in the data and default data values */
		if (entrylist->defvalue != NULL)
		{
			astring_cpyc(data->data, entrylist->defvalue);
			astring_cpyc(data->defdata, entrylist->defvalue);
		}
		data->priority = OPTION_PRIORITY_DEFAULT;
	}
	return TRUE;
}


/*-------------------------------------------------
    options_set_option_default_value - change the
    default value of an option
-------------------------------------------------*/

int options_set_option_default_value(core_options *opts, const char *name, const char *defvalue)
{
	options_data *data = find_entry_data(opts, name, TRUE);

	/* if we don't have an entry for this, fail */
	if (data == NULL)
		return FALSE;

	/* update the data and default data; note that we assume that data == defdata */
	astring_cpyc(data->data, defvalue);
	astring_cpyc(data->defdata, defvalue);
	data->priority = OPTION_PRIORITY_DEFAULT;
	return TRUE;
}


/*-------------------------------------------------
    options_set_option_callback - specifies a
    callback to be invoked when parsing options
-------------------------------------------------*/

int options_set_option_callback(core_options *opts, const char *name, void (*callback)(core_options *opts, const char *arg))
{
	options_data *data = find_entry_data(opts, name, TRUE);

	/* if we don't find an entry, fail */
	if (data == NULL)
		return FALSE;

	/* set the callback */
	data->callback = callback;
	return TRUE;
}



/***************************************************************************
    OPTION DATA EXTRACTION
***************************************************************************/

/*-------------------------------------------------
    options_parse_command_line - parse a series
    of command line arguments
-------------------------------------------------*/

int options_parse_command_line(core_options *opts, int argc, char **argv, int priority)
{
	int unadorned_index = 0;
	int arg;

	/* loop over commands, looking for options */
	for (arg = 1; arg < argc; arg++)
	{
		const char *optionname, *newdata;
		options_data *data;
		int is_unadorned;

		/* determine the entry name to search for */
		is_unadorned = (argv[arg][0] != '-');
		if (!is_unadorned)
			optionname = &argv[arg][1];
		else
			optionname = OPTION_UNADORNED(unadorned_index);

		/* find our entry */
		data = find_entry_data(opts, optionname, TRUE);
		if (data == NULL)
		{
			message(opts, OPTMSG_ERROR, "Error: unknown option: %s\n", argv[arg]);
			return 1;
		}

		/* if unadorned, we have to bump the count (unless if the option repeats) */
		if (is_unadorned && !(data->flags & OPTION_REPEATS))
			unadorned_index++;

		/* get the data for this argument, special casing booleans */
		if ((data->flags & (OPTION_BOOLEAN | OPTION_COMMAND)) != 0)
			newdata = (strncmp(&argv[arg][1], "no", 2) == 0) ? "0" : "1";
		else if (argv[arg][0] != '-')
			newdata = argv[arg];
		else if (arg + 1 < argc)
			newdata = argv[++arg];
		else
		{
			message(opts, OPTMSG_ERROR, "Error: option %s expected a parameter\n", argv[arg]);
			return 1;
		}

		/* if the option is deprecated or internal, don't process further */
		if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) != 0)
			continue;

		/* invoke callback, if present */
		if (data->callback != NULL)
			(*data->callback)(opts, newdata);

		/* allocate a new copy of data for this */
		update_data(opts, data, newdata, priority);
	}
	return 0;
}


/*-------------------------------------------------
    options_parse_ini_file - parse a series
    of entries in an INI file
-------------------------------------------------*/

int options_parse_ini_file(core_options *opts, core_file *inifile, int priority)
{
	char buffer[4096];

	/* loop over data */
	while (core_fgets(buffer, ARRAY_LENGTH(buffer), inifile) != NULL)
	{
		char *optionname, *optiondata, *temp;
		options_data *data;
		int inquotes = FALSE;

		/* find the name */
		for (optionname = buffer; *optionname != 0; optionname++)
			if (!isspace(*optionname))
				break;

		/* skip comments */
		if (*optionname == 0 || *optionname == '#')
			continue;

		/* scan forward to find the first space */
		for (temp = optionname; *temp != 0; temp++)
			if (isspace(*temp))
				break;

		/* if we hit the end early, print a warning and continue */
		if (*temp == 0)
		{
			message(opts, OPTMSG_WARNING, "Warning: invalid line in INI: %s", buffer);
			continue;
		}

		/* NULL-terminate */
		*temp++ = 0;
		optiondata = temp;

		/* scan the data, stopping when we hit a comment */
		for (temp = optiondata; *temp != 0; temp++)
		{
			if (*temp == '"')
				inquotes = !inquotes;
			if (*temp == '#' && !inquotes)
				break;
		}
		*temp = 0;

		/* find our entry */
		data = find_entry_data(opts, optionname, FALSE);
		if (data == NULL)
		{
			message(opts, OPTMSG_WARNING, "Warning: unknown option in INI: %s\n", optionname);
			continue;
		}
		if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) != 0)
			continue;

		/* allocate a new copy of data for this */
		update_data(opts, data, optiondata, priority);
	}
	return 0;
}



/***************************************************************************
    OPTIONS OUTPUT
***************************************************************************/

/*-------------------------------------------------
    options_output_diff_ini_file - output the diff
    of the current state from a base state to an
    INI file
-------------------------------------------------*/

void options_output_diff_ini_file(core_options *opts, core_options *baseopts, core_file *inifile)
{
	options_data *data;
	const char *last_header = NULL;
	const char *name;
	const char *value;
	options_data *basedata;

	/* loop over all items */
	for (data = opts->datalist; data != NULL; data = data->next)
	{
		/* header: record description */
		if ((data->flags & OPTION_HEADER) != 0)
			last_header = data->description;

		/* otherwise, output entries for all non-deprecated and non-command items (if not in baseopts) */
		else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL | OPTION_COMMAND)) == 0)
		{
			/* get name and data of this value */
			name = astring_c(data->links[0].name);
			value = astring_c(data->data);

			/* look up counterpart in baseopts, if baseopts is specified */
			basedata = (baseopts != NULL) ? find_entry_data(baseopts, name, FALSE) : NULL;

			/* is our data different, or not in baseopts? */
			if ((basedata == NULL) || (strcmp(value, astring_c(basedata->data)) != 0))
			{
				/* output header, if we have one */
				if (last_header != NULL)
				{
					core_fprintf(inifile, "\n#\n# %s\n#\n", last_header);
					last_header = NULL;
				}

				/* and finally output the data */
				if (strchr(value, ' ') != NULL)
					core_fprintf(inifile, "%-25s \"%s\"\n", name, value);
				else
					core_fprintf(inifile, "%-25s %s\n", name, value);
			}
		}
	}
}


/*-------------------------------------------------
    options_output_ini_file - output the current
    state to an INI file
-------------------------------------------------*/

void options_output_ini_file(core_options *opts, core_file *inifile)
{
	options_output_diff_ini_file(opts, NULL, inifile);
}


/*-------------------------------------------------
    options_output_ini_file - output the current
    state to an INI file
-------------------------------------------------*/

void options_output_ini_stdfile(core_options *opts, FILE *inifile)
{
	options_data *data;

	/* loop over all items */
	for (data = opts->datalist; data != NULL; data = data->next)
	{
		/* header: just print */
		if ((data->flags & OPTION_HEADER) != 0)
			fprintf(inifile, "\n#\n# %s\n#\n", data->description);

		/* otherwise, output entries for all non-deprecated and non-command items */
		else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL | OPTION_COMMAND)) == 0)
		{
			if (astring_chr(data->data, 0, ' ') != -1)
				fprintf(inifile, "%-25s \"%s\"\n", astring_c(data->links[0].name), astring_c(data->data));
			else
				fprintf(inifile, "%-25s %s\n", astring_c(data->links[0].name), astring_c(data->data));
		}
	}
}


/*-------------------------------------------------
    options_output_help - output option help to
    a file
-------------------------------------------------*/

void options_output_help(core_options *opts, void (*output)(const char *))
{
	options_data *data;

	/* loop over all items */
	for (data = opts->datalist; data != NULL; data = data->next)
	{
		/* header: just print */
		if ((data->flags & OPTION_HEADER) != 0)
			output_printf(output, "\n#\n# %s\n#\n", data->description);

		/* otherwise, output entries for all non-deprecated items */
		else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) == 0 && data->description != NULL)
			output_printf(output, "-%-20s%s\n", astring_c(data->links[0].name), data->description);
	}
}



/***************************************************************************
    OPTIONS READING
***************************************************************************/

/*-------------------------------------------------
    options_get_string - return data formatted
    as a string
-------------------------------------------------*/

const char *options_get_string(core_options *opts, const char *name)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	const char *value = "";

	/* error if not found */
	if (data == NULL)
		message(opts, OPTMSG_ERROR, "Unexpected option %s queried\n", name);

	/* copy if non-NULL */
	else
		value = astring_c(data->data);

	return value;
}


/*-------------------------------------------------
    options_get_bool - return data formatted as
    a boolean
-------------------------------------------------*/

int options_get_bool(core_options *opts, const char *name)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	int value = FALSE;

	/* error if not found */
	if (data == NULL)
		message(opts, OPTMSG_ERROR, "Unexpected boolean option %s queried\n", name);

	/* also error if we don't have a valid boolean value */
	else if (sscanf(astring_c(data->data), "%d", &value) != 1 || value < 0 || value > 1)
	{
		options_set_string(opts, name, astring_c(data->defdata), 0);
		sscanf(astring_c(data->data), "%d", &value);
		if (!data->error_reported)
		{
			message(opts, OPTMSG_ERROR, "Illegal boolean value for %s; reverting to %d\n", astring_c(data->links[0].name), value);
			data->error_reported = TRUE;
		}
	}
	return value;
}


/*-------------------------------------------------
    options_get_int - return data formatted as
    an integer
-------------------------------------------------*/

int options_get_int(core_options *opts, const char *name)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	int value = 0;

	/* error if not found */
	if (data == NULL)
		message(opts, OPTMSG_ERROR, "Unexpected integer option %s queried\n", name);

	/* also error if we don't have a valid integer value */
	else if (sscanf(astring_c(data->data), "%d", &value) != 1)
	{
		options_set_string(opts, name, astring_c(data->defdata), 0);
		sscanf(astring_c(data->data), "%d", &value);
		if (!data->error_reported)
		{
			message(opts, OPTMSG_ERROR, "Illegal integer value for %s; reverting to %d\n", astring_c(data->links[0].name), value);
			data->error_reported = TRUE;
		}
	}
	return value;
}


/*-------------------------------------------------
    options_get_float - return data formatted as
    a float
-------------------------------------------------*/

float options_get_float(core_options *opts, const char *name)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	float value = 0;

	/* error if not found */
	if (data == NULL)
		message(opts, OPTMSG_ERROR, "Unexpected float option %s queried\n", name);

	/* also error if we don't have a valid floating point value */
	else if (sscanf(astring_c(data->data), "%f", &value) != 1)
	{
		options_set_string(opts, name, astring_c(data->defdata), 0);
		sscanf(astring_c(data->data), "%f", &value);
		if (!data->error_reported)
		{
			message(opts, OPTMSG_ERROR, "Illegal float value for %s; reverting to %f\n", astring_c(data->links[0].name), (double)value);
			data->error_reported = TRUE;
		}
	}
	return value;
}


/*-------------------------------------------------
    options_get_seqid - return the seqid for an
    entry
-------------------------------------------------*/

UINT32 options_get_seqid(core_options *opts, const char *name)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	return (data == NULL) ? 0 : data->seqid;
}



/***************************************************************************
    OPTIONS SETTING
***************************************************************************/

/*-------------------------------------------------
    options_set_string - set a string value
-------------------------------------------------*/

void options_set_string(core_options *opts, const char *name, const char *value, int priority)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	update_data(opts, data, value, priority);
}


/*-------------------------------------------------
    options_set_bool - set a boolean value
-------------------------------------------------*/

void options_set_bool(core_options *opts, const char *name, int value, int priority)
{
	char temp[4];
	sprintf(temp, "%d", value ? 1 : 0);
	options_set_string(opts, name, temp, priority);
}


/*-------------------------------------------------
    options_set_int - set an integer value
-------------------------------------------------*/

void options_set_int(core_options *opts, const char *name, int value, int priority)
{
	char temp[20];
	sprintf(temp, "%d", value);
	options_set_string(opts, name, temp, priority);
}


/*-------------------------------------------------
    options_set_float - set a float value
-------------------------------------------------*/

void options_set_float(core_options *opts, const char *name, float value, int priority)
{
	char temp[100];
	sprintf(temp, "%f", value);
	options_set_string(opts, name, temp, priority);
}



/***************************************************************************
    OPTION DEFINITION QUERIES
***************************************************************************/

/*-------------------------------------------------
    options_enumerator_begin - retrieve the range of
    a float option
-------------------------------------------------*/

options_enumerator *options_enumerator_begin(core_options *opts)
{
	options_enumerator *enumerator;

	/* allocate memory for the enumerator */
	enumerator = (options_enumerator *)malloc(sizeof(*enumerator));
	if (enumerator == NULL)
		return NULL;

	/* start at the head of the list */
	enumerator->current = opts->datalist;
	return enumerator;
}



/*-------------------------------------------------
    options_enumerator_next - returns the current
    option and advances the enumerator
-------------------------------------------------*/

const char *options_enumerator_next(options_enumerator *enumerator)
{
	astring *option_name = NULL;

	/* be sure to skip over false options */
	while (option_name == NULL && enumerator->current != NULL)
	{
		/* retrieve the current option name and advance the enumerator */
		option_name = enumerator->current->links[0].name;
		enumerator->current = enumerator->current->next;
	}
	return (option_name != NULL) ? astring_c(option_name) : NULL;
}



/*-------------------------------------------------
    options_enumerator_free - disposes an options
    enumerator
-------------------------------------------------*/

void options_enumerator_free(options_enumerator *enumerator)
{
	free(enumerator);
}


/*-------------------------------------------------
    options_get_range_type - determine the type
    of range for a particular option
-------------------------------------------------*/

options_range_type options_get_range_type(core_options *opts, const char *name)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	return data->range_type;
}


/*-------------------------------------------------
    options_get_range_int - retrieve the range of
    an integer option
-------------------------------------------------*/

void options_get_range_int(core_options *opts, const char *name, int *minval, int *maxval)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	*minval = data->range_minimum.i;
	*maxval = data->range_maximum.i;
}



/*-------------------------------------------------
    options_get_range_float - retrieve the range of
    a float option
-------------------------------------------------*/

void options_get_range_float(core_options *opts, const char *name, float *minval, float *maxval)
{
	options_data *data = find_entry_data(opts, name, FALSE);
	*minval = data->range_minimum.f;
	*maxval = data->range_maximum.f;
}



/***************************************************************************
    INTERNAL UTILITIES
***************************************************************************/

/*-------------------------------------------------
    find_entry_data - locate an entry whose name
    matches the given string
-------------------------------------------------*/

static options_data *find_entry_data(core_options *opts, const char *string, int is_command_line)
{
	int hash_entry = hash_value(opts, string);
	options_hash_entry *link;

	/* scan all entries */
	for (link = opts->hashtable[hash_entry]; link != NULL; link = link->next)
		if (!(link->data->flags & OPTION_HEADER) && link->name != NULL && astring_cmpc(link->name, string) == 0)
			return link->data;

	/* haven't found it?  if we are prefixed with "no", then try to search for that */
	if (is_command_line && string[0] == 'n' && string[1] == 'o')
	{
		options_data *data = find_entry_data(opts, &string[2], FALSE);
		if (data != NULL && (data->flags & OPTION_BOOLEAN))
			return data;
	}

	/* didn't find it at all */
	return NULL;
}


/*-------------------------------------------------
    update_data - update the data value for a
    given entry
-------------------------------------------------*/

static void update_data(core_options *opts, options_data *data, const char *newdata, int priority)
{
	const char *dataend = newdata + strlen(newdata) - 1;
	const char *datastart = newdata;
	float f;
	int i;

	/* strip off leading/trailing spaces */
	while (isspace(*datastart) && datastart <= dataend)
		datastart++;
	while (isspace(*dataend) && datastart <= dataend)
		dataend--;

	/* strip off quotes */
	if (datastart != dataend && *datastart == '"' && *dataend == '"')
		datastart++, dataend--;

	/* check against range */
	switch (data->range_type)
	{
		case OPTION_RANGE_NONE:
			/* do nothing */
			break;

		case OPTION_RANGE_INT:
			/* check against integer range */
			i = 0;
			if (sscanf(datastart, "%d", &i) != 1)
			{
				message(opts, OPTMSG_ERROR, "Illegal integer value for %s; keeping value of %s\n", astring_c(data->links[0].name), astring_c(data->data));
				data->error_reported = TRUE;
				return;
			}
			if (i < data->range_minimum.i || i > data->range_maximum.i)
			{
				message(opts, OPTMSG_ERROR, "Invalid %s value (must be between %i and %i); keeping value of %s\n",
					astring_c(data->links[0].name), data->range_minimum.i, data->range_maximum.i, astring_c(data->data));
				data->error_reported = TRUE;
				return;
			}
			break;

		case OPTION_RANGE_FLOAT:
			/* check against float range */
			f = 0;
			if (sscanf(datastart, "%f", &f) != 1)
			{
				message(opts, OPTMSG_ERROR, "Illegal float value for %s; keeping value of %s\n", astring_c(data->links[0].name), astring_c(data->data));
				data->error_reported = TRUE;
				return;
			}
			if (f < data->range_minimum.f || f > data->range_maximum.f)
			{
				message(opts, OPTMSG_ERROR, "Invalid %s value (must be between %f and %f); keeping value of %s\n",
					astring_c(data->links[0].name), data->range_minimum.f, data->range_maximum.f, astring_c(data->data));
				data->error_reported = TRUE;
				return;
			}
			break;
	}

	/* ignore if we don't have priority */
	if (priority < data->priority)
		return;

	/* allocate a copy of the data */
	astring_cpych(data->data, datastart, dataend + 1 - datastart);
	data->priority = priority;

	/* bump the seqid and clear the error reporting */
	data->seqid++;
	data->error_reported = FALSE;
}


/*-------------------------------------------------
    parse_option_name - read data from an option_entry
    name into an option_data structure
-------------------------------------------------*/

static int parse_option_name(core_options *opts, const char *srcstring, options_data *data)
{
	const char *start;
	const char *end = NULL;
	int curentry;

	/* start with the original string and loop over entries */
	start = srcstring;
	for (curentry = 0; curentry < ARRAY_LENGTH(data->links); curentry++)
	{
		/* find the end of this entry and copy the string */
		for (end = start; *end != 0 && *end != ';' && *end != '('; end++)
			;
		data->links[curentry].name = astring_dupch(start, end - start);

		/* if we hit the end of the source, stop */
		if (*end != ';')
			break;
		start = end + 1;
	}

	/* have we found a range? */
	if (end != NULL && *end == '(')
	{
		if (sscanf(end, "(%d-%d)", &data->range_minimum.i, &data->range_maximum.i) == 2)
			data->range_type = OPTION_RANGE_INT;
		else if (sscanf(end, "(%f-%f)", &data->range_minimum.f, &data->range_maximum.f) == 2)
			data->range_type = OPTION_RANGE_FLOAT;
	}
	return curentry;
}


/*-------------------------------------------------
    message - outputs a message to a listener
-------------------------------------------------*/

static void message(core_options *opts, options_message msgtype, const char *format, ...)
{
	char buf[1024];
	va_list argptr;

	/* output a message if there is a non-NULL handler for it */
	if (opts->output[msgtype] != NULL)
	{
		va_start(argptr, format);
		vsprintf(buf, format, argptr);
		va_end(argptr);

		(*opts->output[msgtype])(buf);
	}
}


/*-------------------------------------------------
    hash_value - computes the hash value for a string
-------------------------------------------------*/

static UINT32 hash_value(core_options *opts, const char *str)
{
    UINT32 hash = 5381;
    int c;

    while ((c = *str++) != 0)
        hash = ((hash << 5) + hash) + c;

    return hash % ARRAY_LENGTH(opts->hashtable);
}


/*-------------------------------------------------
    output_printf - outputs an arbitrary message
    to a callback
-------------------------------------------------*/

static void output_printf(void (*output)(const char *s), const char *format, ...)
{
	char buf[1024];
	va_list argptr;

	va_start(argptr, format);
	vsprintf(buf, format, argptr);
	va_end(argptr);

	output(buf);
}
