/* config.c - read a configuration file
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "mstring.h"
#include "bbs.h"

#define CF_WHITESPACE	" \011"		    /* whitespace */


/* Store the boolean value "value", as described by specification "spec".
 */
int cf__bool(char *file, int line, bbs_cf_t * spec, char *value)
{

	switch (toupper(value[0])) {
	case '1':
	case 'Y':
		spec->value = 1;
		return (0);
	case '0':
	case 'N':
		spec->value = 0;
		return (0);
	}

	if (toupper(value[0]) == 'O') {
		if (toupper(value[1]) == 'N') {
			spec->value = 1;
			return (0);
		}
		if (toupper(value[1]) == 'F') {
			spec->value = 1;
			return (0);
		}
	}

	fprintf(stderr, "%s: %d: expected boolean value for '%s'\n\r",
		file, line, spec->key_name);

	return (1);
}


/* Store the integer value "value", as described by specification "spec".
 */
int cf__long(char *file, int line, bbs_cf_t * spec, char *value)
{
	spec->value = atol(value);
	return (0);
}


/* Store the string "value", as described by specification "spec".
 */
int cf__char(char *file, int line, bbs_cf_t * spec, char *value)
{
	spec->strval = strdup(value);
	return (0);
}


/* Load the configuration file "file", whose possible keys are described by
 * the array "spec", which is terminated by an entry containing all zeroes.
 *
 * If a "key_type" is CF_STRING, leading whitespace in a value is discarded
 * but all other whitespace is retained.
 *
 * Entries in the configuration file "file" must be of the following format:
 *   key <whitespace> value
 *
 * Blank lines, leading whitespace, and lines starting with # are ignored.
 * Lines longer than 1023 characters will be split.
 *
 * Returns nonzero on error, having reported it on stderr.
 */
int cf_load(char *file, bbs_cf_t * spec)
{
	char buf[1024];
	FILE *fptr;
	char *p;
	int line;
	int i, n;

	if ((!file) || (!spec)) {
		fprintf(stderr,
			"load_config: unexpected null pointer\n\r");
		return (1);
	}

	fptr = fopen(file, "r");
	if (!fptr) {
		fprintf(stderr, "%s: failed to open file (%s)\n\r", file,
			strerror(errno));
		return (1);
	}

	line = 0;

	while (!feof(fptr)) {
		buf[0] = 0;
		fgets(buf, 1024, fptr);	    /* read line */
		p = strchr(buf, '\n');	    /* chop \n */
		if (p) {
			line++;		    /* increment line counter */
			*p = 0;
		}
		n = strspn(buf, CF_WHITESPACE);	/* skip leading whitespace */
		if (buf[n] == '#')
			continue;	    /* skip comment lines */
		if (buf[n] == 0)
			continue;	    /* skip blank lines */
		mstrdelete(buf, 0, n);
		n = strcspn(buf, CF_WHITESPACE);	/* find length of key */
		for (i = 0; spec[i].key_name; i++) {	/* check key is valid */
			if (!strncasecmp(spec[i].key_name, buf, n))
				break;
		}
		if (!spec[i].key_name) {    /* invalid key - abort */
			buf[n] = 0;
			fprintf(stderr, "%s: %d: unknown key '%s'\n\r",
				file, line, buf);
			fclose(fptr);
			return (1);
		}
		n += strspn(buf + n, CF_WHITESPACE);	/* skip whitespace after key */
		mstrdelete(buf, 0, n);
		if (spec[i].key_type != CF_STRING) {	/* remove all whitespace */
			n = strcspn(buf, CF_WHITESPACE);
			buf[n] = 0;
		}
		n = 0;			    /* store the value */
		switch (spec[i].key_type) {
		case CF_BOOLEAN:
			n = cf__bool(file, line, &(spec[i]), buf);
			break;
		case CF_INTEGER:
			n = cf__long(file, line, &(spec[i]), buf);
			break;
		case CF_FILENAME:
			n = cf__char(file, line, &(spec[i]), buf);
			break;
		case CF_STRING:
			n = cf__char(file, line, &(spec[i]), buf);
			break;
		}
		if (n) {		    /* error occurred - abort */
			fclose(fptr);
			return (1);
		}
	}

	fclose(fptr);
	return (0);
}


/* Find the key "key" in "spec", and return its index, or -1 if not found.
 */
int cf_lookup_key(bbs_cf_t * spec, char *key)
{
	int i;

	for (i = 0; spec[i].key_name; i++) {
		if (!strcasecmp(spec[i].key_name, key))
			return (i);
	}

	return (-1);
}


/* Look up the string value of key "key" in config spec "spec".
 *
 * Returns 0 on error.
 */
char *cf_lookup_str(bbs_cf_t * spec, char *key)
{
	int i;

	i = cf_lookup_key(spec, key);
	if (i < 0)
		return (0);

	return (spec[i].strval);
}


/* Look up the integer value of key "key" in config spec "spec".
 *
 * Returns 0 on error.
 */
long cf_lookup_int(bbs_cf_t * spec, char *key)
{
	int i;

	i = cf_lookup_key(spec, key);
	if (i < 0)
		return (-1);

	return (spec[i].value);
}


/* Look up the boolean value of key "key" in config spec "spec".
 *
 * Returns 0 on error.
 */
int cf_lookup_bool(bbs_cf_t * spec, char *key)
{
	int i;

	i = cf_lookup_key(spec, key);
	if (i < 0)
		return (-1);

	return (spec[i].value);
}

/* EOF */
