## BugConfig
##
## Parses a BUG configuration XML file to initialize BUG and its mod components.
##
## This module contains element handler base classes that other components
## can use to build specific handlers. While reading a file, the parser keeps
## a stack of nested elements and their handlers which other handlers can
## access to pull inherited information.
##
## For example, the root <bug> element can define a "module" attribute that
## can be inherited by child elements that require one. These inherited
## attributes can be overridden by any element in the nesting, and the new
## value will only apply to it and its children.
##
## Inherited attributes can issue a warning when the overriding value
## doesn't make sense given the context, for example lowering the DLL
## API version. Inherited attributes may be cleared by specifying
## an empty value, e.g. <init dll=""/>.
##
## TODO
## 
##   - Remove child attributes from ctor
##   - Change child tags to child handlers and remove from ctor
##   - Register using keys instead of tags
##   - Fallback attributes should create their fallbacks automatically
##   - Don't register root handler
##   - Move elements here closer to where their modules are initialized in XML
##
## Copyright (c) 2009 The BUG Mod.
##
## Author: EmperorFool

import BugCore
import BugDll
import BugInit
import BugTypes
import BugUtil
import CvEventInterface
import types

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


# Constants

BLOCK_SIZE = 512


## Parsing Dispatcher

# block error alert about xmllib deprecation
try:
    import sys
    stderr = sys.stderr
    sys.stderr = sys.stdout
    import xmllib
finally:
    sys.stderr = stderr

class ConfigParser(xmllib.XMLParser):
    
    def __init__(self):
        xmllib.XMLParser.__init__(self)
    
    def parse(self, path):
        self._path = path
        self.start()
        try:
            file = open(path)
            try:
                while True:
                    s = file.read(BLOCK_SIZE)
                    if not s:
                        break
                    self.feed(s)
                self.close()
            finally:
                file.close()
        except IOError:
            BugUtil.trace("BugConfig - IOError parsing %s", path)
            raise
        except BugUtil.ConfigError:
            BugUtil.trace("BugConfig - failure parsing %s at line %d", path, self.lineno)
            raise
        except:
            BugUtil.trace("BugConfig - failure parsing %s at line %d", path, self.lineno)
            raise
    
    def start(self):
        self._handler = g_rootHandler
        self._element = g_rootHandler.start()
        self._stack = [(self._handler, self._element)]
    
    def unknown_starttag(self, tag, attrs):
        try:
            #BugUtil.debug("BugConfig - %s", self.format(tag, attrs))
            self._handler, self._element = self._handler.startChild(self._element, tag, attrs)
            self._stack.append((self._handler, self._element))
        except:
            BugUtil.trace("BugConfig - failure parsing %s at line %d" % (self._path, self.lineno))
    
    def unknown_endtag(self, tag):
        try:
            #BugUtil.debug("BugConfig - end <%s>", tag)
            handler, element = self._stack.pop()
            if self._stack:
                self._handler, self._element = self._stack[-1]
                self._handler.endChild(self._element, handler, element)
            else:
                self._handler = None
                self._element = None
                handler.end(element)
        except:
            BugUtil.trace("BugConfig - failure parsing %s at line %d" % (self._path, self.lineno))
    
    def handle_data(self, data):
        self._element.addText(data)
    
    def syntax_error(self, message):
        raise BugUtil.ConfigError("error parsing configuration: %s", message)

    def format(self, tag, attrs):
        xml = "<" + tag
        for key, value in attrs.iteritems():
            xml += " " + key + "=\"" + value + "\""
        xml += ">"
        return xml


## Element State Tracking

class Element:
    
    def __init__(self, handler, parent, tag, attrs={}, saveText=False):
        self.handler = handler
        self.parent = parent
        self.tag = tag
        self.attrs = attrs
        self.flatAttrs = {}
        self.processed = False
        self.state = {}
        if saveText:
            self.text = ""
        else:
            self.text = None
    
    def addText(self, text):
        if self.text is not None:
            self.text += text
    
    def hasAttribute(self, name):
        return name in self.attrs
    
    def getAttribute(self, name, default=None, inherit=False):
        if self.hasAttribute(name):
            return self.attrs[name]
        if inherit:
            return self.getInheritedAttribute(name, default)
        else:
            return default
    
    def getInheritedAttribute(self, name, default=None):
        if self.parent:
            return self.parent.getAttribute(name, default, True)
        else:
            return default
    
    def getRequiredAttribute(self, name, inherit=False):
        value = self.getAttribute(name, None, inherit)
        if value is not None:
            return value
        else:
            raise BugUtil.ConfigError("Element <%s> requires attribute %s", self.tag, name)
    
    def getRequiredInheritedAttribute(self, name):
        value = self.getInheritedAttribute(name, None)
        if value is not None:
            return value
        else:
            raise BugUtil.ConfigError("Element <%s> requires inherited attribute %s", self.tag, name)
    
    def getFlatAttribute(self, name):
        return self.flatAttrs.get(name)
    
    def setFlatAttribute(self, name, value):
        self.flatAttrs[name] = value
    
    def isProcessed(self):
        return self.processed
    
    def setProcessed(self, value):
        self.processed = value
    
    def setState(self, key, value):
        self.state[key] = value
    
    def getState(self, key):
        if key in self.state:
            return self.state[key]
        elif self.parent:
            return self.parent.getState(key)
        else:
            return None

class ElementWithArgs(Element):
    
    def __init__(self, handler, parent, tag, attrs={}, saveText=False):
        Element.__init__(self, handler, parent, tag, attrs, saveText)
        self.args = []
        self.kwargs = {}


## Handler Registration

g_handlers = {}

def registerHandler(tag, handler):
    BugUtil.debug("BugConfig - registering %s handler %s", tag, handler.__class__)
    g_handlers[tag] = handler

def isRegistered(tag):
    return tag in g_handlers

def getHandler(tag):
    try:
        return g_handlers[tag]
    except KeyError:
        raise BugUtil.ConfigError("unknown configuration element %s", tag)


## Handler Base Classes

class Handler:
    
    def __init__(self, tag, validAttrs="", validChildren="", elementClass=Element):
        self.tag = tag
        self.attributes = []
        self.validAttrs = set(validAttrs.split())
        if isinstance(validChildren, types.StringTypes):
            validChildren = validChildren.split()
        self.validChildren = set(validChildren)
        self.accumulatedTags = set()
        self.elementClass = elementClass
    
    def addValidAttribute(self, name):
        self.validAttrs.add(name)
    
    def addAttribute(self, name, required=False, inherited=False, default=None, fallback=None, exclude=False):
        self.attributes.append((name, required, inherited, default, fallback, exclude))
    
    def addExcludedAttribute(self, name, inherited=False, default=None, fallback=None):
        self.addAttribute(name, False, inherited, default, fallback, True)
    
    def setAttribute(self, name, *values):
        for i in range(len(self.attributes)):
            attr = self.attributes[i]
            if attr[0] == name:
                attr = list(attr)
                for j in range(len(values)):
                    if j > 4:
                        break
                    attr[j + 1] = values[j]
                break
        else:
            raise BugUtil.ConfigError("Missing attribute %s", name)
    
    def isValidAttribute(self, name):
        return name in self.validAttrs
    
    def addValidChild(self, tag, accumuated=False):
        self.validChildren.add(tag)
        if accumuated:
            self.accumulatedTags.add(tag)
    
    def addAccumulatedChild(self, tag):
        self.addValidChild(tag, True)
    
    def isValidChild(self, tag):
        return tag in self.validChildren
    
    def getChildHandler(self, tag):
        return getHandler(tag)
    
    def start(self, parent=None, tag=None, attrs={}):
        self.validate(parent, tag, attrs)
        element = self.createElement(parent, tag, attrs)
        self.flatten(element)
        return element
    
    def validate(self, parent, tag, attrs):
        for attr in attrs:
            if not self.isValidAttribute(attr):
                BugUtil.warn("BugConfig - invalid <%s> attribute %s", tag, attr)
    
    def createElement(self, parent, tag, attrs):
        return self.elementClass(self, parent, tag, attrs, self.isSaveText(tag, attrs))
    
    def isSaveText(self, tag, attrs):
        return False
    
    def flatten(self, element):
        for (name, required, inherit, default, fallback, exclude) in self.attributes:
            if default is None and fallback:
                default = element.getFlatAttribute(fallback)
            value = element.getAttribute(name, default, inherit)
            if value is None and required:
                raise BugUtil.ConfigError("Element <%s> requires attribute '%s'", element.tag, name)
            element.setFlatAttribute(name, value)
    
    def process(self, element):
        if not element.isProcessed():
            values = []
            for attr in self.attributes:
                if not attr[5]:
                    values.append(element.getFlatAttribute(attr[0]))
            self.handle(element, *values)
            element.setProcessed(True)
    
    def handle(self, element, *values):
        pass
    
    def startChild(self, parentElement, tag, attrs):
        if not self.isValidChild(tag):
            raise BugUtil.ConfigError("Element <%s> does not accept child <%s>", parentElement.tag, tag)
        if tag not in self.accumulatedTags:
            self.process(parentElement)
        handler = self.getChildHandler(tag)
        element = handler.start(parentElement, tag, attrs)
        return (handler, element)
    
    def endChild(self, parentElement, handler, element):
        handler.end(element)
    
    def end(self, element):
        self.process(element)
        self.complete(element)
    
    def complete(self, element):
        pass
    
    def isDllOkay(self, element, dll):
        dll = self.resolveDll(element, dll)
        if dll is not None:
            element.setState("dll", dll)
            return BugDll.isVersion(dll)
        return True
    
    def resolveDll(self, element, dll):
        dll = BugDll.decode(dll)
        inherited = element.getState("dll")
        if dll is None:
            return inherited
        if inherited is None:
            return dll
        if inherited > dll:
            BugUtil.warn("BugConfig - element <%s>.dll attribute %s overrides newer inherited dll attribute %s" % (element.tag, dll, inherited))
        return dll
    
    def isTrue(self, value):
        return BugTypes.isTrue(value)
    
    def createValue(self, type, value):
        return BugTypes.to(type, value)

class HandlerWithArgs(Handler):
    
    def __init__(self, tag, validAttrs="", validChildren=""):
        Handler.__init__(self, tag, validAttrs, validChildren, ElementWithArgs)
        self.addAccumulatedChild(ArgHandler.TAG)


## Core Handlers

class RootHandler(Handler):
    
    TAG = "xml"
    
    def __init__(self):
        Handler.__init__(self, self.TAG, "", (BugHandler.TAG, ModHandler.TAG))

class BugHandler(Handler):
    
    TAG = "bug"
    
    def __init__(self):
        Handler.__init__(self, self.TAG, "module dll", (LoadHandler.TAG, ConfigHandler.TAG, ModHandler.TAG, ))
        self.addAttribute("module")
        self.addAttribute("dll")
    
    def handle(self, element, module, dll):
        dll = BugDll.decode(dll)
        element.setState("dll", dll)
        if module and dll:
            BugUtil.debug("BugConfig - configuration for module %s requires DLL version %s", module, dll)

class LoadHandler(Handler):
    
    TAG = "load"
    
    def __init__(self):
        Handler.__init__(self, self.TAG, "name mod")
        self.addExcludedAttribute("mod")
        self.addAttribute("name", True, False, None, "mod")
    
    def handle(self, element, name):
        BugUtil.debug("BugConfig - loading mod file %s", name)
        BugInit.loadMod(name)

class ConfigHandler(Handler):
    
    TAG = "config"
    
    def __init__(self):
        Handler.__init__(self, self.TAG, 
                "root tag key parents module class", ConfigHandler.TAG)
        self.addAttribute("root")
        self.addAttribute("tag", True, False, None, "root")
        self.addAttribute("key", True, False, None, "tag")
        self.addAttribute("parents")
        self.addAttribute("module", True, True, None, "root")
        self.addAttribute("class", True, False, None, "root")
        #addChildHandler(self)
    
    def handle(self, element, root, tag, key, parents, module, clazz):
        if root:
            element.setState("config-handler", getHandler(root))
        else:
            if isRegistered(key):
                BugUtil.warn("BugConfig - <%s>.key %s handler already registered, ignoring", element.tag, key)
            else:
                handler = BugUtil.callFunction(module, clazz)
                registerHandler(tag, handler)
                # automatically include in enclosing <config> handler
                parent = element.getState("config-handler")
                if parent:
                    parentHandlers = [parent]
                else:
                    parentHandlers = []
                # add listed parents
                if parents:
                    for parent in parents.split():
                        if parent == "-":
                            parentHandlers = []
                        else:
                            parentHandlers.append(getHandler(parent))
                # place in bug handler if none specified
                if not parentHandlers:
                    parentHandlers.append(g_bugHandler)
                # register with each parent handler
                for parent in parentHandlers:
                    parent.addValidChild(tag)
                    #parent.addChildHandler(handler)
                element.setState("config-handler", handler)

class ArgHandler(Handler):
    
    TAG = "arg"
    
    def __init__(self):
        Handler.__init__(self, self.TAG, "name type value")
        self.addAttribute("name")
        self.addAttribute("type")
        self.addAttribute("value")
    
    def isSaveText(self, tag, attrs):
        return "value" not in attrs
    
    def handle(self, element, name, type, value):
        # create argument and store on element.parent
        if value is None:
            value = element.text
        arg = self.createValue(type, value)
        if name:
            element.parent.kwargs[name] = arg
        else:
            element.parent.args.append(arg)

class ModHandler(Handler):
    
    TAG = "mod"
    
    def __init__(self):
        Handler.__init__(self, self.TAG, 
                "id module name author version build date url dll", 
                (LoadHandler.TAG))
        self.addAttribute("id")
        self.addAttribute("name")
        self.addAttribute("dll")
        self.addAttribute("author")
        self.addAttribute("version")
        self.addAttribute("build")
        self.addAttribute("date")
        self.addAttribute("url")
    
    def handle(self, element, id, name, dll, author, version, build, date, url):
        dll = BugDll.decode(dll)
        element.setState("mod", BugCore.game._getMod(id))
        element.setState("dll", BugDll.decode(dll))
    
    def complete(self, element):
        mod = element.getState("mod")
        mod._initDone()
        BugCore.game._addMod(mod)


## Standard Handlers

class InitHandler(HandlerWithArgs):
    
    TAG = "init"
    
    def __init__(self):
        HandlerWithArgs.__init__(self, self.TAG, "module function immediate dll")
        self.addAttribute("module", True, True)
        self.addAttribute("function", True, False, "init")
        self.addAttribute("immediate", False, False, "False")
        self.addAttribute("dll")
    
    def handle(self, element, module, function, immediate, dll):
        immediate = self.isTrue(immediate)
        dll = BugDll.decode(dll)
        if self.isDllOkay(element, dll):
            func = BugUtil.getFunction(module, function, True, *element.args, **element.kwargs)
            if immediate:
                func()
            else:
                BugInit.addInit(module, func)
        else:
            BugUtil.info("BugConfig - ignoring <%s> %s.%s, requires dll version %s", element.tag, module, function, self.resolveDll(element, dll))

class EventsHandler(HandlerWithArgs):
    
    TAG = "events"
    
    def __init__(self):
        HandlerWithArgs.__init__(self, self.TAG, "module function class dll")
        self.addAttribute("module", True, True)
        self.addExcludedAttribute("function", False, None, "module")
        self.addAttribute("class", True, False, None, "function")
        self.addAttribute("dll")
    
    def handle(self, element, module, clazz, dll):
        dll = BugDll.decode(dll)
        if self.isDllOkay(element, dll):
            BugUtil.callFunction(module, clazz, CvEventInterface.getEventManager(), *element.args, **element.kwargs)
        else:
            BugUtil.info("BugConfig - ignoring <%s> from %s.%s, requires dll version %s", element.tag, module, clazz, self.resolveDll(element, dll))

class EventHandler(HandlerWithArgs):
    
    TAG = "event"
    
    def __init__(self):
        HandlerWithArgs.__init__(self, self.TAG, "type module function dll")
        self.addAttribute("type", True)
        self.addAttribute("module", True, True)
        self.addAttribute("function", True)
        self.addAttribute("dll")
    
    def handle(self, element, type, module, function, dll):
        dll = BugDll.decode(dll)
        if self.isDllOkay(element, dll):
            CvEventInterface.getEventManager().addEventHandler(type, BugUtil.getFunction(module, function, True, *element.args, **element.kwargs))
        else:
            BugUtil.info("BugConfig - ignoring <%s> %s, requires dll version %s", element.tag, type, self.resolveDll(element, dll))

class ExportHandler(Handler):
    
    TAG = "export"
    
    def __init__(self):
        Handler.__init__(self, self.TAG, "module function to as dll")
        self.addAttribute("module", True, True)
        self.addAttribute("function", True)
        self.addAttribute("to", True)
        self.addAttribute("as", True, False, None, "function")
        self.addAttribute("dll")
    
    def handle(self, element, module, function, toModule, asName, dll):
        dll = BugDll.decode(dll)
        if self.isDllOkay(element, dll):
            BugUtil.exportFunction(module, function, toModule, asName)
        else:
            BugUtil.info("BugConfig - ignoring <%s> %s.%s, requires dll version %s", element.tag, module, function, self.resolveDll(element, dll))

class ExtendHandler(Handler):
    
    TAG = "extend"
    
    def __init__(self):
        Handler.__init__(self, self.TAG, "how module function to as dll")
        self.addAttribute("how", True, False, BugUtil.EXTEND_INSTEAD)
        self.addAttribute("module", True, True)
        self.addAttribute("function", True)
        self.addAttribute("to", True)
        self.addAttribute("as", True, False, None, "function")
        self.addAttribute("dll")
    
    def handle(self, element, how, module, function, toModule, asName, dll):
        dll = BugDll.decode(dll)
        if self.isDllOkay(element, dll):
            BugUtil.extendFunction(module, function, toModule, asName, how)
        else:
            BugUtil.info("BugConfig - ignoring <%s> %s.%s, requires dll version %s", element.tag, module, function, self.resolveDll(element, dll))


## Initialization

g_rootHandler = None
g_bugHandler = None

def init():
    global g_rootHandler, g_bugHandler
    g_rootHandler = RootHandler()
    g_bugHandler = BugHandler()
    
    registerHandler(RootHandler.TAG, g_rootHandler)
    registerHandler(BugHandler.TAG, g_bugHandler)
    registerHandler(LoadHandler.TAG, LoadHandler())
    registerHandler(ConfigHandler.TAG, ConfigHandler())
    registerHandler(ArgHandler.TAG, ArgHandler())
    
#   registerHandler(ModHandler.TAG, ModHandler())
    
#   parser = ConfigParser()
#   parser.parse(r"C:\Coding\Civ\BUG\BUG\CustomAssets\Config\NewConfig.xml")

init()
