"""
This is the basis of the typeclass system.

The idea is have the object as a normal class with the
database-connection tied to itself through a property.

The instances of all the different object types are all tied to their
own database object stored in the 'dbobj' property.  All attribute
get/set operations are channeled transparently to the database object
as desired. You should normally never have to worry about the database
abstraction, just do everything on the TypeClass object.

That an object is controlled by a player/user is just defined by its
'user' property being set.  This means a user may switch which object
they control by simply linking to a new object's user property.
"""

import traceback
from django.conf import settings

from evennia.typeclasses.models import TypeclassBase
from evennia.typeclasses.attributes import NickHandler
from evennia.objects.manager import ObjectManager
from evennia.objects.models import ObjectDB
from evennia.scripts.scripthandler import ScriptHandler
from evennia.commands import cmdset, command
from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.commands import cmdhandler
from evennia.utils.logger import log_depmsg, log_trace, log_errmsg
from evennia.utils.utils import (variable_from_module, lazy_property,
                             make_iter, to_str, to_unicode)

MULTISESSION_MODE = settings.MULTISESSION_MODE

_ScriptDB = None
_SESSIONS = None

_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
# the sessid_max is based on the length of the db_sessid csv field (excluding commas)
_SESSID_MAX = 16 if MULTISESSION_MODE in (1, 3) else 1

from django.utils.translation import ugettext as _

class SessidHandler(object):
    """
    Handles the get/setting of the sessid
    comma-separated integer field
    """
    def __init__(self, obj):
        self.obj = obj
        self._cache = set()
        self._recache()

    def _recache(self):
        self._cache = list(set(int(val) for val in (self.obj.db_sessid or "").split(",") if val))

    def get(self):
        "Returns a list of one or more session ids"
        return self._cache

    def add(self, sessid):
        "Add sessid to handler"
        _cache = self._cache
        if sessid not in _cache:
            if len(_cache) >= _SESSID_MAX:
                return
            _cache.append(sessid)
            self.obj.db_sessid = ",".join(str(val) for val in _cache)
            self.obj.save(update_fields=["db_sessid"])

    def remove(self, sessid):
        "Remove sessid from handler"
        _cache = self._cache
        if sessid in _cache:
            _cache.remove(sessid)
            self.obj.db_sessid =  ",".join(str(val) for val in _cache)
            self.obj.save(update_fields=["db_sessid"])

    def clear(self):
        "Clear sessids"
        self._cache = []
        self.obj.db_sessid = None
        self.obj.save(update_fields=["db_sessid"])

    def count(self):
        "Return amount of sessions connected"
        return len(self._cache)



#
# Base class to inherit from.
#

class DefaultObject(ObjectDB):
    """
    This is the root typeclass object, representing all entities
    that have an actual presence in-game. Objects generally have a
    location. They can also be manipulated and looked at. Most
    game entities you define should inherit from Object at some distance.
    Evennia defines some important subclasses of Object by default, namely
    Characters, Exits and Rooms (see the bottom of this module).

    Note that all new Objects and their subclasses *must* always be
    created using the ev.create_object() function. This is so the
    typeclass system can be correctly initiated behind the scenes.


    Object Typeclass API:

    * Available properties (only available on *initiated* typeclass objects)

     key (string) - name of object
     name (string) - same as key
     aliases (list of strings) - aliases to the object. Will be saved to
                 database as AliasDB entries but returned as strings.
     dbref (int, read-only) - unique #id-number. Also "id" can be used.
     date_created (string) - time stamp of object creation
     permissions (list of strings) - list of permission strings

     player (Player) - controlling player (if any, only set together with
                      sessid below)
     sessid (int, read-only) - session id (if any, only set together with
                      player above)
     location (Object) - current location. Is None if this is a room
     home (Object) - safety start-location
     sessions (list of Sessions, read-only) - returns all sessions
                 connected to this object
     has_player (bool, read-only)- will only return *connected* players
     contents (list of Objects, read-only) - returns all objects inside
                     this object (including exits)
     exits (list of Objects, read-only) - returns all exits from this
                 object, if any
     destination (Object) - only set if this object is an exit.
     is_superuser (bool, read-only) - True/False if this user is a superuser

    * Handlers available

     locks - lock-handler: use locks.add() to add new lock strings
     db - attribute-handler: store/retrieve database attributes on this
                             self.db.myattr=val, val=self.db.myattr
     ndb - non-persistent attribute handler: same as db but does not
                             create a database entry when storing data
     scripts - script-handler. Add new scripts to object with scripts.add()
     cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
     nicks - nick-handler. New nicks with nicks.add().

    * Helper methods (see evennia.objects.objects.py for full headers)

     search(ostring, global_search=False, use_nicks=True,
            typeclass=None,
            attribute_name=None, use_nicks=True, location=None,
            quiet=False, exact=False)
     execute_cmd(raw_string)
     msg(text=None, from_obj=None, sessid=0, **kwargs)
     msg_contents(message, exclude=None, from_obj=None, **kwargs)
     move_to(destination, quiet=False, emit_to_obj=None,
             use_destination=True, to_none=False)
     copy(new_key=None)
     delete()
     is_typeclass(typeclass, exact=False)
     swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
     access(accessing_obj, access_type='read', default=False)
     check_permstring(permstring)

    * Hook methods

     basetype_setup()     - only called once, used for behind-the-scenes
                            setup. Normally not modified.
     basetype_posthook_setup() - customization in basetype, after the
                             object has been created; Normally not modified.

     at_object_creation() - only called once, when object is first created.
                            Object customizations go here.
     at_object_delete() - called just before deleting an object. If
                          returning False, deletion is aborted. Note that
                          all objects inside a deleted object are
                          automatically moved to their <home>, they don't
                          need to be removed here.

     at_init()            called whenever typeclass is cached from
                          memory, at least once every server restart/reload
     at_cmdset_get(**kwargs) - this is called just before the command
                            handler requests a cmdset from this object, usually
                            without any kwargs
     at_pre_puppet(player)- (player-controlled objects only) called just
                             before puppeting
     at_post_puppet()     - (player-controlled objects only) called just
                             after completing connection player<->object
     at_pre_unpuppet()    - (player-controlled objects only) called just
                             before un-puppeting
     at_post_unpuppet(player) (player-controlled objects only) called
                              just after disconnecting player<->object link
     at_server_reload()   - called before server is reloaded
     at_server_shutdown() - called just before server is fully shut down

     at_before_move(destination)    called just before moving
                                    object to the destination. If returns
                                    False, move is cancelled.
     announce_move_from(destination)  - called in old location, just before
                                        move, if obj.move_to() has
                                        quiet=False
     announce_move_to(source_location) - called in new location,
                                         just after move, if obj.move_to()
                                         has quiet=False
     at_after_move(source_location)    - always called after a move
                                         has been successfully performed.
     at_object_leave(obj, target_location)   - called when an object leaves
                                               this object in any fashion
     at_object_receive(obj, source_location) - called when this object
                                               receives another object
     at_access(result, **kwargs) - this is called with the result of an
                                   access call, along with any kwargs used
                                   for that call. The return of this
                                   method does not affect the result of the
                                   lock check.
     at_before_traverse(traversing_object) - (exit-objects only) called
                                              just before an object
                                              traverses this object
     at_after_traverse(traversing_object, source_location) - (exit-objects
                          only) called just after a traversal has happened.
     at_failed_traverse(traversing_object)      - (exit-objects only) called
                if traversal fails and property err_traverse is not defined.

     at_msg_receive(self, msg, from_obj=None, data=None) - called when a
                             message (via self.msg()) is sent to this obj.
                             If returns false, aborts send.
     at_msg_send(self, msg, to_obj=None, data=None) - called when this
                         objects sends a message to someone via self.msg().

     return_appearance(looker) - describes this object. Used by "look"
                                 command by default
     at_desc(looker=None)      - called by 'look' whenever the appearance
                                 is requested.
     at_get(getter)            - called after object has been picked up.
                                 Does not stop pickup.
     at_drop(dropper)          - called when this object has been dropped.
     at_say(speaker, message)  - by default, called if an object inside
                                 this object speaks

     """
    # typeclass setup
    __metaclass__ = TypeclassBase
    objects = ObjectManager()

    # on-object properties

    @lazy_property
    def cmdset(self):
        return CmdSetHandler(self, True)

    @lazy_property
    def scripts(self):
        return ScriptHandler(self)

    @lazy_property
    def nicks(self):
        return NickHandler(self)

    @lazy_property
    def sessid(self):
        return SessidHandler(self)

    @property
    def sessions(self):
        """
        Retrieve sessions connected to this object.
        """
        # if the player is not connected, this will simply be an empty list.
        if self.db_player:
            return self.db_player.get_all_sessions()
        return []

    @property
    def has_player(self):
        """
        Convenience function for checking if an active player is
        currently connected to this object
        """
        return any(self.sessions)

    @property
    def is_superuser(self):
        "Check if user has a player, and if so, if it is a superuser."
        return self.db_player and self.db_player.is_superuser \
                and not self.db_player.attributes.get("_quell")

    def contents_get(self, exclude=None):
        """
        Returns the contents of this object, i.e. all
        objects that has this object set as its location.
        This should be publically available.

        exclude is one or more objects to not return
        """
        return ObjectDB.objects.get_contents(self, excludeobj=exclude)
    contents = property(contents_get)


    @property
    def exits(self):
        """
        Returns all exits from this object, i.e. all objects
        at this location having the property destination != None.
        """
        return [exi for exi in self.contents if exi.destination]

    # main methods

    ## methods inherited from the database object (overload them here)

    def search(self, searchdata,
               global_search=False,
               use_nicks=True,  # should this default to off?
               typeclass=None,
               location=None,
               attribute_name=None,
               quiet=False,
               exact=False):
        """
        Returns the typeclass of an Object matching a search string/condition

        Perform a standard object search in the database, handling
        multiple results and lack thereof gracefully. By default, only
        objects in self's current location or inventory is searched.
        Note: to find Players, use eg. ev.player_search.

        Inputs:

        searchdata (str or obj): Primary search criterion. Will be matched
                    against object.key (with object.aliases second) unless
                    the keyword attribute_name specifies otherwise.
                    Special strings:
                        #<num> - search by unique dbref. This is always
                                 a global search.
                        me,self - self-reference to this object
                        <num>-<string> - can be used to differentiate
                                         between multiple same-named matches
        global_search (bool): Search all objects globally. This is overruled
                              by "location" keyword.
        use_nicks (bool): Use nickname-replace (nicktype "object") on the
                          search string
        typeclass (str or Typeclass, or list of either): Limit search only
                   to Objects with this typeclass. May be a list of typeclasses
                   for a broader search.
        location (Object): Specify a location to search, if different from the
                     self's given location plus its contents. This can also
                     be a list of locations.
        attribute_name (str): Define which property to search. If set, no
                      key+alias search will be performed. This can be used to
                      search database fields (db_ will be automatically
                      appended), and if that fails, it will try to return
                      objects having Attributes with this name and value
                      equal to searchdata. A special use is to search for
                      "key" here if you want to do a key-search without
                      including aliases.
        quiet (bool) - don't display default error messages - this tells the
                      search method that the user wants to handle all errors
                      themselves. It also changes the return value type, see
                      below.
        exact (bool) - if unset (default) - prefers to match to beginning of
                      string rather than not matching at all. If set, requires
                      exact mathing of entire string.

        Returns:
            quiet=False (default):
                no match or multimatch:
                auto-echoes errors to self.msg, then returns None
                    (results are handled by settings.SEARCH_AT_RESULT
                               and settings.SEARCH_AT_MULTIMATCH_INPUT)
                match:
                    a unique object match
            quiet=True:
                returns a list of 0, 1 or more matches

        """
        is_string = isinstance(searchdata, basestring)

        if is_string:
            # searchdata is a string; wrap some common self-references
            if searchdata.lower() in ("here", ):
                return self.location
            if searchdata.lower() in ("me", "self",):
                return self

        if use_nicks:
            # do nick-replacement on search
            searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)

        candidates=None
        if(global_search or (is_string and searchdata.startswith("#") and
                    len(searchdata) > 1 and searchdata[1:].isdigit())):
            # only allow exact matching if searching the entire database
            # or unique #dbrefs
            exact = True
        elif location:
            # location(s) were given
            candidates = []
            for obj in make_iter(location):
                candidates.extend(obj.contents)
        else:
            # local search. Candidates are self.contents, self.location
            # and self.location.contents
            location = self.location
            candidates = self.contents
            if location:
                candidates = candidates + [location] + location.contents
            else:
                # normally we are included in location.contents
                candidates.append(self)

        results = ObjectDB.objects.object_search(searchdata,
                                                 attribute_name=attribute_name,
                                                 typeclass=typeclass,
                                                 candidates=candidates,
                                                 exact=exact)
        if quiet:
            return results
        return  _AT_SEARCH_RESULT(self, searchdata, results, global_search)

    def search_player(self, searchdata, quiet=False):
        """
        Simple shortcut wrapper to search for players, not characters.

        searchdata - search criterion - the key or dbref of the player
                     to search for. If this is "here" or "me", search
                     for the player connected to this object.
        quiet - return the results as a list rather than echo eventual
                standard error messages.

        Returns:
            quiet=False (default):
                no match or multimatch:
                    auto-echoes errors to self.msg, then returns None
                    (results are handled by settings.SEARCH_AT_RESULT
                                 and settings.SEARCH_AT_MULTIMATCH_INPUT)
                match:
                    a unique player match
            quiet=True:
                no match or multimatch:
                    returns None or list of multi-matches
                match:
                    a unique object match
        """
        if isinstance(searchdata, basestring):
            # searchdata is a string; wrap some common self-references
            if searchdata.lower() in ("me", "self",):
                return self.player

        results = self.player.__class__.objects.player_search(searchdata)

        if quiet:
            return results
        return _AT_SEARCH_RESULT(self, searchdata, results, global_search=True)

    def execute_cmd(self, raw_string, sessid=None, **kwargs):
        """
        Do something as this object. This method is a copy of the execute_
        cmd method on the session. This is never called normally, it's only
        used when wanting specifically to let an object be the caller of a
        command. It makes use of nicks of eventual connected players as well.

        Argument:
        raw_string (string) - raw command input
        sessid (int) - optional session id to return results to
        **kwargs - other keyword arguments will be added to the found command
                   object instace as variables before it executes. This is
                   unused by default Evennia but may be used to set flags and
                   change operating paramaters for commands at run-time.

        Returns Deferred - this is an asynchronous Twisted object that will
            not fire until the command has actually finished executing. To
            overload this one needs to attach callback functions to it, with
            addCallback(function). This function will be called with an
            eventual return value from the command execution.

            This return is not used at all by Evennia by default, but might
            be useful for coders intending to implement some sort of nested
            command structure.
        """
        # nick replacement - we require full-word matching.

        # do text encoding conversion
        raw_string = to_unicode(raw_string)
        raw_string = self.nicks.nickreplace(raw_string,
                     categories=("inputline", "channel"), include_player=True)
        return cmdhandler.cmdhandler(self, raw_string, callertype="object", sessid=sessid, **kwargs)


    def msg(self, text=None, from_obj=None, sessid=0, **kwargs):
        """
        Emits something to a session attached to the object.

        message (str): The message to send
        from_obj (obj): object that is sending.
        data (object): an optional data object that may or may not
                       be used by the protocol.
        sessid (int): sessid to relay to, if any.
                      If set to 0 (default), use either from_obj.sessid (if set) or self.sessid automatically
                      If None, echo to all connected sessions

        When this message is called, from_obj.at_msg_send and self.at_msg_receive are called.

        """
        global _SESSIONS
        if not _SESSIONS:
            from evennia.server.sessionhandler import SESSIONS as _SESSIONS

        text = to_str(text, force_string=True) if text else ""

        if "data" in kwargs:
            # deprecation warning
            log_depmsg("ObjectDB.msg(): 'data'-dict keyword is deprecated. Use **kwargs instead.")
            data = kwargs.pop("data")
            if isinstance(data, dict):
                kwargs.update(data)

        if from_obj:
            # call hook
            try:
                from_obj.at_msg_send(text=text, to_obj=self, **kwargs)
            except Exception:
                log_trace()
        try:
            if not self.at_msg_receive(text=text, **kwargs):
                # if at_msg_receive returns false, we abort message to this object
                return
        except Exception:
            log_trace()

        sessions = _SESSIONS.session_from_sessid([sessid] if sessid else make_iter(self.sessid.get()))
        for session in sessions:
            session.msg(text=text, **kwargs)

    def msg_contents(self, message, exclude=None, from_obj=None, **kwargs):
        """
        Emits something to all objects inside an object.

        exclude is a list of objects not to send to. See self.msg() for
                more info.
        """
        contents = self.contents
        if exclude:
            exclude = make_iter(exclude)
            contents = [obj for obj in contents if obj not in exclude]
        for obj in contents:
            obj.msg(message, from_obj=from_obj, **kwargs)

    def move_to(self, destination, quiet=False,
                emit_to_obj=None, use_destination=True, to_none=False):
        """
        Moves this object to a new location.

        Moves this object to a new location. Note that if <destination> is an
        exit object (i.e. it has "destination"!=None), the move_to will
        happen to this destination and -not- into the exit object itself, unless
        use_destination=False. Note that no lock checks are done by this
        function, such things are assumed to have been handled before calling
        move_to.

        destination: (Object) Reference to the object to move to. This
                     can also be an exit object, in which case the destination
                     property is used as destination.
        quiet:  (bool)    If true, don't emit left/arrived messages.
        emit_to_obj: (Object) object to receive error messages
        use_destination (bool): Default is for objects to use the "destination"
                             property of destinations as the target to move to.
                             Turning off this keyword allows objects to move
                             "inside" exit objects.
        to_none - allow destination to be None. Note that no hooks are run when
                     moving to a None location. If you want to run hooks,
                     run them manually (and make sure they can manage None
                     locations).

        Returns True/False depending on if there were problems with the move.
                This method may also return various error messages to the
                emit_to_obj.
        """
        def logerr(string=""):
            trc = traceback.format_exc()
            errstring = "%s%s" % (trc, string)
            log_trace()
            self.msg(errstring)

        errtxt = _("Couldn't perform move ('%s'). Contact an admin.")
        if not emit_to_obj:
            emit_to_obj = self

        if not destination:
            if to_none:
                # immediately move to None. There can be no hooks called since
                # there is no destination to call them with.
                self.location = None
                return True
            emit_to_obj.msg(_("The destination doesn't exist."))
            return
        if destination.destination and use_destination:
            # traverse exits
            destination = destination.destination

        # Before the move, call eventual pre-commands.
        try:
            if not self.at_before_move(destination):
                return
        except Exception:
            logerr(errtxt % "at_before_move()")
            #emit_to_obj.msg(errtxt % "at_before_move()")
            #logger.log_trace()
            return False

        # Save the old location
        source_location = self.location
        if not source_location:
            # there was some error in placing this room.
            # we have to set one or we won't be able to continue
            if self.home:
                source_location = self.home
            else:
                default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
                source_location = default_home

        # Call hook on source location
        try:
            source_location.at_object_leave(self, destination)
        except Exception:
            logerr(errtxt % "at_object_leave()")
            #emit_to_obj.msg(errtxt % "at_object_leave()")
            #logger.log_trace()
            return False

        if not quiet:
            #tell the old room we are leaving
            try:
                self.announce_move_from(destination)
            except Exception:
                logerr(errtxt % "at_announce_move()")
                #emit_to_obj.msg(errtxt % "at_announce_move()" )
                #logger.log_trace()
                return False

        # Perform move
        try:
            #print "move_to location:", destination
            self.location = destination
        except Exception:
            emit_to_obj.msg(errtxt % "location change")
            log_trace()
            return False

        if not quiet:
            # Tell the new room we are there.
            try:
                self.announce_move_to(source_location)
            except Exception:
                logerr(errtxt % "announce_move_to()")
                #emit_to_obj.msg(errtxt % "announce_move_to()")
                #logger.log_trace()
                return  False

        # Perform eventual extra commands on the receiving location
        # (the object has already arrived at this point)
        try:
            destination.at_object_receive(self, source_location)
        except Exception:
            logerr(errtxt % "at_object_receive()")
            #emit_to_obj.msg(errtxt % "at_object_receive()")
            #logger.log_trace()
            return False

        # Execute eventual extra commands on this object after moving it
        # (usually calling 'look')
        try:
            self.at_after_move(source_location)
        except Exception:
            logerr(errtxt % "at_after_move")
            #emit_to_obj.msg(errtxt % "at_after_move()")
            #logger.log_trace()
            return False
        return True

    def clear_exits(self):
        """
        Destroys all of the exits and any exits pointing to this
        object as a destination.
        """
        for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]:
            out_exit.delete()
        for in_exit in ObjectDB.objects.filter(db_destination=self):
            in_exit.delete()

    def clear_contents(self):
        """
        Moves all objects (players/things) to their home
        location or to default home.
        """
        # Gather up everything that thinks this is its location.
        objs = ObjectDB.objects.filter(db_location=self)
        default_home_id = int(settings.DEFAULT_HOME.lstrip("#"))
        try:
            default_home = ObjectDB.objects.get(id=default_home_id)
            if default_home.dbid == self.dbid:
                # we are deleting default home!
                default_home = None
        except Exception:
            string = _("Could not find default home '(#%d)'.")
            log_errmsg(string % default_home_id)
            default_home = None

        for obj in objs:
            home = obj.home
            # Obviously, we can't send it back to here.
            if not home or (home and home.dbid == self.dbid):
                obj.home = default_home
                home = default_home

            # If for some reason it's still None...
            if not home:
                string = "Missing default home, '%s(#%d)' "
                string += "now has a null location."
                obj.location = None
                obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin."))
                log_errmsg(string % (obj.name, obj.dbid))
                return

            if obj.has_player:
                if home:
                    string = "Your current location has ceased to exist,"
                    string += " moving you to %s(#%d)."
                    obj.msg(_(string) % (home.name, home.dbid))
                else:
                    # Famous last words: The player should never see this.
                    string = "This place should not exist ... contact an admin."
                    obj.msg(_(string))
            obj.move_to(home)


    def copy(self, new_key=None):
        """
        Makes an identical copy of this object. If you want to customize the
        copy by changing some settings, use ObjectDB.object.copy_object()
        directly.

        new_key (string) - new key/name of copied object. If new_key is not
                            specified, the copy will be named <old_key>_copy
                            by default.
        Returns: Object (copy of this one)
        """
        def find_clone_key():
            """
            Append 01, 02 etc to obj.key. Checks next higher number in the
            same location, then adds the next number available

            returns the new clone name on the form keyXX
            """
            key = self.key
            num = 1
            for obj in (obj for obj in self.location.contents
                        if obj.key.startswith(key) and
                            obj.key.lstrip(key).isdigit()):
                num += 1
            return "%s%03i" % (key, num)
        new_key = new_key or find_clone_key()
        return ObjectDB.objects.copy_object(self, new_key=new_key)

    delete_iter = 0
    def delete(self):
        """
        Deletes this object.
        Before deletion, this method makes sure to move all contained
        objects to their respective home locations, as well as clean
        up all exits to/from the object.
        """
        global _ScriptDB
        if not _ScriptDB:
            from evennia.scripts.models import ScriptDB as _ScriptDB

        if self.delete_iter > 0:
            # make sure to only call delete once on this object
            # (avoid recursive loops)
            return False

        if not self.at_object_delete():
            # this is an extra pre-check
            # run before deletio field-related properties
            # is kicked into gear.
            self.delete_iter = 0
            return False

        self.delete_iter += 1

        # See if we need to kick the player off.

        for session in self.sessions:
            session.msg(_("Your character %s has been destroyed.") % self.key)
            # no need to disconnect, Player just jumps to OOC mode.
        # sever the connection (important!)
        if self.player:
            self.player.character = None
        self.player = None

        for script in _ScriptDB.objects.get_all_scripts_on_obj(self):
            script.stop()
        #for script in _GA(self, "scripts").all():
        #    script.stop()

        # if self.player:
        #     self.player.user.is_active = False
        #     self.player.user.save(

        # Destroy any exits to and from this room, if any
        self.clear_exits()
        # Clear out any non-exit objects located within the object
        self.clear_contents()
        self.attributes.clear()
        self.nicks.clear()
        self.aliases.clear()

        # Perform the deletion of the object
        super(ObjectDB, self).delete()
        return True
        # methods inherited from the typeclass system


    def __eq__(self, other):
        """
        Checks for equality against an id string or another object or user.

        This has be located at this level, having it in the
        parent doesn't work.
        """
        try:
            return self.dbid == other.dbid
        except AttributeError:
           # compare players instead
            try:
                return self.player.uid == other.player.uid
            except AttributeError:
                return False

    def at_first_save(self):
        """
        This is called by the typeclass system whenever an instance of
        this class is saved for the first time. It is a generic hook
        for calling the startup hooks for the various game entities.
        When overloading you generally don't overload this but
        overload the hooks called by this method.
        """
        self.basetype_setup()
        self.at_object_creation()

        if hasattr(self, "_createdict"):
            # this will only be set if the utils.create function
            # was used to create the object. We want the create
            # call's kwargs to override the values set by hooks.
            cdict = self._createdict
            updates = []
            if not cdict.get("key"):
                if not self.db_key:
                    self.db_key = "#%i" % self.dbid
                    updates.append("db_key")
            elif self.key != cdict.get("key"):
                updates.append("db_key")
                self.db_key = cdict["key"]
            if cdict.get("location") and self.location != cdict["location"]:
                self.db_location = cdict["location"]
                updates.append("db_location")
            if cdict.get("home") and self.home != cdict["home"]:
                self.home = cdict["home"]
                updates.append("db_home")
            if cdict.get("destination") and self.destination != cdict["destination"]:
                self.destination = cdict["destination"]
                updates.append("db_destination")
            if updates:
                self.save(update_fields=updates)

            if cdict.get("permissions"):
                self.permissions.add(cdict["permissions"])
            if cdict.get("locks"):
                self.locks.add(cdict["locks"])
            if cdict.get("aliases"):
                self.aliases.add(cdict["aliases"])
            if cdict.get("location"):
                cdict["location"].at_object_receive(self, None)
                self.at_after_move(None)

            if cdict.get("attributes"):
                # this should be a dict of attrname:value
                keys, values = cdict["attributes"].keys(), cdict["attributes"].values()
                self.attributes.batch_add(keys, values)
            if cdict.get("nattributes"):
                # this should be a dict of nattrname:value
                for key, value in cdict["nattributes"].items():
                    self.nattributes.add(key, value)

            del self._createdict

        self.basetype_posthook_setup()


    ## hooks called by the game engine

    def basetype_setup(self):
        """
        This sets up the default properties of an Object,
        just before the more general at_object_creation.

        You normally don't need to change this unless you change some
        fundamental things like names of permission groups.
        """
        # the default security setup fallback for a generic
        # object. Overload in child for a custom setup. Also creation
        # commands may set this (create an item and you should be its
        # controller, for example)

        self.locks.add(";".join([
            "control:perm(Immortals)",  # edit locks/permissions, delete
            "examine:perm(Builders)",   # examine properties
            "view:all()",               # look at object (visibility)
            "edit:perm(Wizards)",       # edit properties/attributes
            "delete:perm(Wizards)",     # delete object
            "get:all()",                # pick up object
            "call:true()",              # allow to call commands on this object
            "tell:perm(Wizards)",        # allow emits to this object
            "puppet:pperm(Immortals)"])) # lock down puppeting only to staff by default

    def basetype_posthook_setup(self):
        """
        Called once, after basetype_setup and at_object_creation. This
        should generally not be overloaded unless you are redefining
        how a room/exit/object works. It allows for basetype-like
        setup after the object is created. An example of this is
        EXITs, who need to know keys, aliases, locks etc to set up
        their exit-cmdsets.
        """
        pass

    def at_object_creation(self):
        """
        Called once, when this object is first created. This is
        the normal hook to overload for most object types.
        """
        pass

    def at_object_delete(self):
        """
        Called just before the database object is
        permanently delete()d from the database. If
        this method returns False, deletion is aborted.
        """
        return True

    def at_init(self):
        """
        DEPRECATED: Use __init__ instead.

        This is always called whenever this object is initiated --
        that is, whenever it its typeclass is cached from memory. This
        happens on-demand first time the object is used or activated
        in some way after being created but also after each server
        restart or reload.
        """
        pass

    def at_cmdset_get(self, **kwargs):
        """
        Called just before cmdsets on this object are requested by the
        command handler. If changes need to be done on the fly to the
        cmdset before passing them on to the cmdhandler, this is the
        place to do it. This is called also if the object currently
        have no cmdsets. **kwargs are usually not set but could be
        used e.g. to force rebuilding of a dynamically created cmdset
        or similar.
        """
        pass

    def at_pre_puppet(self, player, sessid=None):
        """
        Called just before a Player connects to this object
        to puppet it.

        player - connecting player object
        sessid - session id controlling the connection
        """
        pass

    def at_post_puppet(self):
        """
        Called just after puppeting has been completed and
        all Player<->Object links have been established.
        """
        pass

    def at_pre_unpuppet(self):
        """
        Called just before beginning to un-connect a puppeting
        from this Player.
        """
        pass

    def at_post_unpuppet(self, player, sessid=None):
        """
        Called just after the Player successfully disconnected
        from this object, severing all connections.

        player - the player object that just disconnected from
                 this object.
        sessid - session id controlling the connection
        """
        pass

    def at_server_reload(self):
        """
        This hook is called whenever the server is shutting down for
        restart/reboot. If you want to, for example, save non-persistent
        properties across a restart, this is the place to do it.
        """
        pass

    def at_server_shutdown(self):
        """
        This hook is called whenever the server is shutting down fully
        (i.e. not for a restart).
        """
        pass

    def at_access(self, result, accessing_obj, access_type, **kwargs):
        """
        This is called with the result of an access call, along with
        any kwargs used for that call. The return of this method does
        not affect the result of the lock check. It can be used e.g. to
        customize error messages in a central location or other effects
        based on the access result.
        """
        pass


    # hooks called when moving the object

    def at_before_move(self, destination):
        """
        Called just before starting to move
        this object to destination.

        destination - the object we are moving to

        If this method returns False/None, the move
        is cancelled before it is even started.
        """
        #return has_perm(self, destination, "can_move")
        return True

    def announce_move_from(self, destination):
        """
        Called if the move is to be announced. This is
        called while we are still standing in the old
        location.

        destination - the place we are going to.
        """
        if not self.location:
            return
        name = self.name
        loc_name = ""
        loc_name = self.location.name
        dest_name = destination.name
        string = "%s is leaving %s, heading for %s."
        self.location.msg_contents(string % (name, loc_name, dest_name), exclude=self)

    def announce_move_to(self, source_location):
        """
        Called after the move if the move was not quiet. At this
        point we are standing in the new location.

        source_location - the place we came from
        """

        name = self.name
        if not source_location and self.location.has_player:
            # This was created from nowhere and added to a player's
            # inventory; it's probably the result of a create command.
            string = "You now have %s in your possession." % name
            self.location.msg(string)
            return

        src_name = "nowhere"
        loc_name = self.location.name
        if source_location:
            src_name = source_location.name
        string = "%s arrives to %s from %s."
        self.location.msg_contents(string % (name, loc_name, src_name), exclude=self)

    def at_after_move(self, source_location):
        """
        Called after move has completed, regardless of quiet mode or not.
        Allows changes to the object due to the location it is now in.

        source_location - where we came from. This may be None.
        """
        pass

    def at_object_leave(self, moved_obj, target_location):
        """
        Called just before an object leaves from inside this object

        moved_obj - the object leaving
        target_location - where the object is going.
        """
        pass

    def at_object_receive(self, moved_obj, source_location):
        """
        Called after an object has been moved into this object.

        moved_obj - the object moved into this one
        source_location - where moved_object came from.
        """
        pass

    def at_before_traverse(self, traversing_object):
        """
        Called just before an object uses this object to
        traverse to another object (i.e. this object is a type of Exit)

        The target location should normally be available as self.destination.
        """
        pass

    def at_traverse(self, traversing_object, target_location):
        """
        This hook is responsible for handling the actual traversal, normally
        by calling traversing_object.move_to(target_location). It is normally
        only implemented by Exit objects. If it returns False (usually because
        move_to returned False), at_after_traverse below should not be called
        and instead at_failed_traverse should be called.
        """
        pass

    def at_after_traverse(self, traversing_object, source_location):
        """
        Called just after an object successfully used this object to
        traverse to another object (i.e. this object is a type of Exit)

        The target location should normally be available as self.destination.
        """
        pass

    def at_failed_traverse(self, traversing_object):
        """
        This is called if an object fails to traverse this object for some
        reason. It will not be called if the attribute err_traverse is defined,
        that attribute will then be echoed back instead.
        """
        pass

    def at_msg_receive(self, text=None, **kwargs):
        """
        This hook is called whenever someone
        sends a message to this object.

        Note that from_obj may be None if the sender did
        not include itself as an argument to the obj.msg()
        call - so you have to check for this. .

        Consider this a pre-processing method before
        msg is passed on to the user sesssion. If this
        method returns False, the msg will not be
        passed on.
        Input:
            msg = the message received
            from_obj = the one sending the message
        Output:
            boolean True/False
        """
        return True

    def at_msg_send(self, text=None, to_obj=None, **kwargs):
        """
        This is a hook that is called when /this/ object
        sends a message to another object with obj.msg()
        while also specifying that it is the one sending.

        Note that this method is executed on the object
        passed along with the msg() function (i.e. using
        obj.msg(msg, from_obj=caller) will then launch caller.at_msg())
        and if no object was passed, it will never be called.
        """
        pass

    # hooks called by the default cmdset.

    def return_appearance(self, pobject):
        """
        This is a convenient hook for a 'look'
        command to call.
        """
        if not pobject:
            return
        # get and identify all objects
        visible = (con for con in self.contents if con != pobject and
                                                    con.access(pobject, "view"))
        exits, users, things = [], [], []
        for con in visible:
            key = con.key
            if con.destination:
                exits.append(key)
            elif con.has_player:
                users.append("{c%s{n" % key)
            else:
                things.append(key)
        # get description, build string
        string = "{c%s{n" % self.key
        desc = self.db.desc
        if desc:
            string += "\n %s" % desc
        if exits:
            string += "\n{wExits:{n " + ", ".join(exits)
        if users or things:
            string += "\n{wYou see:{n " + ", ".join(users + things)
        return string

    def at_desc(self, looker=None):
        """
        This is called whenever someone looks
        at this object. Looker is the looking
        object.
        """
        pass

    def at_get(self, getter):
        """
        Called when this object has been picked up. Obs-
        this method cannot stop the pickup - use permissions
        for that!

        getter - the object getting this object.
        """
        pass

    def at_drop(self, dropper):
        """
        Called when this object has been dropped.

        dropper - the object which just dropped this object.
        """
        pass

    def at_say(self, speaker, message):
        """
        Called on this object if an object inside this object speaks.
        The string returned from this method is the final form
        of the speech. Obs - you don't have to add things like
        'you say: ' or similar, that is handled by the say command.

        speaker - the object speaking
        message - the words spoken.
        """
        return message


#
# Base Character object
#

class DefaultCharacter(DefaultObject):
    """
    This is just like the Object except it implements its own
    version of the at_object_creation to set up the script
    that adds the default cmdset to the object.
    """

    def basetype_setup(self):
        """
        Setup character-specific security

        You should normally not need to overload this, but if you do, make
        sure to reproduce at least the two last commands in this method (unless
        you want to fundamentally change how a Character object works).

        """
        super(DefaultCharacter, self).basetype_setup()
        self.locks.add(";".join(["get:false()",  # noone can pick up the character
                                 "call:false()"])) # no commands can be called on character from outside
        # add the default cmdset
        self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)

    def at_object_creation(self):
        """
        All this does (for now) is to add the default cmdset. Since
        the script is permanently stored to this object (the permanent
        keyword creates a script to do this), we should never need to
        do this again for as long as this object exists.
        """
        pass

    def at_after_move(self, source_location):
        "Default is to look around after a move."
        self.execute_cmd('look')

    def at_pre_puppet(self, player, sessid=None):
        """
        This recovers the character again after having been "stoved away"
        at the unpuppet
        """
        if self.db.prelogout_location:
            # try to recover
            self.location = self.db.prelogout_location
        if self.location is None:
            # make sure location is never None (home should always exist)
            self.location = self.home
        if self.location:
            # save location again to be sure
            self.db.prelogout_location = self.location
            self.location.at_object_receive(self, self.location)
        else:
            player.msg("{r%s has no location and no home is set.{n" % self, sessid=sessid)

    def at_post_puppet(self):
        """
        Called just after puppeting has completed.
        """
        self.msg("\nYou become {c%s{n.\n" % self.name)
        self.execute_cmd("look")
        if self.location:
            self.location.msg_contents("%s has entered the game." % self.name, exclude=[self])

    def at_post_unpuppet(self, player, sessid=None):
        """
        We stove away the character when the player goes ooc/logs off,
        otherwise the character object will remain in the room also after the
        player logged off ("headless", so to say).
        """
        if self.location: # have to check, in case of multiple connections closing
            self.location.msg_contents("%s has left the game." % self.name, exclude=[self])
            self.db.prelogout_location = self.location
            self.location = None

#
# Base Room object
#

class DefaultRoom(DefaultObject):
    """
    This is the base room object. It's just like any Object except its
    location is None.
    """
    def basetype_setup(self):
        """
        Simple setup, shown as an example
        (since default is None anyway)
        """

        super(DefaultRoom, self).basetype_setup()
        self.locks.add(";".join(["get:false()",
                                 "puppet:false()"])) # would be weird to puppet a room ...
        self.location = None


#
# Base Exit object
#

class DefaultExit(DefaultObject):
    """
    This is the base exit object - it connects a location to another.
    This is done by the exit assigning a "command" on itself with the
    same name as the exit object (to do this we need to remember to
    re-create the command when the object is cached since it must be
    created dynamically depending on what the exit is called). This
    command (which has a high priority) will thus allow us to traverse
    exits simply by giving the exit-object's name on its own.
    """

    # Helper classes and methods to implement the Exit. These need not
    # be overloaded unless one want to change the foundation for how
    # Exits work. See the end of the class for hook methods to overload.

    def create_exit_cmdset(self, exidbobj):
        """
        Helper function for creating an exit command set + command.

        The command of this cmdset has the same name as the Exit object
        and allows the exit to react when the player enter the exit's name,
        triggering the movement between rooms.

        Note that exitdbobj is an ObjectDB instance. This is necessary
        for handling reloads and avoid tracebacks if this is called while
        the typeclass system is rebooting.
        """
        class ExitCommand(command.Command):
            """
            This is a command that simply cause the caller
            to traverse the object it is attached to.
            """
            obj = None

            def func(self):
                "Default exit traverse if no syscommand is defined."

                if self.obj.access(self.caller, 'traverse'):
                    # we may traverse the exit.
                    self.obj.at_traverse(self.caller, self.obj.destination)
                else:
                    # exit is locked
                    if self.obj.db.err_traverse:
                        # if exit has a better error message, let's use it.
                        self.caller.msg(self.obj.db.err_traverse)
                    else:
                        # No shorthand error message. Call hook.
                        self.obj.at_failed_traverse(self.caller)

        # create an exit command. We give the properties here,
        # to always trigger metaclass preparations
        cmd = ExitCommand(key=exidbobj.db_key.strip().lower(),
                          aliases=exidbobj.aliases.all(),
                          locks=str(exidbobj.locks),
                          auto_help=False,
                          destination=exidbobj.db_destination,
                          arg_regex=r"^$",
                          is_exit=True,
                          obj=exidbobj)
        # create a cmdset
        exit_cmdset = cmdset.CmdSet(None)
        exit_cmdset.key = '_exitset'
        exit_cmdset.priority = 101
        exit_cmdset.duplicates = True
        # add command to cmdset
        exit_cmdset.add(cmd)
        return exit_cmdset

    # Command hooks
    def basetype_setup(self):
        """
        Setup exit-security

        You should normally not need to overload this - if you do make sure you
        include all the functionality in this method.
        """
        super(DefaultExit, self).basetype_setup()

        # setting default locks (overload these in at_object_creation()
        self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ...
                                 "traverse:all()", # who can pass through exit by default
                                 "get:false()"]))   # noone can pick up the exit

        # an exit should have a destination (this is replaced at creation time)
        if self.location:
            self.destination = self.location

    def at_cmdset_get(self, **kwargs):
        """
        Called when the cmdset is requested from this object, just before the
        cmdset is actually extracted. If no Exit-cmdset is cached, create
        it now.

        kwargs:
          force_init=True - force a re-build of the cmdset (for example to update aliases)
        """

        if "force_init" in kwargs or not self.cmdset.has_cmdset("_exitset", must_be_default=True):
            # we are resetting, or no exit-cmdset was set. Create one dynamically.
            self.cmdset.add_default(self.create_exit_cmdset(self), permanent=False)

    # this and other hooks are what usually can be modified safely.

    def at_object_creation(self):
        "Called once, when object is first created (after basetype_setup)."
        pass

    def at_traverse(self, traversing_object, target_location):
        """
        This implements the actual traversal. The traverse lock has already been
        checked (in the Exit command) at this point.
        """
        source_location = traversing_object.location
        if traversing_object.move_to(target_location):
            self.at_after_traverse(traversing_object, source_location)
        else:
            if self.db.err_traverse:
                # if exit has a better error message, let's use it.
                self.caller.msg(self.db.err_traverse)
            else:
                # No shorthand error message. Call hook.
                self.at_failed_traverse(traversing_object)

    def at_after_traverse(self, traversing_object, source_location):
        """
        Called after a successful traverse.
        """
        pass

    def at_failed_traverse(self, traversing_object):
        """
        This is called if an object fails to traverse this object for some
        reason. It will not be called if the attribute "err_traverse" is
        defined, that attribute will then be echoed back instead as a
        convenient shortcut.

        (See also hooks at_before_traverse and at_after_traverse).
        """
        traversing_object.msg("You cannot go there.")
