## FontUtil
##
## Utilities for dealing with FontSymbols, "XXX_CHAR" keys, and "[ICON_XXX]" tags in XML messages.
## "init.xml" adds most of the built-in and BUG-related symbol keys by using this module's functions.
## You can add your own using the <symbol> XML entity.
##
## Getting Symbols
##
##   getSymbol(symbolOrKey)
##	 Returns a FontSymbols instance matching the given symbol or key.
##	 If passed a FontSymbols instance, it is returned. If a string, it is looked
##	 up in this modules list of known symbols.
##
##   getChar(symbolOrKey)
##	 Returns a string containing a single-character for the desired <symbolOrKey>.
##
##   getOrdinal(symbolOrKey)
##	 Returns the Unicode ordinal for the desired <symbolOrKey>.
##
## Message Processing
##
##   replaceSymbols(text, unknownReplacement)
##	 Returns a copy of <text> after replacing all occurrances of "[ICON_XXX]" with
##	 the symbols registered in this module. Any symbol that isn't found is replaced
##	 with <unknownReplacement> (default "").
##
## Notes
##   - Must be initialized externally by calling init()
##
## Copyright (c) 2008 The BUG Mod.
##
## Author: EmperorFool

from CvPythonExtensions import *
import BugConfig
import BugDll
import BugUtil
import CvUtil
import re

## constants

UNKNOWN_CHAR = "?"
SYMBOL_REGEXP = re.compile(r"\[ICON_([a-zA-Z0-9_]+)\]")

## globals

gc = CyGlobalContext()
nextSymbolID = int(FontSymbols.MAX_NUM_SYMBOLS)

# key -> symbol (FontSymbols)
keySymbols = {}

# symbol -> primary key (string)
symbolPrimaryKeys = {}

# symbol -> ordinal (int)
symbolOrdinals = {}

# symbol -> character (unicode string)
symbolChars = {}

## initialization and registration

def init():
	symbolNames = {}
	for name, symbol in FontSymbols.__dict__.iteritems():
		if name.endswith("_CHAR") and isinstance(symbol, FontSymbols):
			symbolNames[symbol] = name
	for key, symbol in CvUtil.OtherFontIcons.iteritems():
		addBuiltinSymbol(key, symbol)
		if symbol in symbolNames:
			name = symbolNames[symbol]
			registerSymbolSynonym(key, symbol, name[:-5])
			registerSymbolSynonym(key, symbol, name)

#			del symbolNames[symbol]
	# add the FontSymbols that aren't in CvUtil
#	for symbol, name in symbolNames:
#		addBuiltinSymbol()
	
	for count, getInfo in (
		(YieldTypes.NUM_YIELD_TYPES, gc.getYieldInfo),
		(CommerceTypes.NUM_COMMERCE_TYPES, gc.getCommerceInfo),
	):
		for enum in range(count):
			info = getInfo(enum)
			addSymbol(info.getType().lower().replace("_", " "), 
					info.getChar(), info.getType())

def addBuiltinSymbol(key, symbol):
	registerSymbol(key, symbol, gc.getGame().getSymbolID(symbol))

def addOffsetSymbol(key, symbolOrKey, offset, name=None):
	return addSymbol(key, getOrdinal(symbolOrKey) + offset, name)

def addSymbol(key, ordinal, name=None):
	if not name:
		name = key.upper().replace(" ", "_")
	else:
		name = name.upper().replace(" ", "_")
	symbolName = name + "_CHAR"
	symbol = findOrCreateSymbol(symbolName)
	registerSymbol(key, symbol, ordinal)
	registerSymbolSynonym(key, symbol, name)
	registerSymbolSynonym(key, symbol, symbolName)
	return symbol

def findOrCreateSymbol(name):
	try:
		symbol = getattr(FontSymbols, name)
		if isinstance(symbol, FontSymbols):
			BugUtil.debug("FontUtil - found FontSymbols name %s", name)
			return symbol
	except AttributeError:
		pass
	# create a FontSymbols enum for it
	global nextSymbolID
	symbol = FontSymbols(nextSymbolID)
	nextSymbolID += 1
	BugUtil.debug("FontUtil - created FontSymbols.%s", name)
	setattr(FontSymbols, name, symbol)
	return symbol

def registerSymbol(key, symbol, ordinal):
	BugUtil.info("FontUtil - registering symbol '%s' for %d", key, ordinal)
	if key in keySymbols:
		raise BugUtil.ConfigError("duplicate font symbol key '%s'" % key)
	if symbol in symbolPrimaryKeys:
		raise BugUtil.ConfigError("duplicate font symbol for key '%s'" % key)
	keySymbols[key] = symbol
	symbolPrimaryKeys[symbol] = key
	symbolOrdinals[symbol] = ordinal
	symbolChars[symbol] = u"%c" % ordinal
	
def registerSymbolSynonym(key, symbol, synonym):
	if synonym in keySymbols:
		BugUtil.warn("FontUtil - ignoring duplicate synonym '%s' for key '%s'", synonym, key)
	else:
		BugUtil.debug("FontUtil - registering synonym '%s'", synonym)
		keySymbols[synonym] = symbol


## symbol lookup

def getSymbol(symbolOrKey):
	if isinstance(symbolOrKey, FontSymbols):
		return symbolOrKey
	try:
		return keySymbols[symbolOrKey]
	except KeyError:
		try:
			return keySymbols[symbolOrKey.upper() + "_CHAR"]
		except KeyError:
			raise BugUtil.ConfigError("unknown font symbol or key '%s'" % str(symbolOrKey))

def getOrdinal(symbolOrKey):
	try:
		return symbolOrdinals[getSymbol(symbolOrKey)]
	except KeyError:
		raise BugUtil.ConfigError("unknown font symbol or key '%s'" % str(symbolOrKey))

def getChar(symbolOrKey):
	try:
		return symbolChars[getSymbol(symbolOrKey)]
	except KeyError:
		raise BugUtil.ConfigError("unknown font symbol or key '%s'" % str(symbolOrKey))


## message processing

def replaceSymbols(text, unknownReplacement=""):
	def replace(match):
		try:
			return getChar(match.group(1))
		except BugUtil.ConfigError:
			return unknownReplacement
	return SYMBOL_REGEXP.sub(replace, text)


## configuration handler

class SymbolHandler(BugConfig.Handler):
	
	TAG = "symbol"
	
	def __init__(self):
		BugConfig.Handler.__init__(self, SymbolHandler.TAG, "id name from offset dll")
		self.addAttribute("id", True)
		self.addAttribute("name")
		self.addAttribute("from")
		self.addAttribute("offset")
		self.addAttribute("dll")
		self.lastSymbol = None
	
	def handle(self, element, id, name, fromKey, offset, dll):
		dll = BugDll.decode(dll)
		if self.isDllOkay(element, dll):
			if not fromKey:
				if not self.lastSymbol:
					raise BugUtil.ConfigError("<%s> %s requires an offset symbol" % (element.tag, id))
				fromKey = self.lastSymbol
			if offset is None:
				offset = 1
			else:
				offset = int(offset)
			self.lastSymbol = addOffsetSymbol(id, fromKey, offset, name)
		else:
			BugUtil.info("FontUtil - ignoring <%s> %s, requires dll version %s", element.tag, id, self.resolveDll(element, dll))
