## BugTypes
##
## Provides functions for dealing with types and values in XML and INI files.
##
## normalize(type)
##   Returns the canonical type or raises an exception if not found.
##
## default(type)
##   Returns the default value for <type> after normalizing it.
##
## isTrue(value, noneIsFalse=True)
##   Returns True if <value> is one of the valid string representations for True.
##
## to(type, value, noneIsDefault=True, emptyIsDefault=True)
##   Converts <value> from a string to <type>.
##
## TODO
##
##   Create ability to add new types. Add "key" type.
##
## Copyright (c) 2009 The BUG Mod.
##
## Author: EmperorFool

import BugUtil

# BUG - Mac Support - start
BugUtil.fixSets(globals())
# BUG - Mac Support - end


## Type Constants

NONE = "none"

BOOL = "boolean"
BOOLEAN = BOOL
INT = "int"
INTEGER = INT
FLOAT = "float"
REAL = FLOAT
STRING = "string"
COLOR = "color"

TUPLE = "tuple"
LIST = "list"
SET = "set"
DICT = "dict"


## Normalizing Types

NORMALIZED_TYPES = {
	BOOL: BOOL,
	"bool": BOOL,
	"bit": BOOL,
	INT: INT,
	"integer": INT,
	"long": INT,
	"number": INT,
	FLOAT: FLOAT,
	"real": FLOAT,
	"double": FLOAT,
	"decimal": FLOAT,
	STRING: STRING,
	"str": STRING,
	COLOR: COLOR,
	
	TUPLE: TUPLE,
	"vector": TUPLE,
	LIST: LIST,
	"array": LIST,
	SET: SET,
	DICT: DICT,
	"map": DICT,
}

def normalize(type):
	"""
	Returns the canonical type or raises an exception if not found.
	
	If <type> is None or empty (""), returns None.
	"""
	if not type:
		return None
	try:
		return NORMALIZED_TYPES[type.lower()]
	except:
		raise BugUtil.ConfigError("Invalid type %s", type)


## Defaults

DEFAULT_CONSTANT = 0
DEFAULT_FUNCTION = 1

DEFAULTS = {
	BOOL: (DEFAULT_CONSTANT, False),
	INT: (DEFAULT_CONSTANT, 0),
	FLOAT: (DEFAULT_CONSTANT, 0.0),
	STRING: (DEFAULT_CONSTANT, ""),
	COLOR: (DEFAULT_CONSTANT, "COLOR_WHITE"),
	TUPLE: (DEFAULT_CONSTANT, ()),
	LIST: (DEFAULT_FUNCTION, list),
	SET: (DEFAULT_FUNCTION, set),
	DICT: (DEFAULT_FUNCTION, dict),
}

def default(type):
	"""
	Returns the default value for <type> after normalizing it.
	
	If <type> is None or empty (""), None is returned.
	"""
	type = normalize(type)
	if not type:
		return None
	try:
		form, default = DEFAULTS[type]
	except KeyError:
		raise BugUtil.ConfigError("Invalid type %s", type)
	else:
		if form == DEFAULT_CONSTANT:
			return default
		else:
			return default()


## Converting Values

TRUE_STRINGS = ('true', 't', 'yes', 'y', 'on', '1')

CONVERT_FROM_STRING = {
	BOOL: lambda x: x.lower() in TRUE_STRINGS,
	STRING: lambda x: x,
	INT: lambda x: int(x),
	FLOAT: lambda x: float(x),
	COLOR: lambda x: x,
	TUPLE: lambda x: eval("(%s,)" % x),
	LIST: lambda x: eval("[%s]" % x),
	SET: lambda x: eval("set([%s])" % x),
	DICT: lambda x: eval("{%s}" % x),
}

def isTrue(value, noneIsFalse=True):
	"""
	Returns True if <value> is one of the valid string representations for True.
	
	By default, None is considered False.
	"""
	if not value:
		if noneIsFalse:
			return False
		else:
			return None
	else:
		return value.lower() in TRUE_STRINGS

def to(type, value, noneIsDefault=True, emptyIsDefault=True):
	"""
	Converts <value> from a string to <type>.
	
	If <type> is None or empty (""), the value is evaluated directly using eval();
	otherwise <type> is normalized first. If <value> is None or empty ("") as well,
	the special value None is returned.
	
	If <value> is None or empty (""), the default is used based on the optional parameters.
	
	The <type> is normalized first, and line endings (\r\n) are replaced with spaces in <value>.
	"""
	if type:
		if not value:
			if (value is None and noneIsDefault) or (value == "" and emptyIsDefault):
				return default(type)
			else:
				return None
		else:
			try:
				return CONVERT_FROM_STRING[normalize(type)](value.replace("\r\n", " "))
			except KeyError:
				raise BugUtil.ConfigError("Invalid type %s", type)
			except:
				raise BugUtil.ConfigError("Invalid %s: %s", type, value)
	else:
		if value:
			try:
				return eval(value)
			except:
				raise BugUtil.ConfigError("Invalid expression %s", value)
		else:
			return None
