#MUDPyE - (M)ulti-(U)ser (D)imension (Py)thon (E)ngine
#Copyright (C) 2005  Corey Staten

#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.

#Send feedback/questions to MUDPyE@gmail.com

import copy
import mptraceback
import types

staticTypes = [types.LongType, types.IntType, types.FloatType, types.StringType, types.BooleanType, types.NoneType]

class CallableNone(object):
  """Simulates None, but is callable(doing nothing)."""
  def __eq__(self, x):
    return x == None

  def __ge__(self, x):
    return x >= None

  def __le__(self, x):
    return x <= None

  def __gt__(self, x):
    return x > None

  def __lt__(self, x):
    return x < None

  def __ne__(self, x):
    return x != None

  def __call__(self, *args, **kwargs):
    return None

callNone = CallableNone()

class MudFunctionWrapper(object):
  """Wraps context functions to catch error source file."""
  def __init__(self, func, srcContext):
    self.func = func
    self.srcContext = srcContext

  def __call__(self, *args, **kwargs):
    try:
      #print "Exec: %s - %s%s" % (self.srcContext["_srcType"], self.func.func_name, args)
      return self.func(*args, **kwargs)
    except:
      self.srcContext["mudWorld"].mudTraceback.append("* %s in %s: %s\r\n" % (self.func.func_name, self.srcContext["_srcType"], args))
      raise


def GenMudObjectRefType(genMudWorld):
  class MudObjectRef(object):
    """Class used to represent references to MUD objects."""
    mudWorld = genMudWorld

    def __init__(self, ID):
      object.__setattr__(self, "objDict", None)
      object.__setattr__(self, "objID", ID)

    def __getattribute__(self, key):
      objDict = object.__getattribute__(self, "objDict")
      objID = object.__getattribute__(self, "objID")
      #Make sure the object is loaded.
      if objDict is None:
        objDict = MudObjectRef.mudWorld.objDB.GetObjDict(objID)
        if objDict is None:
          MudObjectRef.mudWorld.loggers["mud.scripterror"].error("Invalid reference to object [%s]." % objID)
          raise IOError, "Unable to load object %s, invalid reference." % objID
        object.__setattr__(self, "objDict", objDict)
      if objDict.has_key(key):
        returnVal = objDict[key]
        #If the value type isn't static(IE it can be changed), we need to dirty the object.
        if (type(returnVal) not in staticTypes) and (objID not in MudObjectRef.mudWorld.objDB.dirtyObjs):
          MudObjectRef.mudWorld.objDB.dirtyObjs.append(objID)
        #We need to lambdadize functions if they are returned, to simulate that they are bound to the object.
        if type(returnVal) is MudFunctionWrapper:
          return lambda *args: returnVal(self, *args)
        return returnVal
      #Next, check the source context.
      sourceContext = MudObjectRef.mudWorld.sourceDB.GetContext(objDict["_objType"])
      if sourceContext.has_key(key):
        returnVal = sourceContext[key]
        #We need to lambdadize functions if they are returned, to simulate that they are bound to the object.
        if type(returnVal) is MudFunctionWrapper:
          return lambda *args, **kwargs: returnVal(self, *args, **kwargs)
        return returnVal
      #Value not found.
      raise AttributeError, "MudObject %s has no attribute %s." % (objID, key)

    def __setattr__(self, key, value):
      objDict = object.__getattribute__(self, "objDict")
      objID = object.__getattribute__(self, "objID")
      #Make sure the object is loaded.
      if objDict is None:
        objDict = MudObjectRef.mudWorld.objDB.GetObjDict(objID)
        if objDict is None:
          MudObjectRef.mudWorld.loggers["mud.scripterror"].error("Invalid reference to object [%s]." % objID)
          return None
        object.__setattr__(self, "objDict", objDict)
      objDict[key] = value
      if objID not in MudObjectRef.mudWorld.objDB.dirtyObjs:
        MudObjectRef.mudWorld.objDB.dirtyObjs.append(objID)

    def __delattr__(self, key):
      objDict = object.__getattribute__(self, "objDict")
      objID = object.__getattribute__(self, "objID")
      #Make sure the object is loaded.
      if objDict is None:
        objDict = MudObjectRef.mudWorld.objDB.GetObjDict(objID)
        if objDict is None:
          MudObjectRef.mudWorld.loggers["mud.scripterror"].error("Invalid reference to object [%s]." % objID)
          return None
        object.__setattr__(self, "objDict", objDict)
      del objDict[key]
      if objID not in MudObjectRef.mudWorld.objDB.dirtyObjs:
        MudObjectRef.mudWorld.objDB.dirtyObjs.append(objID)
        

    def __repr__(self):
      return "<MudObjectRef: %s>" % object.__getattribute__(self, "objID")

    def __str__(self):
      return "<MudObjectRef: %s>" % object.__getattribute__(self, "objID")

  return MudObjectRef

def Ex(mudObjRef, funcName, *args, **kwargs):
  return ExAsType(mudObjRef, mudObjRef._objType, funcName, *args, **kwargs)

def ExAsType(mudObjRef, objType, funcName, *args, **kwargs):
  """Executes function as objType.  Generally used for calling functions in a parent context."""
  found = False
  returnVal = None
  mudWorld = type(mudObjRef).mudWorld
  srcContext = mudWorld.sourceDB.GetContext(objType)
  if srcContext == None:
    type(mudObjRef).mudWorld.loggers["mud.scripterror"].warning("Attempt to execute function [%s] with non-existant object type [%s]." % (funcName, objType))
    return (False, None)
  if (srcContext.has_key(funcName)) and callable(srcContext[funcName]):
    try:
      returnVal = srcContext[funcName](mudObjRef, *args, **kwargs)
      found = True
    except:
      mudWorld.loggers["mud.scripterror"].error("Error executing function [%s] on objType [%s]:\r\n%s" % (funcName, objType, mptraceback.mud_traceback(mudWorld)))
  return (found, returnVal)

def ExParentToChild(mudObjRef, funcName, *args, **kwargs):
  """Executes function starting with the highest parent and working its way down.
     Note that this doesn't count levels, it fully backward explores the parent
     tree of the first item in the _parents list before moving on to the second."""
  _RecursiveExParentToChild(mudObjRef, mudObjRef._objType, [], funcName, *args, **kwargs)

def _RecursiveExParentToChild(mudObjRef, curType, exList, funcName, *args, **kwargs):
  """Recursive helper of ExParentToChild."""
  curContext = type(mudObjRef).mudWorld.sourceDB.GetContext(curType)
  exList.append(curType)
  if curContext == None:
    type(mudObjRef).mudWorld.loggers["mud.scripterror"].error("Unable to load context [%s]." % curType)
    return
  for parent in curContext["_parents"]:
    if parent not in exList:
      _RecursiveExParentToChild(mudObjRef, parent, exList, funcName, *args, **kwargs)
  ExAsType(mudObjRef, curType, funcName, *args, **kwargs)

def ExChildToParent(mudObjRef, funcName, *args, **kwargs):
  """Executes function starting with the child and working its way up through the parents.
     Note that this doesn't count levels, it fully backward explores the parent
     tree of the first item in the _parents list before moving on to the second."""
  _RecursiveExChildToParent(mudObjRef, mudObjRef._objType, [], funcName, *args, **kwargs)

def _RecursiveExChildToParent(mudObjRef, curType, exList, funcName, *args, **kwargs):
  """Recursive helper of ExChildToParent."""
  ExAsType(mudObjRef, curType, funcName, *args, **kwargs)
  curContext = type(mudObjRef).mudWorld.sourceDB.GetContext(curType)
  exList.append(curType)
  if curContext == None:
    type(mudObjRef).mudWorld.loggers["mud.scripterror"].error("Unable to load context [%s]." % curType)
    return
  for parent in curContext["_parents"]:
    if parent not in exList:
      _RecursiveExChildToParent(mudObjRef, parent, exList, funcName, *args, **kwargs)

def VersionUpdate(mudObjRef):
  """Handles updating objects when their versions are mismatched."""
  versions = mudObjRef._versions
  _RecursiveVersionUpdate(mudObjRef, versions, [], mudObjRef._objType)

def _RecursiveVersionUpdate(mudObjRef, versions, exList, curType):
  curContext = type(mudObjRef).mudWorld.sourceDB.GetContext(curType)
  exList.append(curType)
  if curContext == None:
    type(mudObjRef).mudWorld.loggers["mud.scripterror"].error("Unable to load context [%s]." % curType)
    return
  for parent in curContext["_parents"]:
    if parent not in exList:
      _RecursiveVersionUpdate(mudObjRef, versions, exList, parent)
  if not(versions.has_key(curType)):
      versions[curType] = curContext["_version"]
  elif (versions[curType] != curContext["_version"]):
      if (curContext.has_key("_Sys_VersionUpdate")) and callable(curContext["_Sys_VersionUpdate"]):
        newVersion = ExAsType(curType, mudObjRef, "_Sys_VersionUpdate", versions[curType])
      else:
        type(mudObjRef).mudWorld.loggers["engine.db"].warning("Version mismatch with no VersionUpdate function: %s version (%d) to (%d)" % (curType, versions[curType], curContext["_version"]))