from functools import partial

from core.combat import getHitEnemies
from core.item.common import createArrow, transformBow
from core.talent import applyEffectSpell, concludeSkillUse, createSkill, usableSkillCost
from core.tuning.skill import SkillTuning

from siege import Locale, game, Talent
from siege.graphic import Substitution
from siege.util import seconds, Vector
from siege.world import World
from siege.world.realm import Layer


def getTalent():
    talent = Talent("arms", "arms/icon.png", [1000] * 20)
    talent.skills.append(AttackBoost.create())
    talent.skills.append(DefenseBoost.create())
    talent.skills.append(AgilityBoost.create())
    talent.skills.append(SpeedBoost.create())
    talent.skills.append(Stun.create())
    talent.skills.append(TripleStrike.create())
    # talent.skills.append(Knockup.create())
    talent.skills.append(RapidShot.create())
    talent.skills.append(RegenBoost.create())
    talent.skills.append(Shockwave.create())
    talent.skills.append(SpectralShot.create())
    game.events['create_talent'].invoke(talent)
    return talent


class AttackBoost(object):
    @staticmethod
    def onActivate(level, player):
        player.entity.stats.ATK.adjust(int(SkillTuning.ATTACK_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def onDeactivate(level, player):
        player.entity.stats.ATK.adjust(int(-SkillTuning.ATTACK_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def description(level):
        return Locale.getEscaped("AttackBoostDesc")

    @staticmethod
    def create():
        return createSkill(AttackBoost, SkillTuning.ATTACK_BOOST)


class DefenseBoost(object):
    @staticmethod
    def onActivate(level, player):
        player.entity.stats.DEF.adjust(int(SkillTuning.DEFENSE_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def onDeactivate(level, player):
        player.entity.stats.DEF.adjust(int(-SkillTuning.DEFENSE_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def description(level):
        return Locale.getEscaped("DefenseBoostDesc")

    @staticmethod
    def create():
        return createSkill(DefenseBoost, SkillTuning.DEFENSE_BOOST)


class AgilityBoost(object):
    @staticmethod
    def onActivate(level, player):
        player.entity.stats.AGI.adjust(int(SkillTuning.AGILITY_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def onDeactivate(level, player):
        player.entity.stats.AGI.adjust(int(-SkillTuning.AGILITY_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def description(level):
        return Locale.getEscaped("AgilityBoostDesc")

    @staticmethod
    def create():
        return createSkill(AgilityBoost, SkillTuning.AGILITY_BOOST)


class SpeedBoost(object):
    @staticmethod
    def onActivate(level, player):
        player.entity.stats.AGI.adjust(int(SkillTuning.SPEED_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def onDeactivate(level, player):
        player.entity.stats.AGI.adjust(int(-SkillTuning.SPEED_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def description(level):
        return Locale.getEscaped("SpeedBoostDesc")

    @staticmethod
    def create():
        return createSkill(SpeedBoost, SkillTuning.SPEED_BOOST)


class Stun(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        player.entity.playerState.canMove.set(False)
        skillEntity = game.entity.create(game.content.get("stun"))
        center = player.entity.physics.getCenter()
        player.entity.render.faceDirection(position, player.entity.loopWidth)
        skillEntity.render.setOriginToCenter()
        skillEntity.render.flipX(player.entity.render.getFlipX())
        skillEntity.setInitialPosition(Vector(center.x + (-30 if skillEntity.getRender().getFlipX() else 30), center.y))
        projectile = skillEntity.projectile
        projectile.setOwner(player.entity)
        projectile.lifetime.reset(seconds(2))
        skillEntity.physics.onTimeStep.listen(partial(Stun.handleTimeStep, skillEntity, projectile, level, []))
        World.get().move(skillEntity, player.entity.realm, Layer.Active)
        concludeSkillUse(player, player.entity.talents.arcane, level, tool, slot, SkillTuning.STUN)

    @staticmethod
    def handleTimeStep(entity, projectile, level, stunnedEnemies, physics, timestepTime):
        enemies = getHitEnemies(entity, projectile.owner.combat.team)
        for enemy in enemies:
            if enemy not in stunnedEnemies:
                enemy.effects.add('Stun', level, SkillTuning.STUN.DURATIONS[level])
                stunnedEnemies.append(enemy)
        if not entity.animation.isPlayingGroup():
            projectile.owner.playerState.canMove.set(True)
            entity.destroy()
            return True

    @staticmethod
    def isAvailable(player, level, index, slot, tool):
        return usableSkillCost(player, level, SkillTuning.STUN)

    @staticmethod
    def description(level):
        return Locale.getEscaped("StunDesc")

    @staticmethod
    def create():
        return createSkill(Stun, SkillTuning.STUN)


class TripleStrike(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        player.entity.playerState.canMove.set(False)
        skillEntity = game.entity.create(game.content.get("triple_strike"))
        center = player.entity.physics.getCenter()
        player.entity.render.faceDirection(position, player.entity.loopWidth)
        skillEntity.render.setOriginToCenter()
        skillEntity.render.flipX(player.entity.render.getFlipX())
        skillEntity.setInitialPosition(Vector(center.x + (-30 if skillEntity.getRender().getFlipX() else 30), center.y - 8))
        projectile = skillEntity.projectile
        projectile.setOwner(player.entity)
        projectile.lifetime.reset(seconds(10))
        projectile.attackData.power = SkillTuning.TRIPLE_STRIKE.POWERS[level - 1]
        skillEntity.physics.onTimeStep.listen(partial(TripleStrike.handleTimeStep, skillEntity, projectile, [0]))
        World.get().move(skillEntity, player.entity.realm, Layer.Active)
        concludeSkillUse(player, player.entity.talents.arcane, level, tool, slot, SkillTuning.TRIPLE_STRIKE)

    @staticmethod
    def handleTimeStep(entity, projectile, attackFrame, physics, timestepTime):
        animation = entity.animation
        if animation.isPlayingGroup():
            frame = animation.getFrame()
            if frame != attackFrame[0] and (frame == 3 or frame == 6):
                projectile.owner.combat.finishAttack(projectile.attackId)
                projectile.attackId = projectile.owner.combat.startAttack()
                attackFrame[0] = frame
            if frame == 8:
                projectile.owner.playerState.canMove.set(True)
            projectile.damageTouchingEnemies(physics, timestepTime)
        else:
            entity.destroy()

    @staticmethod
    def isAvailable(player, level, index, slot, tool):
        return usableSkillCost(player, level, SkillTuning.TRIPLE_STRIKE)

    @staticmethod
    def description(level):
        return Locale.getEscaped("TripleStrikeDesc")

    @staticmethod
    def create():
        return createSkill(TripleStrike, SkillTuning.TRIPLE_STRIKE)


class RapidShot(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        offset = Vector(0, player.entity.physics.getBody().height / 2.0)
        applyEffectSpell(player, player.entity.talents.arms, level, tool, slot, player.entity, 'rapid_shot', SkillTuning.RAPID_SHOT.NAME, SkillTuning.RAPID_SHOT, offset)

    @staticmethod
    def isAvailable(player, level, index, slot, tool):
        return usableSkillCost(player, level, SkillTuning.RAPID_SHOT)

    @staticmethod
    def description(level):
        return Locale.getEscaped("RapidShotDesc")

    @staticmethod
    def create():
        return createSkill(RapidShot, SkillTuning.RAPID_SHOT)


class RegenBoost(object):
    @staticmethod
    def onActivate(level, player):
        player.entity.stats.HPRegen.adjust(int(SkillTuning.REGEN_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def onDeactivate(level, player):
        player.entity.stats.HPRegen.adjust(int(-SkillTuning.REGEN_BOOST.AMOUNT_PER_LEVEL * level))

    @staticmethod
    def description(level):
        return Locale.getEscaped("RegenBoostDesc")

    @staticmethod
    def create():
        return createSkill(RegenBoost, SkillTuning.REGEN_BOOST)


class Shockwave(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        game.audio.play("mods/core/audio/sfx/slash_shing.ogg")
        crescent = game.entity.create(game.content.get("shockwave"))
        crescent.projectile.attackData.power = SkillTuning.SHOCKWAVE.POWERS[level - 1]
        isFlipped = player.entity.render.getFlipX()
        center = player.entity.physics.getCenter()
        crescent.render.setOriginToCenter()
        crescent.setInitialPosition(center)
        crescent.render.flipX(isFlipped)
        crescent.projectile.setOwner(player.entity)
        crescent.projectile.lifetime.reset(seconds(10))
        direction = -1 if isFlipped else 1
        speed = Vector(6 * direction, 0)
        crescent.physics.setVelocity(speed)
        crescent.physics.onTimeStep.listen(crescent.projectile.disappearOnHit)
        World.get().move(crescent, player.entity.realm, Layer.Active)
        concludeSkillUse(player, player.entity.talents.arms, level, tool, slot, SkillTuning.SHOCKWAVE)

    @staticmethod
    def isAvailable(player, level, index, slot, tool):
        return usableSkillCost(player, level, SkillTuning.SHOCKWAVE)

    @staticmethod
    def description(level):
        return Locale.getEscaped("ShockwaveDesc")

    @staticmethod
    def create():
        return createSkill(Shockwave, SkillTuning.SHOCKWAVE)


class SpectralShot(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        render = player.entity.render
        render.changeSprite(Substitution("bow", "mods/core/talent/arms/spectral_shot_bow.png"))
        render.changeSprite(Substitution("arrow", "mods/core/talent/arms/spectral_shot_arrow.png", Vector(10, 4)))
        delay = 500
        animation = player.entity.animation
        animation.play("bow_front", "arm_front")
        animation.play("bow_back", "arm_back")
        animation.scale(delay, "arm_front")
        animation.scale(delay, "arm_back")
        player.entity.playerState.canMove.set(False)
        physics = player.entity.physics
        center = physics.getCenter()
        position = game.input.getClientInput(player).mousePosition
        flipX, angle = transformBow(player, render, center, position)

        def transformHandler(physics, timestepTime):
            transformBow(player, render, center, position)
        physics.onTimeStep.listen(transformHandler)
        animation.onFinished.listen(partial(SpectralShot.onFinishedAnimation, player, physics, level, flipX, angle, transformHandler))
        player.entity.gear.resetGraphics()
        concludeSkillUse(player, player.entity.talents.arms, level, tool, slot, SkillTuning.SPECTRAL_SHOT)

    @staticmethod
    def onFinishedAnimation(player, physics, level, flipX, angle, transformer, name, group):
        if 'bow' not in name:
            return False
        animation = player.entity.animation
        animation.play("idle_arm_front", "arm_front")
        animation.play("idle_arm_back", "arm_back")
        physics.onTimeStep.remove(transformer)
        center = physics.getCenter()
        arrowContent = game.content.get('spectral_shot_arrow')
        if flipX:
            angle -= 180
        arrow = createArrow(player, SkillTuning.SPECTRAL_SHOT.POWERS[level - 1], arrowContent, center, angle, Vector(10, 10))
        arrow.projectile.rotateToDirection(arrow.physics, 0)
        arrow.physics.onTimeStep.clear()
        arrow.physics.onTimeStep.listen(partial(SpectralShot.onHit, arrow.projectile))
        player.entity.playerState.canMove.set(True)
        return True

    @staticmethod
    def onHit(projectile, physics, timestepTime):
        if projectile.damageTouchingEnemies(physics, timestepTime):
            projectile.getParent().destroy()

    @staticmethod
    def isAvailable(player, level, index, slot, tool):
        return usableSkillCost(player, level, SkillTuning.SPECTRAL_SHOT)

    @staticmethod
    def description(level):
        return Locale.getEscaped("SpectralShotDesc")

    @staticmethod
    def create():
        return createSkill(SpectralShot, SkillTuning.SPECTRAL_SHOT)
