"""Exports Script class, a base for xmlScript, used by ScriptableAgent.

The main goal of scripts is to draw a boundary between the generic core 
code and the game-specific extensions.

Script defines a set methods (actions and handlers). Methods are implemented
in native Python code. Method's code is executed with exec function and can 
transparently access script's calling context. Because of that script methods 
can dynamically add new handlers, actions, states and attributes to the owner 
object (ScriptableAgent).

Inheritance for scripts is implemented in Java-style fashion. Methods from 
the current script override same-name methods from the inherited script. 
Overridden methods are still accessible with scope specification. Scripts 
support multiple inheritance. The search for the inherited method is done 
using depth first method.
"""

class Script:
    """Script keeps and can execute string presentation of methods 
    (scripted event handlers and actions).
    When method is called, script runs it using exec() function.
    """
    def __init__(self, owner=None, name=""):
        self.methods = {}
        self.name = name
        self.owner = owner
        self.inherited = []

    def findInherited(self,scriptname):
        """Finds script by name using depth first approach."""
        #__depth__ first search
        for s in self.inherited:
            if (s.name == scriptname):
                return s
            scr = s.findInherited(scriptname)
            if (scr!=None):
                return scr

    def findMethod(self,name):
        """Finds method by name in itself or inherited scripts. 
        If it is a simple call of a method, then it just searches 
        for a script that has its implementation, depth frist. 
        If it is a name with a scope (scriptname.methodname) then it starts 
        searching from a script with a name 'scriptname'. 
        In all cases scripts are searched using depth frist approach."""
        if (name.find(".")==-1): #simple case - no dots
            if (self.methods.has_key(name)):
                return self.methods[name]
            elif (self.inherited!=None):
                for s in self.inherited:
                    m = s.findMethod(name)
                    if (m!=None):
                        return m
        else:
            parts =  name.split(".")
            s = self.findInherited(parts[0])
            if (s!=None):
                return s.findMethod(parts[1])
        return None

    def callMethod(self, name, params=None):
        """Invokes method using exec(), returns 1 if succeeded. 
        If method not found, the fucntion bails out and returns 0. 
        Search is done using findMethod, so it covers current 
        script's own dictionary and dictionaries of the inherited 
        scripts.
        Note. Although Script does not distinguish between actions 
        (state updaters) and handlers (called on commands), scripted 
        handlers still expect to receive message as a parameter.
        """
        method = self.findMethod(name)
        if (method!=None):
            #print "METHOD ",name
            try:
                exec(method)
            except:
                self.handleExceptionInMethod(name,method)
            return 1
        else:
            return 0

    def handleExceptionInMethod(self,name,method):
        """Handles exceptions in the executed string. 
        Provides some information for trouble shooting."""
        import sys,traceback
        print "ERROR: can't execute method ", name
        print sys.exc_type, sys.exc_value
        #print sys.exc_type, sys.exc_value, sys.exc_traceback
        #traceback.print_exc(file=sys.stdout)