/*
	info.c

	Info strings.

	Copyright (C) 1996-1997  Id Software, Inc.

	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:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#include <stdio.h>
#include <ctype.h>

#include "QF/hash.h"
#include "QF/info.h"
#include "QF/sys.h"
#include "QF/zone.h"

#include "compat.h"

struct info_s {
	struct hashtab_s	*tab;
	size_t				maxsize;
	int					cursize;
};

/*
	Info_FilterForKey

	Searches for key in the "client-needed" info string list
*/
VISIBLE qboolean
Info_FilterForKey (const char *key, const char **filter_list)
{
	const char  **s;

	for (s = filter_list; *s; s++) {
		if (strequal (*s, key)) {
			return true;
		}
	}
	return false;
}

VISIBLE int
Info_CurrentSize (info_t *info)
{
	return info->cursize;
}

/*
	Info_ValueForKey

	Searches the string for the given
	key and returns the associated value, or an empty string.
*/
VISIBLE const char *
Info_ValueForKey (info_t *info, const char *key)
{
	info_key_t *k = Hash_Find (info->tab, key);
	if (k)
		return k->value;
	return "";
}

VISIBLE info_key_t *
Info_Key (info_t *info, const char *key)
{
	return Hash_Find (info->tab, key);
}

VISIBLE info_key_t **
Info_KeyList (info_t *info)
{
	return (info_key_t **) Hash_GetList (info->tab);
}

VISIBLE void
Info_RemoveKey (info_t *info, const char *key)
{
	info_key_t *k = Hash_Del (info->tab, key);

	if (k) {
		free ((char*)k->key);
		free ((char*)k->value);
		free (k);
	}
}

VISIBLE int
Info_SetValueForStarKey (info_t *info, const char *key, const char *value,
						 int flags)
{
	info_key_t *k;
	int         cursize;
	char		*str;
	byte		*d, *s;

	if (strstr (key, "\\") || strstr (value, "\\")) {
		Sys_Printf ("Can't use keys or values with a \\\n");
		return 0;
	}
	if (strstr (key, "\"") || strstr (value, "\"")) {
		Sys_Printf ("Can't use keys or values with a \"\n");
		return 0;
	}
	if (strlen (key) > 63 || strlen (value) > 63) {
		Sys_Printf ("Keys and values must be < 64 characters.\n");
		return 0;
	}

	k = Hash_Find (info->tab, key);
	cursize = info->cursize;
	if (k) {
		cursize -= strlen (k->key) + 1;
		cursize -= strlen (k->value) + 1;
	}
	if (info->maxsize &&
		cursize + strlen (key) + 1 + strlen (value) + 1 > info->maxsize) {
		Sys_Printf ("Info string length exceeded\n");
		return 0;
	}
	if (k) {
		if (strequal (k->value, value))
			return 0;
		info->cursize -= strlen (k->value) + 1;
		free ((char *) k->value);
	} else {
		if (!(k = malloc (sizeof (info_key_t))))
			Sys_Error ("Info_SetValueForStarKey: out of memory");
		if (!(k->key = strdup (key)))
			Sys_Error ("Info_SetValueForStarKey: out of memory");
		info->cursize += strlen (key) + 1;
		Hash_Add (info->tab, k);
	}
	if (!(str = strdup (value)))
		Sys_Error ("Info_SetValueForStarKey: out of memory");
	for (d = s = (byte *) str; *s; s++) {
		if (flags & 1) {
			*s &= 127;
			if (*s < 32)
				continue;
		}
		if (flags & 2)
			*s = tolower (*s);
		if (*s > 13)
			*d++ = *s;
	}
	*d = 0;
	info->cursize += strlen (str) + 1;
	k->value = str;
	return 1;
}

VISIBLE int
Info_SetValueForKey (info_t *info, const char *key, const char *value,
					 int flags)
{
	if (key[0] == '*') {
		Sys_Printf ("Can't set * keys\n");
		return 0;
	}

	return Info_SetValueForStarKey (info, key, value, flags);
}

VISIBLE void
Info_Print (info_t *info)
{
	info_key_t **key_list;
	info_key_t **key;

	key_list = (info_key_t **) Hash_GetList (info->tab);

	for (key = key_list; *key; key++) {
		Sys_Printf ("%-15s %s\n", (*key)->key, (*key)->value);
	}
	free (key_list);
}

static const char *
info_get_key (const void *k, void *unused)
{
	return ((info_key_t *)k)->key;
}

static void
free_key (void *_k, void *unused)
{
	info_key_t *k = (info_key_t *) _k;
	free ((char *) k->key);
	free ((char *) k->value);
	free (k);
}

VISIBLE info_t *
Info_ParseString (const char *s, int maxsize, int flags)
{
	info_t     *info;
	char       *string = Hunk_TempAlloc (strlen (s) + 1);
	char       *key, *value, *end;

	info = malloc (sizeof (info_t));
	info->tab = Hash_NewTable (61, info_get_key, free_key, 0, 0);
	info->maxsize = maxsize;
	info->cursize = 0;

	strcpy (string, s);
	key = string;
	if (*key == '\\')
		key++;
	while (*key) {
		value = key;
		while (*value && *value != '\\')
			value++;
		if (*value) {
			*value++ = 0;
			for (end = value; *end && *end != '\\'; end++)
				;
			if (*end)
				*end++ = 0;
		} else {
			value = end = (char *) "";
		}
		Info_SetValueForStarKey (info, key, value, flags);
		key = end;
	}
	return info;
}

VISIBLE void
Info_Destroy (info_t *info)
{
	Hash_DelTable (info->tab);
	free (info);
}

VISIBLE char *
Info_MakeString (info_t *info, int (*filter) (const char *))
{
	char       *string;
	const char *s;
	char       *d;
	info_key_t **key_list;
	info_key_t **key;

	d = string = Hunk_TempAlloc (info->cursize + 1);
	key_list = (info_key_t **) Hash_GetList (info->tab);

	for (key = key_list; *key; key++) {
		if (!*(*key)->value)
			continue;
		if (filter && filter ((*key)->key))
			continue;
		*d++ = '\\';
		for (s = (*key)->key; *s; s++)
			*d++ = *s;
		*d++ = '\\';
		for (s = (*key)->value; *s; s++)
			*d++ = *s;
	}
	*d = 0;
	free (key_list);
	return string;
}

VISIBLE void
Info_AddKeys (info_t *info, info_t *keys)
{
	info_key_t **key_list;
	info_key_t **key;

	key_list = (info_key_t **)Hash_GetList (keys->tab);
	for (key = key_list; *key; key++) {
		Info_SetValueForKey (info, (*key)->key, (*key)->value, 0);
	}
	free (key_list);
}