class ActionScheduler(object):
    def __init__(self):
        self._actors = deque()

    @property
    def entities(self):  # All monsters have health
        return [actor for actor in self._actors if actor.has("health")]

    @property
    def actors(self):
        return list(self._actors)

    def register(self, actor):
        self._actors.append(actor)

    def release(self, actor):
        self._actors.remove(actor)

    def effects_tick(self, entity):
        if entity.has("effect_queue"):
            entity.effect_queue.update(gametime.normal_energy_gain)

    def _actors_tick(self, time):
        if len(self._actors) > 0:
            entity = self._actors[0]
            entity.reset_spoofed_children()
            entity.first_tick(time)  # Equipped effects.
            self.effects_tick(entity)
            entity.before_tick(time)
            self.on_tick(entity)
            entity.actor.tick()
            entity.on_tick(time)
            entity.after_tick(time)
            self._actors.rotate()

    def on_tick(self, entity):
        self.sharing_tile_effects_tick(entity)

    def sharing_tile_effects_tick(self, actor):
        if(actor.has("dungeon_level") and
           actor.has("position")):
            tile = (actor.dungeon_level.value.
                    get_tile(actor.position.value))
            for piece in tile.get_all_pieces():
                for share_effect in piece.get_children_with_tag("entity_share_tile_effect"):
                    share_effect.share_tile_effect_tick(actor, gametime.normal_energy_gain)

    def tick(self, time):
        self._actors_tick(time) class WeaponMeleeAttacker(WeaponAttacker):
    def __init__(self, weapon):
        super(WeaponMeleeAttacker, self).__init__(weapon)
        self.component_type = "melee_attacker"

    def can_hit(self, entity):
        if geometry.chess_distance(self.parent.position.value, entity.position.value) > 1:
            return False
        return True def get_dummy_actor():
    dummy_actor = Composite()
    dummy_actor.set_child(DoNothingActor())
    dummy_actor.set_child(Equipment())
    dummy_actor.set_child(DataPoint(DataTypes.ENERGY, -gametime.single_turn))
    dummy_actor.set_child(DataPoint(DataTypes.OFFENCIVE_ATTACK_CHANCE, 0.0))
    dummy_actor.set_child(DataPoint(DataTypes.COUNTER_ATTACK_CHANCE, 0.0))
    dummy_actor.set_child(DataPoint(DataTypes.DEFENCIVE_ATTACK_CHANCE, 0.0))
    dummy_actor.set_child(DataPoint(DataTypes.CRIT_CHANCE, 0.0))

    return dummy_actor class Composite(Component):
    """
    Abstract composite class of composite design pattern.

    Component classes of composite type should inherit from this class.
    Composite objects may hold other Components.

    component_type is needed if this composite will be a child.
    """

    def __init__(self, component_type=None):
        super(Composite, self).__init__()
        self.component_type = component_type
        self._spoofed_children = {}
        self._children = {}
        self._children_tag_table = {}

    def __getinitargs__(self):
        return ()

    def get_child(self, component_type):
        return self.__getattr__(component_type)

    def set_child(self, child):
        """
        Adds a child component to this component.
        If the child already has a parent an exception is thrown.
        """
        if child.tags is None:
            raise Exception("Missing tags when: Component {0} tried ta add_child "
                            "component: {1} to its children. ".format(str(self), str(child)))
        if child.component_type is None:
            raise Exception("Missing component_type when: Component {0} tried ta add_child "
                            "component: {1} to its children. ".format(str(self), str(child)))
        if not child._parent is None:
            raise Exception("Component {0} tried ta add_child "
                            "component: {1} to its children. "
                            "But it already "
                            "had parent: {2}.".format(str(self), str(child), str(child.parent)))
        if self.has(child.component_type):
            self.remove_component_of_type(child.component_type)
        self._children[child.component_type] = child
        self._add_child_to_tag_table(child)
        child.parent = self

    def add_spoof_child(self, child):
        """
        Adds a spoofed child component to this composite.
        If the child already has a parent an exception is thrown.
        """
        if child.tags is None:
            raise Exception("Component {0} tried to add_child "
                            "component: {1} to its children. "
                            "But tags "
                            "was not set.".format(str(self), str(child)))
        if child.component_type is None:
            raise Exception("Component {0} tried to add_child "
                            "component: {1} to its children. "
                            "But component_type "
                            "was not set.".format(str(self), str(child)))
        if not child._parent is None and child.parent != self:
            raise Exception("Component {0} tried to add_child "
                            "component: {1} to its children. "
                            "But it already "
                            "had parent: {2}.".format(str(self), str(child), str(child.parent)))
        if not child.component_type in self._spoofed_children:
            self._spoofed_children[child.component_type] = []
        self._spoofed_children[child.component_type].append(child)
        self._add_child_to_tag_table(child)
        child.parent = self

    def _add_child_to_tag_table(self, child):
        for tag in child.tags:
            if not tag in self._children_tag_table:
                self._children_tag_table[tag] = []
            self._children_tag_table[tag].append(child)

    def _remove_child_from_tag_table(self, child):
        for tag in child.tags:
            if tag in self._children_tag_table:
                self._children_tag_table[tag].remove(child)

    def reset_spoofed_children(self):
        """
        Removes all spoofed children.
        """
        for child_type in self._spoofed_children.values():
            for child in child_type:
                self.remove_component(child)
        self._spoofed_children = {}  # Unecessary?

    def remove_component(self, child):
        """
        Removes a child component from this component.
        """
        if (child.component_type in self._children and
                    child is self._children[child.component_type]):
                child.parent = None
                del self._children[child.component_type]
        if (child.component_type in self._spoofed_children and
                    child in self._spoofed_children[child.component_type]):
            self._spoofed_children[child.component_type].remove(child)
            child.parent = None
        self._remove_child_from_tag_table(child)
        return child

    def remove_component_of_type(self, component_type):
        """
        Removes a child component of a type of this component.
        """
        if component_type in self._children:
            return self.remove_component(self._children[component_type])
        return None

    def update(self):
        """
        Runs update on all child components.
        """
        map(lambda x: x.update(), self._children.values())

    def first_tick(self, time):
        """
        Runs first_tick on all child components.
        """
        visited_component_types = set()
        for component_type, component in self._spoofed_children.iteritems():
            visited_component_types.add(component_type)
            component[0].first_tick(time)
        components_without_spoof = [component for key, component in self._children.iteritems()
                                    if not key in visited_component_types]
        for component in components_without_spoof:
            component.first_tick(time)

    def before_tick(self, time):
        """
        Runs before_tick on all child components.
        """
        visited_component_types = set()
        for component_type, component in self._spoofed_children.iteritems():
            visited_component_types.add(component_type)
            component[0].before_tick(time)
        components_without_spoof = [component for key, component in self._children.iteritems()
                                    if not key in visited_component_types]
        for component in components_without_spoof:
            component.before_tick(time)

    def on_tick(self, time):
        """
        Runs on_tick on all child components.
        """
        visited_component_types = set()
        for component_type, component in self._spoofed_children.iteritems():
            visited_component_types.add(component_type)
            component[0].on_tick(time)
        components_without_spoof = [component for key, component in self._children.iteritems()
                                    if not key in visited_component_types]
        for component in components_without_spoof:
            component.on_tick(time)

    def after_tick(self, time):
        """
        Runs after_tick on all child components.
        """
        visited_component_types = set()
        for component_type, component in self._spoofed_children.iteritems():
            visited_component_types.add(component_type)
            component[0].after_tick(time)
        components_without_spoof = [component for key, component in self._children.iteritems()
                                    if not key in visited_component_types]
        for component in components_without_spoof:
            component.after_tick(time)

    def send_message(self, message):
        """
        Sends message to all child components.
        """
        map(lambda x: x.send_message(message), self._children.values())

    def __getattr__(self, component_type):
        if component_type == "_spoofed_children" or component_type == "_children":
            raise AttributeError("Tried to access field {0} from composite {1} "
                                 "But it doesn't exist.".format(str(component_type),
                                                                str(self)))
        try:
            if (component_type in self._spoofed_children and
                        len(self._spoofed_children[component_type]) > 0):
                return self._spoofed_children[component_type][0]
            return self._children[component_type]
        except KeyError:
            error_message = ("Tried to access component {0} from composite {1} "
                             "But it doesn't exist.".format(str(component_type), str(self)))
            if self.has("description"):
                error_message += "\n The name of the composite is: " + self.description.name
            raise AttributeError(error_message)

    def get_original_child(self, component_type):
        """
        Gets the "real" child of the composite.
        """
        return self._children[component_type]

    def get_spoofed_children_of_type(self, component_type):
        try:
            return self._spoofed_children[component_type]
        except KeyError:
            raise Exception("Tried to access components of type"
                            " {0} from composite {1} "
                            "But it doesn't exist.".format(str(component_type),
                                                           str(self)))

    def has(self, component_type):
        """
        Returns true if this component has a child of the given type.

        False otherwise.
        """
        return component_type in self._children

    def has_tag(self, tag):
        """
        Returns true if this component has the given tag.

        False otherwise.
        """
        return tag in self.tags

    # TODO: rename to descendant
    def get_children_with_tag(self, tag):
        """
        Gets the list of all children with the given tag.
        """
        if tag in self._children_tag_table:
            result = self._children_tag_table[tag]
        else:
            result = []
        composite_children = [c for c in self._children.values() if isinstance(c, Composite)]
        grand_children = []
        for c in composite_children:
            grand_children += c.get_children_with_tag(tag)
        return result + grand_children

    def get_child_or_ancestor(self, component_type):
        if component_type in self._children:
            return self._children[component_type]
        elif self._parent:
            return self.parent.get_child_or_ancestor(component_type)
        else:
            raise Exception("Tried to find relative of type: [" + component_type + "] but it doesn't exist")

    def has_child_or_ancestor(self, component_type):
        if component_type in self._children:
            return True
        elif self._parent:
            return self.parent.get_child_or_ancestor(component_type)
        else:
            return False class CounterAttackEffect(StatBonusEquipEffectWithItemStat):
    def __init__(self, effect_chance):
        super(CounterAttackEffect, self).__init__(DataTypes.COUNTER_ATTACK_CHANCE, effect_chance)
        self.effect_chance = effect_chance
        self.component_type = "counter_attack"

    def _item_stat(self):
        return ItemStat(COUNTER_ITEM_STAT_TYPE, self.effect_chance, colors.PURPLE, "Counter",
                        ItemStat.PERCENT_FORMAT, order=30, is_common_stat=False) class OffenciveAttackEffect(StatBonusEquipEffectWithItemStat):
    def __init__(self, effect_chance):
        super(OffenciveAttackEffect, self).__init__(DataTypes.OFFENCIVE_ATTACK_CHANCE, effect_chance)
        self.effect_chance = effect_chance
        self.component_type = "offencive_attack"

    def _item_stat(self):
        return ItemStat(DataTypes.OFFENCIVE_ATTACK_CHANCE, self.effect_chance, colors.LIGHT_GREEN, "Strike Step",
                        ItemStat.PERCENT_FORMAT, order=40, is_common_stat=False) class DefenciveAttackEffect(StatBonusEquipEffectWithItemStat):
    def __init__(self, effect_chance):
        super(DefenciveAttackEffect, self).__init__(DataTypes.DEFENCIVE_ATTACK_CHANCE, effect_chance)
        self.effect_chance = effect_chance
        self.component_type = "defencive_attack"

    def _item_stat(self):
        return ItemStat(DataTypes.DEFENCIVE_ATTACK_CHANCE, self.effect_chance, colors.LIGHT_GREEN, "Def Strike",
                        ItemStat.PERCENT_FORMAT, order=40, is_common_stat=False) class CritChanceBonusEffect(StatBonusEquipEffectWithItemStat):
    def __init__(self, effect_chance):
        super(CritChanceBonusEffect, self).__init__(DataTypes.CRIT_CHANCE, effect_chance)
        self.effect_chance = effect_chance
        self.component_type = "crit_chance_bonus"

    def _item_stat(self):
        return ItemStat(DataTypes.CRIT_CHANCE_WEAPON, self.effect_chance, colors.RED, "Crit %",
                        ItemStat.PERCENT_FORMAT, order=40, is_common_stat=True) class EquipmentType(Leaf):
    """
    Holds the equipment type of a equipment item.
    """

    def __init__(self, equipment_type):
        super(EquipmentType, self).__init__()
        self.component_type = "equipment_type"
        self.value = equipment_type class DataTypes:
    CLASS = "job"
    RACE = "race"

    ENERGY = "energy"
    CRIT_MULTIPLIER = "crit_multiplier"
    UNARMED_CRIT_CHANCE = "unarmed_crit_chance"
    CRIT_CHANCE = "crit_chance"
    CRIT_CHANCE_WEAPON = "crit_chance_weapon_effect"
    STRENGTH = "strength"
    ARMOR = "armor"
    ACCURACY = "accuracy"
    DAMAGE = "damage"
    STEALTH = "stealth"
    AWARENESS = "awareness"
    EVASION = "evasion"

    COUNTER_ATTACK_CHANCE = "counter_attack_chance"
    OFFENCIVE_ATTACK_CHANCE = "offencive_attack_chance"
    DEFENCIVE_ATTACK_CHANCE = "defencive_attack_chance"

    MELEE_SPEED = "melee_speed"
    SHOOT_SPEED = "shoot_speed"
    THROW_SPEED = "throw_speed"
    THROW_ITEM_SPEED = "throw_item_speed"
    CAST_SPEED = "cast_speed"

    MELEE_DAMAGE_MULTIPLIER = "melee_damage_multiplier"
    THROW_DAMAGE_MULTIPLIER = "throw_damage_multiplier"

    INTELLIGENCE = "intelligence"
    GAME_PIECE_TYPE = "game_piece_type"
    MOVEMENT_SPEED = "movement_speed"
    FACTION = "faction"

    WEIGHT = "weight"
    WEAPON_RANGE = "weapon_range"

    SIGHT_RADIUS = "sight_radius"
    SKIP_ACTION_CHANCE = "skip_action_chance"

    DENSITY = "density"
    CLOUD_TYPE = "cloud_type"
    CLONE_FUNCTION = "clone_function"

    MINIMUM_DEPTH = "minimum_depth"

    GAME_STATE = "game_state"
import unittest
import equipment
from actionscheduler import ActionScheduler
from attacker import WeaponMeleeAttacker
from common_test import get_dummy_actor
from compositecore import Composite
from equipmenteffect import CounterAttackEffect, OffenciveAttackEffect, DefenciveAttackEffect, CritChanceBonusEffect
from item_components import EquipmentType
from stats import DataTypes


TEST_VALUE = 0.4711


def get_dummy_weapon():
    weapon = Composite()
    weapon.set_child(EquipmentType(equipment.EquipmentTypes.MELEE_WEAPON))
    return weapon


class TestComposition(unittest.TestCase):
    def setUp(self):
        pass

    def weapon_with_player_stat_bonus_should_give_player_stat_bonus(self, bonus_type, expected_bonus, test_weapon, actor):
        scheduler = ActionScheduler()
        scheduler.register(actor)

        self.assertTrue(actor.equipment.try_equip(test_weapon), "Could not equip weapon")
        scheduler.tick()
        self.assertTrue(actor.has(bonus_type), "The actor has no '" + bonus_type + "' bonus stat.")
        self.assertEqual(actor.get_child(bonus_type).value, expected_bonus)

    def test_weapon_with_counter_attack_effect_gives_wielder_counter_attack_bonus(self):
        test_weapon = get_dummy_weapon()
        test_weapon.set_child(CounterAttackEffect(TEST_VALUE))
        self.weapon_with_player_stat_bonus_should_give_player_stat_bonus(DataTypes.COUNTER_ATTACK_CHANCE, TEST_VALUE,
                                                                         test_weapon, get_dummy_actor())

    def test_weapon_with_offencive_attack_effect_gives_wielder_counter_attack_bonus(self):
        test_weapon = get_dummy_weapon()
        test_weapon.set_child(OffenciveAttackEffect(TEST_VALUE))
        self.weapon_with_player_stat_bonus_should_give_player_stat_bonus(DataTypes.OFFENCIVE_ATTACK_CHANCE, TEST_VALUE,
                                                                         test_weapon, get_dummy_actor())

    def test_weapon_with_defencive_attack_effect_gives_wielder_counter_attack_bonus(self):
        test_weapon = get_dummy_weapon()

        test_weapon.set_child(DefenciveAttackEffect(TEST_VALUE))