/* ARISA - polymorphic value functions
 * Copyright (C) 2004 Carl Ritson
 *
 * 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, USA
 */

#include "arisa.h"
#include <ctype.h>

static value_t *alloc_value(const char *name, size_t stringlen) {
	size_t namelen = (name != NULL ? strlen(name) + 1 : 0);
	value_t *v = xalloc(sizeof(value_t) + namelen + stringlen);
	
	v->name		= name != NULL ? ((char *)v) + sizeof(value_t) : NULL;
	v->flags	= 0;
	if(v->name != NULL)
		xstrncpy(v->name,name,namelen);
	if(strlen > 0)
		v->val.str = v->name + namelen;
	
	return v;
}

value_t	*value_int32(const char *name, int32_t i32) {
	value_t *v 	= alloc_value(name,0);
	v->flags	= VALUE_INT32;
	v->val.i32	= i32;
	return v;
}

value_t *value_uint32(const char *name, uint32_t ui32) {
	value_t *v	= alloc_value(name,0);
	v->flags 	= VALUE_INT32 | VALUE_UNSIGNED;
	v->val.ui32	= ui32;
	return v;
}

value_t *value_int64(const char *name, int64_t i64) {
	value_t	*v	= alloc_value(name,0);
	v->flags	= VALUE_INT64;
	v->val.i64	= i64;
	return v;
}

value_t *value_uint64(const char *name, uint64_t ui64) {
	value_t *v	= alloc_value(name,0);
	v->flags	= VALUE_INT64 | VALUE_UNSIGNED;
	v->val.ui64	= ui64;
	return v;
}

value_t	*value_string(const char *name, const char *string) {
	size_t stringlen= string != NULL ? strlen(string) + 1 : 0;
	value_t	*v	= alloc_value(name,stringlen);
	v->flags	= VALUE_STRING;
	if(stringlen > 0)
		xstrncpy(v->val.str,string,stringlen);
	else
		v->val.str = NULL;
	return v;
}

value_t *value_float(const char *name, float f) {
	return value_double(name, (double) f);
}

value_t *value_double(const char *name, double d) {
	value_t *v	= alloc_value(name,0);
	v->flags	= VALUE_DOUBLE;
	v->val.d	= d;
	return v;
}

value_t *value_bool(const char *name, int flags, int b) {
	value_t *v = value_int32(name,(int32_t)b);
	v->flags |= 
		VALUE_BOOL | (flags & (VALUE_BOOL_ONOFF | VALUE_BOOL_YESNO));
	return v;
}

value_t *value_enum_str(const char *name, const char *str,
		int (*en)(const char *)) {
	value_t *v = value_string(name,str);
	assert(en != NULL);
	v->flags 	|= VALUE_ENUM;
	v->num.en 	= en;
	return v;
}

value_t *value_enum_val(const char *name, int val, 
		const char *(*de)(int)) {
	value_t *v = value_int32(name,(int32_t)val);
	assert(de != NULL);
	v->flags	|= VALUE_ENUM;
	v->num.de	= de;
	return v;
}	

static uint64_t int64_multiplier(const char *str) {
	uint64_t unit = 1024;
	uint64_t ret;
	int mult = 0;
	
	if(str == NULL)
		return 1;
	if(*str == '\0')
		return 1;
	switch(*str) {
		case 'k': case 'K': mult = 1; break;
		case 'm': case 'M': mult = 2; break;
		case 'g': case 'G': mult = 3; break;
		case 't': case 'T': mult = 4; break;
		case 'p': case 'P': mult = 5; break;
		default:
			return 1;
	}
	str++;
	if(*str == 'b' || *str == 'B')
		unit = 1000;

	for(ret = 1ULL; mult > 0; --mult) // note pre-deincrement
		ret *= unit;

	return ret;
}

static int32_t str_to_int32_t(const char *str, int32_t def) {
	int32_t ret = def;
	if(str != NULL) {
		char *p;
		ret = (int32_t)strtol(str,&p,10);
		ret *= (int32_t)int64_multiplier(p);
	}
	return ret;
}

static uint32_t str_to_uint32_t(const char *str, uint32_t def) {
	uint32_t ret = def;
	if(str != NULL) {
		char *p;
		ret = (uint32_t)strtoul(str,&p,10);
		ret *= (uint32_t)int64_multiplier(p);
	}
	return ret;
}

static int64_t str_to_int64_t(const char *str, int64_t def) {
	int64_t ret = def;
	if(str != NULL) {
		char *p;
		ret = (int64_t)strtoll(str,&p,10);
		ret *= (int64_t)int64_multiplier(p);
	}
	return ret;
}

static uint64_t str_to_uint64_t(const char *str, uint64_t def) {
	uint64_t ret = def;
	if(str != NULL) {
		char *p;
		ret = (uint64_t)strtoull(str,&p,10);
		ret *= (uint64_t)int64_multiplier(p);
	}
	return ret;
}

static float str_to_float(const char *str, float def) {
	if(str == NULL)
		return def;
	else
		return (float)strtof(str,NULL);
}

static double str_to_double(const char *str, double def) {
	if(str == NULL)
		return def;
	else
		return (double)strtod(str,NULL);
}

#define TYPE_CONVERTER(type) \
	type ret = flags & VALUE_USE_DEFAULT ? def : 0;			\
									\
	if(v->flags & VALUE_INT32) {					\
		ret = (type) (v->flags & VALUE_UNSIGNED 		\
				? v->val.ui32 : v->val.i32);		\
	} else if(v->flags & VALUE_INT64) {				\
		ret = (type) (v->flags & VALUE_UNSIGNED 		\
				? v->val.ui64 : v->val.i64);		\
	} else if(v->flags & VALUE_DOUBLE) {				\
		ret = (type) v->val.d;					\
	} else if(v->flags & VALUE_STRING) {				\
		if(!(v->flags & VALUE_ENUM)) {				\
			ret = (type) str_to_##type(v->val.str,		\
				flags & VALUE_USE_DEFAULT ? def : 0);	\
		} else {						\
			ret = (type) v->num.en(v->val.str);		\
		}							\
	} 								\
									\
	if(flags & VALUE_USE_MIN && ret < min)				\
		ret = flags & VALUE_USE_DEFAULT ? def : min;		\
	else if(flags & VALUE_USE_MAX && ret > max) 			\
		ret = flags & VALUE_USE_DEFAULT ? def : max;		\
									\
	return ret;

int32_t	value_as_int32(value_t *v, int flags, 
		int32_t min, int32_t max, int32_t def) {
	TYPE_CONVERTER(int32_t)
}

uint32_t value_as_uint32(value_t *v, int flags,
		uint32_t min, uint32_t max, uint32_t def) {
	TYPE_CONVERTER(uint32_t)
}

int64_t	value_as_int64(value_t *v, int flags,
		int64_t min, int64_t max, int64_t def) {
	TYPE_CONVERTER(int64_t)
}

uint64_t value_as_uint64(value_t *v, int flags,
		uint64_t min, uint64_t max, uint64_t def) {
	TYPE_CONVERTER(uint64_t)
}

float value_as_float(value_t *v, int flags,
		float min, float max, float def) {
	TYPE_CONVERTER(float)
}

double value_as_double(value_t *v, int flags,
		double min, double max, double def) {
	TYPE_CONVERTER(double)
}

char *value_as_string(value_t *v, int flags, 
		char *buffer, size_t bufsize, char *def) {
	char *ret = NULL;
	
	if(v->flags & VALUE_STRING) {
		if(v->val.str != NULL && buffer != NULL) {
			xstrncpy(buffer,v->val.str,bufsize);
			ret = buffer;
		} else if(v->val.str != NULL && buffer == NULL) {
			ret = xstrdup(v->val.str);
		}
	} else if(v->flags & VALUE_BOOL) {
		ret = value_as_bool_str(v,
			v->flags & (VALUE_BOOL_ONOFF | VALUE_BOOL_YESNO),
			buffer,bufsize);
	} else if(v->flags & VALUE_INT32) {
		if(!(v->flags & VALUE_ENUM)) {
			if(buffer == NULL) {
				bufsize = 12;
				buffer = xalloc(bufsize);
			}
			snprintf(buffer,bufsize,
				v->flags & VALUE_UNSIGNED ? "%u" : "%d",
				v->flags & VALUE_UNSIGNED ? 
				v->val.ui32 : v->val.i32);
			ret = buffer;
		} else {
			if(buffer != NULL) {
				xstrncpy(buffer,v->num.de((int)v->val.i32),
						bufsize);
				ret = buffer;
			} else
				ret = xstrdup(v->num.de((int)v->val.i32));
		}		
	} else if(v->flags & VALUE_INT64) {
		if(buffer == NULL) {
			bufsize = 24;
			buffer = xalloc(bufsize);
		}
		snprintf(buffer,bufsize,
			v->flags & VALUE_UNSIGNED ? "%llu" : "%lld",
			v->flags & VALUE_UNSIGNED ?
			v->val.ui64 : v->val.i64);
		ret = buffer;
	} else if(v->flags & VALUE_DOUBLE) {
		if(buffer == NULL) {
			bufsize = 64;
			buffer = xalloc(bufsize);
		}
		snprintf(buffer,bufsize,
				flags & VALUE_NICE_FLOATS ? "%.2f" : "%.10e",
				v->val.d);
		ret = buffer;
	}
	
	if(ret == NULL && !(flags & VALUE_ALLOW_NULL)) {
		if(flags & VALUE_USE_DEFAULT)
			ret = def;
		if(ret == NULL) {
			if(buffer != NULL) {
				xstrncpy(buffer,"",bufsize);
				ret = buffer;
			} else
				ret = xstrdup("");
		}
	}		
	
	if(ret != def && (flags && VALUE_FREE_DEFAULT)) {
		if(def != NULL)
			xfree(def);
	}
	
	return ret;
}

int str_to_bool(char *str) {
	if(str == NULL)
		return -1;
	switch(tolower(str[0])) {
		case 'f': return 0;
		case 'n': return 0;
		case 'o':
			  if(strcasecmp(str,"on") == 0)
				  return 1;
			  else if(strncasecmp(str,"of",2) == 0)
				  return 0;
			  break;
		case 't': return 1;
		case 'y': return 1;
		case '0': return 0;
		case '1': return 1;
		default:
			  break;
	}
	return -1;
}

int value_as_bool(value_t *v, int flags, int def) {
	if(!(v->flags & VALUE_BOOL)) {
		char buffer[32];
		int ret;
		
		value_as_string(v,flags & ~VALUE_ALLOW_NULL,
				buffer,sizeof(buffer),NULL);
		
		if((ret = str_to_bool(buffer)) != -1)
			return ret;
	} else
		return v->val.i32;
	
	if(flags & VALUE_USE_DEFAULT)
		return def;
	else
		return 0;
}

char *value_as_bool_str(value_t *v, int flags, char *buffer, size_t bufsize) {
	int val = value_as_bool(v,flags,0);
	
	if(buffer == NULL) {
		bufsize = 8;
		buffer = xalloc(bufsize);
	}
	
	if(flags & VALUE_BOOL_ONOFF) {
		if(val)
			xstrncpy(buffer,"on",bufsize);
		else
			xstrncpy(buffer,"off",bufsize);
	} else if(flags & VALUE_BOOL_YESNO) {
		if(val)
			xstrncpy(buffer,"yes",bufsize);
		else
			xstrncpy(buffer,"no",bufsize);
	} else {
		if(val)
			xstrncpy(buffer,"true",bufsize);
		else
			xstrncpy(buffer,"false",bufsize);
	}

	return buffer;
}

int value_as_enum(value_t *v, int flags, int def) {
	int ret = value_as_int32(v,0,0,0,0);

	if(ret == -1 && (flags & VALUE_USE_DEFAULT))
		ret = def;
	
	return ret;
}

int value_set_enumeration(value_t *v, 
		int (*en)(const char *), const char *(*de)(int)) {
	if(v->flags & VALUE_STRING) {
		if(en == NULL)
			return -1;
		v->num.en = en;
	} else {
		if(de == NULL)
			return -1;
		v->num.de = de;
	}
	v->flags |= VALUE_ENUM;
	return 0;
}

/** enum specific functions **/
const char *interfacetype_to_str(int type) {
	switch(type) {
		case INTERFACE_SEND:	return "SEND";
		case INTERFACE_RECV:	return "RECV";
		case INTERFACE_CHAT:	return "CHAT";
		default:		return NULL;
	}
}

int str_to_interfacetype(const char *str) {
	if(str == NULL)
		return -1;
	switch(tolower(str[0]) == 0) {
		case 's': return INTERFACE_SEND;
		case 'r': return INTERFACE_RECV;
		case 'c': return INTERFACE_CHAT;
		default: return -1;
	}
}

const char *interfacemode_to_str(int mode) {
	switch(mode) {
		case IMODE_DCCSERVER:	return "dccserver";
		case IMODE_NORMAL:	return "normal";
		case IMODE_REVERSE:	return "reverse";
		default:		return NULL;	
	}
}

int str_to_interfacemode(const char *str) {
	if(str == NULL)
		return -1;
	switch(tolower(str[0])) {
		case 'd': return IMODE_DCCSERVER;
		case 'n': return IMODE_NORMAL;
		case 'r': return IMODE_REVERSE;
		default: return -1;
	}
}

const char *listtype_to_str(int type) {
	switch(type) {
		case LIST_NORMAL:	return "NORMAL";
		case LIST_NOPACKS:	return "NOPACKS";
		case LIST_TAGONLY:	return "TAGONLY";
		case LIST_CHAT:		return "CHAT";
		default:		return NULL;
	}
}

int str_to_listtype(const char *str) {
	if(str == NULL)
		return -1;
	switch(tolower(str[0])) {
		case 'c':	return LIST_CHAT;
		case 'n':	if(strncasecmp(str,"nor",3) == 0)
					return LIST_NORMAL;
				else if(strncasecmp(str,"nop",3) == 0)
					return LIST_NOPACKS;
				else
					return -1;
		case 't':	return LIST_TAGONLY;
		default:	return -1;
	}
}

const char *pformat_to_str(int pformat) {
	switch(pformat) {
		case PFORMAT_MINIMAL:	return "Minimal";
		case PFORMAT_SUMMARY:	return "Summary";
		case PFORMAT_FULL:	return "Full";
		case PFORMAT_TAGONLY:	return "Tagonly";
		default:		return NULL;
	}
}

int str_to_pformat(const char *str) {
	if(str == NULL)
		return -1;
	switch(tolower(str[0])) {
		case 'f': return PFORMAT_FULL;
		case 'm': return PFORMAT_MINIMAL;
		case 's': return PFORMAT_SUMMARY;
		case 't': return PFORMAT_TAGONLY;
		default: return -1;
	}
}

const char *networkstate_to_str(int ns) {
	switch(ns) {
		case NETWORK_OFFLINE:	return "OFFLINE";
		case NETWORK_RUNNING:	return "RUNNING";
		case NETWORK_LOGIN:	return "LOGGING IN";
		case NETWORK_CONNECTING:return "CONNECTING";
		case NETWORK_CONNECTED:	return "CONNECTED";
		default:		return NULL;
	}
}

const char *chattype_to_str(int ct) {
	switch(ct) {
		case CHAT_ADMIN:	return "ADMIN";
		case CHAT_LIST:		return "LIST";
		default:		return NULL;
	}
}

const char *colourtype_to_str(int ct) {
	switch(ct) {
		case COLOUR_NONE:	return "NONE";
		case COLOUR_IRC:	return "IRC";
		case COLOUR_INT:	return "INTERNAL";
		default:		return NULL;
	}
}

int str_to_colourtype(const char *str) {
	switch(tolower(str[0])) {
		case 'n':	return COLOUR_NONE;
		case 'i':	if(tolower(str[1]) == 'r')
					return COLOUR_IRC;
				else if(tolower(str[1]) == 'n')
					return COLOUR_INT;
				break;
		default:	break;
	}
	return -1;
}

const char *state_to_str(int s) {
	switch(s) {
		case STATE_UNKNOWN:	return "UNKNOWN";
		case STATE_QUEUED:	return "QUEUED";
		case STATE_RESOLVING:	return "RESOLVING";
		case STATE_CONNECTING:	return "CONNECTING";
		case STATE_NEGOTIATING:	return "NEGOTIATING";
		case STATE_LISTENING:	return "LISTENING";
		case STATE_ACTIVE:	return "ACTIVE";
		case STATE_COMPLETING:	return "COMPLETING";
		case STATE_COMPLETE:	return "COMPLETE";
		case STATE_SLOW:	return "SLOW";
		case STATE_TIMEOUT:	return "TIMEOUT";
		case STATE_DELETED:	return "DELETED";
		case STATE_PURGING:	return "PURGING";
		case STATE_PURGEREQUEUE:return "REQUEUE";
		case STATE_ERROR:	return "ERROR";
		case STATE_RETRY:	return "RETRY";
		case STATE_RESUME:	return "RESUME";
		case STATE_RESUMED:	return "RESUMED";
		default:		return NULL;
	}
}

int str_to_sorttype(const char *str) {
	switch(tolower(str[0])) {
		case 'a':		return SORT_AZ;
		case 'z':		return SORT_ZA;
		case 'i': 
			switch(tolower(str[1])) {
				case 'a': return SORT_I_AZ;
				case 'z': return SORT_I_ZA;
				default:  break;
			}
			break;
	}
	return SORT_NONE;
}

const char *sorttype_to_str(int st) {
	switch(st) {
		case SORT_NONE:	return "none";
		case SORT_AZ:	return "az";
		case SORT_ZA:	return "za";
		case SORT_I_AZ:	return "iaz";
		case SORT_I_ZA:	return "iza";
		default:	return NULL;
	}
}

int str_to_hashtype(const char *str) {
	if(strncasecmp(str,"crc+",4) == 0 || strcasecmp(str,"c+m") == 0) {
		return HASH_CRC_AND_MD5;
	} else if(tolower(str[0]) == 'c') {
		return HASH_CRC;
	} else if(tolower(str[0]) == 'm') {
		return HASH_MD5;
	} else {
		return HASH_NONE;
	}
}

const char *hashtype_to_str(int ht) {
	switch(ht) {
		case HASH_NONE:	return "none";
		case HASH_CRC:	return "crc";
		case HASH_MD5:	return "md5";
		case HASH_CRC_AND_MD5: return "crc+md5";
		default:	return NULL;
	}
}

int str_to_responsetype(const char *str) {
	switch(tolower(str[0])) {
		case 'n': return RESPONSE_NOTICE;
		case 'p': return RESPONSE_PRIVMSG;
		default:  return -1;
	}
}

const char *responsetype_to_str(int rt) {
	switch(rt) {
		case RESPONSE_NOTICE:	return "notice";
		case RESPONSE_PRIVMSG:	return "privmsg";
		default:		return NULL;
	}
}
