"""
This module implements a simple mobile object with
a very rudimentary AI as well as an aggressive enemy
object based on that mobile class.

"""

import random, time
from django.conf import settings

from evennia import search_object, utils, Script
from contrib.tutorial_world import objects as tut_objects
from contrib.tutorial_world import scripts as tut_scripts

BASE_CHARACTER_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS


#------------------------------------------------------------
#
# Mob - mobile object
#
# This object utilizes exits and moves about randomly from
# room to room.
#
#------------------------------------------------------------

class Mob(tut_objects.TutorialObject):
    """
    This type of mobile will roam from exit to exit at
    random intervals. Simply lock exits against the is_mob attribute
    to block them from the mob (lockstring = "traverse:not attr(is_mob)").
    """
    def at_object_creation(self):
        "This is called when the object is first created."
        self.db.tutorial_info = "This is a moving object. It moves randomly from room to room."

        self.scripts.add(tut_scripts.IrregularEvent)
        # this is a good attribute for exits to look for, to block
        # a mob from entering certain exits.
        self.db.is_mob = True
        self.db.last_location = None
        # only when True will the mob move.
        self.db.roam_mode = True

    def announce_move_from(self, destination):
        "Called just before moving"
        self.location.msg_contents("With a cold breeze, %s drifts in the direction of %s." % (self.key, destination.key))

    def announce_move_to(self, source_location):
        "Called just after arriving"
        self.location.msg_contents("With a wailing sound, %s appears from the %s." % (self.key, source_location.key))

    def update_irregular(self):
        "Called at irregular intervals. Moves the mob."
        if self.roam_mode:
            exits = [ex for ex in self.location.exits
                                    if ex.access(self, "traverse")]
            if exits:
                # Try to make it so the mob doesn't backtrack.
                new_exits = [ex for ex in exits
                                     if ex.destination != self.db.last_location]
                if new_exits:
                    exits = new_exits
                self.db.last_location = self.location
                # execute_cmd() allows the mob to respect exit and
                # exit-command locks, but may pose a problem if there is more
                # than one exit with the same name.
                # - see Enemy example for another way to move
                self.execute_cmd("%s" % exits[random.randint(0, len(exits) - 1)].key)


#------------------------------------------------------------
#
# Enemy - mobile attacking object
#
# An enemy is a mobile that is aggressive against players
# in its vicinity. An enemy will try to attack characters
# in the same location. It will also pursue enemies through
# exits if possible.
#
# An enemy needs to have a Weapon object in order to
# attack.
#
# This particular tutorial enemy is a ghostly apparition that can only
# be hurt by magical weapons. It will also not truly "die", but only
# teleport to another room. Players defeated by the apparition will
# conversely just be teleported to a holding room.
#
#------------------------------------------------------------

class AttackTimer(Script):
    """
    This script is what makes an eneny "tick".
    """
    def at_script_creation(self):
        "This sets up the script"
        self.key = "AttackTimer"
        self.desc = "Drives an Enemy's combat."
        self.interval = random.randint(2, 3) # how fast the Enemy acts
        self.start_delay = True # wait self.interval before first call
        self.persistent = True

    def at_repeat(self):
        "Called every self.interval seconds."
        if self.obj.db.inactive:
            return
        # id(self.ndb.twisted_task)
        if self.obj.db.roam_mode:
            self.obj.roam()
            #return
        elif self.obj.db.battle_mode:
            #print "attack"
            self.obj.attack()
            return
        elif self.obj.db.pursue_mode:
            #print "pursue"
            self.obj.pursue()
            #return
        else:
            #dead mode. Wait for respawn.
            if not self.obj.db.dead_at:
                self.obj.db.dead_at = time.time()
            if (time.time() - self.obj.db.dead_at) > self.obj.db.dead_timer:
                self.obj.reset()


class Enemy(Mob):
    """
    This is a ghostly enemy with health (hit points). Their chance to hit,
    damage etc is determined by the weapon they are wielding, same as
    characters.

    An enemy can be in four modes:
       roam (inherited from Mob) - where it just moves around randomly
       battle - where it stands in one place and attacks players
       pursue - where it follows a player, trying to enter combat again
       dead - passive and invisible until it is respawned

    Upon creation, the following attributes describe the enemy's actions
      desc - description
      full_health - integer number > 0
      defeat_location - unique name or #dbref to the location the player is
                        taken when defeated. If not given, will remain in room.
      defeat_text - text to show player when they are defeated (just before
                    being whisped away to defeat_location)
      defeat_text_room - text to show other players in room when a player
                         is defeated
      win_text - text to show player when defeating the enemy
      win_text_room - text to show room when a player defeates the enemy
      respawn_text - text to echo to room when the mob is reset/respawn in
                     that room.

    """
    def at_object_creation(self):
        "Called at object creation."
        super(Enemy, self).at_object_creation()

        self.db.tutorial_info = "This moving object will attack players in the same room."

        # state machine modes
        self.db.roam_mode = True
        self.db.battle_mode = False
        self.db.pursue_mode = False
        self.db.dead_mode = False
        # health (change this at creation time)
        self.db.full_health = 20
        self.db.health = 20
        self.db.dead_at = time.time()
        self.db.dead_timer = 100 # how long to stay dead
        # this is used during creation to make sure the mob doesn't move away
        self.db.inactive = True
        # store the last player to hit
        self.db.last_attacker = None
        # where to take defeated enemies
        self.db.defeat_location = "darkcell"
        self.scripts.add(AttackTimer)

    def update_irregular(self):
        "the irregular event is inherited from Mob class"
        strings = self.db.irregular_echoes
        if strings:
            self.location.msg_contents(strings[random.randint(0, len(strings) - 1)])

    def roam(self):
        "Called by Attack timer. Will move randomly as long as exits are open."

        # in this mode, the mob is healed.
        self.db.health = self.db.full_health
        players = [obj for obj in self.location.contents
                   if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
        if players:
            # we found players in the room. Attack.
            self.db.roam_mode = False
            self.db.pursue_mode = False
            self.db.battle_mode = True

        elif random.random() < 0.2:
            # no players to attack, move about randomly.
            exits = [ex.destination for ex in self.location.exits
                                                if ex.access(self, "traverse")]
            if exits:
                # Try to make it so the mob doesn't backtrack.
                new_exits = [ex for ex in exits
                                    if ex.destination != self.db.last_location]
                if new_exits:
                    exits = new_exits
                self.db.last_location = self.location
                # locks should be checked here
                self.move_to(exits[random.randint(0, len(exits) - 1)])
            else:
                # no exits - a dead end room. Respawn back to start.
                self.move_to(self.home)

    def attack(self):
        """
        This is the main mode of combat. It will try to hit players in
        the location. If players are defeated, it will whisp them off
        to the defeat location.
        """
        last_attacker = self.db.last_attacker
        players = [obj for obj in self.location.contents
                   if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
        if players:

            # find a target
            if last_attacker in players:
                # prefer to attack the player last attacking.
                target = last_attacker
            else:
                # otherwise attack a random player in location
                target = players[random.randint(0, len(players) - 1)]

            # try to use the weapon in hand
            attack_cmds = ("thrust", "pierce", "stab", "slash", "chop")
            cmd = attack_cmds[random.randint(0, len(attack_cmds) - 1)]
            self.execute_cmd("%s %s" % (cmd, target))

            # analyze result.
            if target.db.health <= 0:
                # we reduced enemy to 0 health. Whisp them off to
                # the prison room.
                tloc = search_object(self.db.defeat_location)
                tstring = self.db.defeat_text
                if not tstring:
                    tstring = "You feel your conciousness slip away ... you fall to the ground as "
                    tstring += "the misty apparition envelopes you ...\n The world goes black ...\n"
                target.msg(tstring)
                ostring = self.db.defeat_text_room
                if tloc:
                    if not ostring:
                        ostring = "\n%s envelops the fallen ... and then their body is suddenly gone!" % self.key
                        # silently move the player to defeat location
                        # (we need to call hook manually)
                    target.location = tloc[0]
                    tloc[0].at_object_receive(target, self.location)
                elif not ostring:
                    ostring = "%s falls to the ground!" % target.key
                self.location.msg_contents(ostring, exclude=[target])
                # Pursue any stragglers after the battle
                self.battle_mode = False
                self.roam_mode = False
                self.pursue_mode = True
        else:
            # no players found, this could mean they have fled.
            # Switch to pursue mode.
            self.battle_mode = False
            self.roam_mode = False
            self.pursue_mode = True

    def pursue(self):
        """
        In pursue mode, the enemy tries to find players in adjoining rooms, preferably
        those that previously attacked it.
        """
        last_attacker = self.db.last_attacker
        players = [obj for obj in self.location.contents if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
        if players:
            # we found players in the room. Maybe we caught up with some,
            # or some walked in on us before we had time to pursue them.
            # Switch to battle mode.
            self.battle_mode = True
            self.roam_mode = False
            self.pursue_mode = False
        else:
            # find all possible destinations.
            destinations = [ex.destination for ex in self.location.exits
                                                if ex.access(self, "traverse")]
            # find all players in the possible destinations. OBS-we cannot
            # just use the player's current position to move the Enemy; this
            # might have changed when the move is performed, causing the enemy
            # to teleport out of bounds.
            players = {}
            for dest in destinations:
                for obj in [o for o in dest.contents
                           if utils.inherits_from(o, BASE_CHARACTER_TYPECLASS)]:
                    players[obj] = dest
            if players:
                # we found targets. Move to intercept.
                if last_attacker in players:
                    # preferably the one that last attacked us
                    self.move_to(players[last_attacker])
                else:
                    # otherwise randomly.
                    key = players.keys()[random.randint(0, len(players) - 1)]
                    self.move_to(players[key])
            else:
                # we found no players nearby. Return to roam mode.
                self.battle_mode = False
                self.roam_mode = True
                self.pursue_mode = False

    def at_hit(self, weapon, attacker, damage):
        """
        Called when this object is hit by an enemy's weapon
        Should return True if enemy is defeated, False otherwise.

        In the case of players attacking, we handle all the events
        and information from here, so the return value is not used.
        """

        self.db.last_attacker = attacker
        if not self.db.battle_mode:
            # we were attacked, so switch to battle mode.
            self.db.roam_mode = False
            self.db.pursue_mode = False
            self.db.battle_mode = True
            #self.scripts.add(AttackTimer)

        if not weapon.db.magic:
            # In the tutorial, the enemy is a ghostly apparition, so
            # only magical weapons can harm it.
            string = self.db.weapon_ineffective_text
            if not string:
                string = "Your weapon just passes through your enemy, causing no effect!"
            attacker.msg(string)
            return
        else:
            # an actual hit
            health = float(self.db.health)
            health -= damage
            self.db.health = health
            if health <= 0:
                string = self.db.win_text
                if not string:
                    string = "After your last hit, %s folds in on itself, it seems to fade away into nothingness. " % self.key
                    string += "In a moment there is nothing left but the echoes of its screams. But you have a "
                    string += "feeling it is only temporarily weakened. "
                    string += "You fear it's only a matter of time before it materializes somewhere again."
                attacker.msg(string)
                string = self.db.win_text_room
                if not string:
                    string = "After %s's last hit, %s folds in on itself, it seems to fade away into nothingness. " % (attacker.name, self.key)
                    string += "In a moment there is nothing left but the echoes of its screams. But you have a "
                    string += "feeling it is only temporarily weakened. "
                    string += "You fear it's only a matter of time before it materializes somewhere again."
                self.location.msg_contents(string, exclude=[attacker])

                # put mob in dead mode and hide it from view.
                # AttackTimer will bring it back later.
                self.db.dead_at = time.time()
                self.db.roam_mode = False
                self.db.pursue_mode = False
                self.db.battle_mode = False
                self.db.dead_mode = True
                self.location = None
            else:
                self.location.msg_contents("%s wails, shudders and writhes." % self.key)
        return False

    def reset(self):
        """
        If the mob was 'dead', respawn it to its home position and reset
        all modes and damage."""
        if self.db.dead_mode:
            self.db.health = self.db.full_health
            self.db.roam_mode = True
            self.db.pursue_mode = False
            self.db.battle_mode = False
            self.db.dead_mode = False
            self.location = self.home
            string = self.db.respawn_text
            if not string:
                string = "%s fades into existence from out of thin air. It's looking pissed." % self.key
                self.location.msg_contents(string)
