#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 cPickle
import marshal
import types

#This is a simple module that wraps around cPickle.
#It replaces things that can not be unpickled properly with "None".
#Pickling is done recursively, so no "bad" objects can possibly get through(IE, it's uncrashable theoretically).

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

class Save_MudObjectRef(object):
  def __init__(self, objID):
    self.objID = objID

  def __getstate__(self):
    return self.objID

  def __setstate__(self, objID):
    self.objID = objID
    
class Save_Tuple(object):
  def __init__(self, elementList):
    self.elementList = elementList

  def __getstate__(self):
    return self.elementList
    
  def __setstate__(self, elementList):
    self.elementList = elementList

def SimplifyTupleElement(element):
  elementType = type(element)
  if elementType in easyTypes:
    return element
  elif elementType is types.TupleType:
    return RecursiveSimplifyTuple(element)
  else:
    return None

def RecursiveSimplifyTuple(curTuple):
  newList = map(SimplifyTupleElement, curTuple)
  return tuple(newList)

def RecursiveNullifyBadValues(convertObject, seenObjs):
  """Returns a version of an object that is safe to pickle.  Doesn't change the object itself."""
  if seenObjs.has_key(id(convertObject)):
    return seenObjs[id(convertObject)]
  returnVal = None
  objectType = type(convertObject)
  if objectType in easyTypes:
    returnVal = convertObject
  elif objectType is types.ListType:
    returnVal = []
    seenObjs[id(convertObject)] = returnVal
    returnVal.extend(map(RecursiveNullifyBadValues, convertObject, [seenObjs]*len(convertObject)))
  elif objectType is types.DictType:
    returnVal = {}
    seenObjs[id(convertObject)] = returnVal
    for key,val in convertObject.items():
      returnVal[key] = RecursiveNullifyBadValues(val, seenObjs)
  elif objectType is types.TupleType:
    newList = []
    returnVal = Save_Tuple(newList)
    seenObjs[id(convertObject)] = returnVal
    newList.extend(map(RecursiveNullifyBadValues, convertObject, [seenObjs]*len(convertObject)))
    #Tuples need to be of simple types only.  Non-constant tuples don't work, because to make them
    #work we would need to be able to create the copied tuple BEFORE recursing over its elements.
    #Since we can't modify tuples this way, we're forced to restrict them to simple types.
    #returnVal = RecursiveSimplifyTuple(convertObject)
  elif objectType.__name__ == "MudObjectRef":
    returnVal = Save_MudObjectRef(object.__getattribute__(convertObject, "objID"))
  elif objectType.__name__ == "CallableNone":
    returnVal = None
  else:
    try:
      returnVal = RecursiveNullifyBadValues(convertObject._Sys_Serialize(), seenObjs)
    except:
      pass
  return returnVal

def dumps(convertObject, proto=0):
  return cPickle.dumps(RecursiveNullifyBadValues(convertObject, {}), proto)

def RecursiveLoadReferences(loadObj, seenObjs, getRefFunc):
  if seenObjs.has_key(id(loadObj)):
    return seenObjs[id(loadObj)]
  returnVal = loadObj
  objectType = type(loadObj)
  if objectType is types.ListType:
    returnVal = []
    seenObjs[id(loadObj)] = returnVal
    returnVal.extend(map(RecursiveLoadReferences, loadObj, [seenObjs]*len(loadObj), [getRefFunc]*len(loadObj)))
  elif objectType is types.DictType:
    returnVal = {}
    seenObjs[id(loadObj)] = returnVal
    for key,val in loadObj.items():
      returnVal[key] = RecursiveLoadReferences(val, seenObjs, getRefFunc)
  elif objectType is Save_Tuple:
    returnVal = tuple(loadObj.elementList)
  elif objectType is Save_MudObjectRef:
    returnVal = getRefFunc(loadObj.objID)
  return returnVal


def loads(pickledString, getRefFunc):
  return RecursiveLoadReferences(cPickle.loads(pickledString), {}, getRefFunc)