import abc
from functools import reduce
import hearthbreaker.constants
from hearthbreaker.constants import CARD_RARITY, MINION_TYPE
from hearthbreaker.game_objects import Bindable, GameObject, GameException, Hero


def _battlecry_targetable(target):
    return target.player is target.player.game.current_player or not target.stealth


def _is_spell_targetable(target):
    return target.spell_targetable() and not target.dead


class Card(Bindable, GameObject):
    """
    Represents a card in Heathstone.  Every card is implemented as a subclass, either directly or through
    :class:`MinionCard`, :class:`SecretCard` or :class:`WeaponCard`.  If it is a direct subclass of this
    class then it is a standard spell, whereas if it is a subclass of one of :class:`MinionCard`, :class:`SecretCard`
    or :class:`WeaponCard`., then it is a minion, secret or weapon respectively.

    In order to play a card, it should be passed to :meth:`Game.play_card`.  Simply calling :meth:`use` will
    cause its effect, but not update the game state.
    """

    def __init__(self, name, mana, character_class, rarity, collectible, target_func=None,
                 filter_func=_is_spell_targetable, overload=0, ref_name=None, effects=None, buffs=None):
        """
            Creates a new :class:`Card`.

            ``target_func`` and ``filter_func`` have very specific behavior.  In use, the target function is called with
             the filter function as a parameter to generate the list of possible targets for the card, then ask the
             agent to choose the appropriate target.  The selected target will be available as the :attr:`target`
             attribute of :class:`Card`. As such, it is critical to call :meth:`use` in any overriding implementations.

         
   :param string name: The name of the card in English
            :param int mana: The base amount of mana this card costs
            :param int character_class: A constant from :class:`hearthbreaker.constants.CHARACTER_CLASS` denoting
                                     
   which character this card belongs to or
                                        :const:`hearthbreaker.constants.CHARACTER_CLASS.ALL` if neutral
            :param int rarity: A constant from :class:`hearthbreaker.constants.CARD_RARITY` denoting the rarity of the
                               card.
            :param function target_func: A function which takes a game, and returns a list of targets.  If None, then
                                         the card is assumed not to require a target.  If `target_func` returns
                                         an empty list, then the card cannot be played.  If it returns None, then the
                                         card is played, but with no target (i.e. a battlecry which has no valid target
                                         will not stop the minion from being played).

                                         See :mod:`hearthbreaker.targeting` for more details.
            :param function filter_func: A boolean function which can be used to filter the list of targets. An example
                                         for :class:`hearthbreaker.cards.spells.priest.ShadowMadness` might be a
                                         function which returns true if the target's attack is less than 3.
            :param int overload: The amount of overload on the card
            :param effects:  The effects that will be triggered for this card
            :type effects: [:class:`hearthbreaker.tags.base.Effect`]
            :param buffs:  The buffs that will be applied directly to this card
            :type buffs: [:class:`hearthbreaker.tags.base.Buff`]
        """
        Bindable.__init__(self)
        GameObject.__init__(self, effects=effects, buffs=buffs)
        self.name = name
        if ref_name:
            self.ref_name = ref_name
        else:
            self.ref_name = name
        self.mana = mana
        self.character_class = character_class
        self.rarity = rarity
        self.cancel = False
        self.targetable = target_func is not None
        if self.targetable:
            self.targets = []
            self.target = None
            self.get_targets = target_func
            self.filter_func = filter_func
        self.overload = overload
        self.drawn = True
        self.current_target = None
        self.collectible = collectible

    def can_choose(self, player):
        """
        Verifies if this card can be chosen from a list of options (i.e. in Tracking)

        :return: True if the card can be chosen, false otherwise.
        :rtype: bool
        """
        return True

    def can_use(self, player, game):
        """
        Verifies if the card can be used with the game state as it is.

        Checks that the player has enough mana to play the card, and that the card has a valid
        target if it requires one.

        :return: True if the card can be played, false otherwise.
        :rtype: bool
        """
        if game.game_ended:
            return False
        return player.mana >= self.mana_cost()

    def mana_cost(self):
        """
        Calculates the mana cost for this card.

        This cost is the base cost for the card, modified by any tags from the card itself, or
        from other cards (such as :class:`hearthbreaker.cards.minions.neutral.VentureCoMercenary`)


        :return: representing the actual mana cost of this card.
        :rtype: int
        """
        from hearthbreaker.tags.status import ManaChange
        # Mana appears to be calculated in reverse order from other stats (auras first, then buffs)

        mana = reduce(lambda a, b: b.update(self, a), [aura.status
                                                       for p in self.player.game.players
                                                       for aura in p.object_auras
                                                       if aura.match(self) and isinstance(aura.status, ManaChange)],
                      self.mana)
        mana = reduce(lambda a, b: b.update(self, a), [buff.status for buff in self.buffs
                                                       if isinstance(buff.status, ManaChange) and
                                                       (not buff.condition or buff.condition.evaluate(self, self))],
                      mana)

        return mana

    def use(self, player, game):
        """
        Use the card.

        This method will cause the card's effect, but will not update the game state or trigger any events.
        To play a card correctly, use :meth:`Game.play_card`.

        Implementations of new cards should override this method, but be sure to call ``super().use(player, game)``

        :param hearthbreaker.game_objects.Player player: The player who is using the card.
        :param hearthbreaker.game_objects.Game game: The game this card is being used in.
        """
        pass

    @staticmethod
    def is_card():
        return True

    def __to_json__(self):
        r_val = super().__to_json__()
        r_val['name'] = self.name
        return r_val

    def __str__(self):  # pragma: no cover
        """
        Outputs a description of the card for debugging purposes.
        """
        return self.name + " (" + str(self.mana) + " mana)"

    def replace(self, new_card):
        index = self.player.hand.index(self)
        self.unattach()
        new_card.attach(new_card, self.player)
        self.player.hand[index] = new_card


class MinionCard(Card, metaclass=abc.ABCMeta):
    """
    Describes a :class:`Card` for summoning a minion.  This is distinct from the :class:`Minion` that represents the
    minion after it has been played.  This class represents the card aspects (mana cost, character class and rarity)
    as well as being responsible for creating the minion that will be added to the table.

    :see: :class:`Card`
    :see: :meth:`create_minion`
    """
    def __init__(self, name, mana, character_class, rarity, collectible=True,
                 minion_type=hearthbreaker.constants.MINION_TYPE.NONE, ref_name=None, battlecry=None, cho