/*
 * Karray: string-addressable KW array, used for easy manipulation
 * of configuration files.
 *
 * Copyright (c) 1996 David L Parsons
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by David L Parsons (orc@pell.chi.il.us).  My name may not be used
 * to endorse or promote products derived from this software without
 * specific prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 */
static const char rcsid[] = "$Id: s.Karray.cc 1.6 98/08/28 05:19:47-00:00 orc $";

extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <ctype.h>
#include <string.h>
}

#include "Karray.H"

/*
 * Karray::~Karray() - deletes the object in question
 */
Karray::~Karray()
{
    Erase();
    free((char*)_list);
} /* Karray::~Karray */


/*
 * operator[] (char *index)
 */
KW&
Karray::operator[] (char *key)
{
    return valueof(key);
} /* Karray::operator [] */


/*
 * valueof(char *index)
 */
KW&
Karray::valueof(char *key)
{
    register i;
    static KW null(0);

    i = locate(key);

    if (i >= 0)
	return *(_list[i]._content);

    if (autoappend())
	return create(key);
    return null;
} /* Karray:operator[] */


/*
 * locate() finds the slot for a given key
 */
int
Karray::locate(char *key)
{
    register i;

    for (i=0; i<count(); i++)
	if (strcasecmp(_list[i]._key, key) == 0)
	    return i;
    return -1;
} /* Karray::locate */


/*
 * assign() sets an arg to a value (or adds a new arg with a given value)
 */
KW&
Karray::create(char* key, char* value)
{
    int index;

    assert(key != (char*)0);

    if ((index=locate(key)) < 0) {
	for (index=0; index<count(); index++)
	    if (!_list[index]._used)
		break;
	if (index == count()) {
	    _list = (struct KA*)realloc((char*)_list,
					(1+index) * sizeof(struct KA));
	    _nrlist++;
	}
	_list[index]._key = strdup(key);
	_list[index]._content = new KW(value);
	_list[index]._used = 1;
    }
    else
	*(_list[index]._content) = value;
    return *(_list[index]._content);
} /* Karray::assign */


/*
 * foreach() - iterate across the thing array
 */
int
Karray::foreach(int (*op)(char*,KW*))
{
    int i;
    int st;

    for (i=0; i<count(); i++)
	if (_list[i]._used && (st=(*op)(_list[i]._key, _list[i]._content)) != 0)
	    return st;
    return 0;
} /* Karray::foreach */


/*
 * drop() deletes something by name
 */
int
Karray::drop(char* key)
{
    int index = locate(key);

    if (index >= 0 && index < count()) {
	free(_list[index]._key);
	delete _list[index]._content;
	_list[index]._used = 0;
    }
    _dirty = 1;
    return count();
} /* Karray::drop */


/*
 * changed() tells us if anything has changed
 */
int
Karray::changed()
{
    int x;

    if (_dirty)
	return 1;
    for (x=0; x<count(); x++)
	if (_list[x]._used && _list[x]._content->changed())
	    return 1;
    return 0;
} /* Karray::changed */


/*
 * changed(int) sets the changed flags
 */
int
Karray::changed(int val)
{
    int x;

    _dirty = val;
    for (x=0; x<count(); x++)
	if (_list[x]._used)
	    _list[x]._content->changed(val);
    return val;
} /* Karray::changed */


/*
 * print() prints out the object, in our native configuration file
 * format. double-quote the entire string, and backslash-quote ",
 * backslash, and $
 */
int
Karray::print(FILE *out)
{
    int i;
    char *p;

    for (i=0; i<count(); i++)
	if (_list[i]._used && (p = _list[i]._content->value()) != 0) {
	    fprintf(out, "%s=\"", _list[i]._key);
	    for (; *p; ++p) {
		if (strchr("\"\\$", *p) != 0)
		    fputc('\\', out);
		fputc(*p, out);
	    }
	    fprintf(out, "\"\n");
	}
    return ferror(out);
} /* Karray::print */


/*
 * read() loads a configuration file into the Karray
 *
 * TODO: error callbacks need to be provided via a callback method hook
 * in the karray.
 *
 * This code is pretty amazingly grotty.  What it does is that it implements
 * reading /bin/sh type VAR=ARG variable settings, and it tries its damnest
 * to be able to read honest-to-god shell scripts.  It can't deal with
 * multiple commands (VAR=ARG cmd; VAR=ARG cmd) but it will attempt to
 * read multiple settings on a line.
 * What it does is, for each line, it goes into a loop where it finds
 * the first token, and if that's a legitimate token (alphanumeric and
 * '_') that ends with a =, it's loaded into the Karray, then we walk
 * the value (nonblank and quoted strings) adding it into the Karray
 * a line at a time until we find unquoted blanks, at which point we
 * go back to the top of the loop and look for the next token; repeat
 * until we find something illegitimate, at which point we punt this
 * line, read in the next, and do the whole thing again.
 *
 * The code will make your eyes bleed.  Be warned.
 */
int
Karray::read(FILE *in)
{
    KW* key;
    char line[2048];
    char *token,		/* points at the base of the current token */
	 *ptr,			/* the character we're examining now */
	 *copy,			/* a copy pointer, for weeding out specials */
	 quote;			/* if we're doing a string, this is the
				 * terminating quote */

    while (fgets(line, sizeof line, in)) {
	token = line;
	do {
	    while (isspace(*token) && *token)	/* get to first nonblank */
		++token;
	    if (*token == '#' || *token == 0 || !isalpha(*token))
		break;
	    ptr = token;			/* mark start, look for end */
	    while (isalnum(*ptr) || *ptr == '_')
		++ptr;
	    if (*ptr != '=')			/* legitimate VAR=? */
		break;
	    *ptr++ = 0;
	    key = &create(token);		/* yes, store it in our
						 * array, get back a reference
						 * to it for loading values.
						 */
	    token = copy = ptr;
	    /* gobble up the value; first blank terminates it
	     */
	    while (!isspace(*ptr) && *ptr != 0) {
		if (*ptr == '\'' || *ptr == '"') {
		    quote = *ptr++;
		    while (*ptr != quote) {
			if (*ptr == 0) {
		    newquoteline:
			    *copy = 0;
			    (*key) += token;
			    if (!fgets(line, sizeof line, in)) {
				changed(0);
				return EOF;
			    }
			    token = ptr = copy = line;
			}
			else if (*ptr == '\\') {
			    if (*++ptr == 0) {
				*copy++ = '\n';
				goto newquoteline;
			    }
			}
			if (ptr > copy)
			    *copy++ = *ptr++;
			else
			    copy++, ptr++;
		    }
		    ++ptr;
		    continue;
		}
		if (*ptr == '\\') {
		    if (*++ptr == 0) {
			*copy++ = '\n';
			*copy = 0;
			(*key) += token;
			if (!fgets(line, sizeof line, in)) {
			    changed(0);
			    return EOF;
			}
			token = ptr = copy = line;
			continue;
		    }
		}
		if (ptr > copy)
		    *copy++ = *ptr++;
		else
		    copy++, ptr++;
	    }
	    *copy = 0;
	    (*key) += token;
	} while (*ptr);
    }
    changed(0);
    return ferror(in) ? EOF : 0;
} /* Karray::read */


/*
 * Erase() wipes the contents of the array out
 */
void
Karray::Erase()
{
    int i;

    for (i=0; i< count(); i++) {
	if (_list[i]._used) {
	    delete _list[i]._content;
	    free(_list[i]._key);
	    _list[i]._used = 0;
	}
    }
    _dirty = 1;
    _nrlist = 0;
} /* Karray::Erase */
