from functools import partial
import math

from core.combat import DamageType
from core.item.common import destroyHitFragileTiles
from core.talent import applyEffectSpell, castSpell, castSpellClient, concludeSkillUse, createSkill, getTuningData, usableSkillCost
from core.tuning.skill import SkillTuning

from siege import Locale, game, Talent
from siege.network import NetworkManager
from siege.util import seconds, Sizes, TileVector, Timer, Vector
from siege.world import World, toRadians
from siege.world.realm import Layer


def getTalent():
    talent = Talent("arcane", "arcane/icon.png", [1000] * 20)
    talent.skills.append(Fire.create())
    talent.skills.append(Blizzard.create())
    talent.skills.append(Aero.create())
    talent.skills.append(Stone.create())
    talent.skills.append(Enfire.create())
    talent.skills.append(Enblizzard.create())
    talent.skills.append(Enaero.create())
    talent.skills.append(Enstone.create())
    talent.skills.append(Firestorm.create())
    talent.skills.append(Snowstorm.create())
    talent.skills.append(Windstorm.create())
    talent.skills.append(Sandstorm.create())
    talent.skills.append(Flare.create())
    talent.skills.append(Freeze.create())
    talent.skills.append(Tornado.create())
    talent.skills.append(Upheaval.create())
    game.events['create_talent'].invoke(talent)
    return talent


def projectileSpell(player, level, tool, slot, contentName, skillTuning):
    spell = game.entity.create(game.content.get(contentName))
    position = player.entity.physics.getCenter()
    position.x += -8 if player.entity.render.getFlipX() else 8
    position.y -= 8
    targetPosition = game.input.getClientInput(player).mousePosition
    player.entity.render.faceDirection(targetPosition, player.entity.loopWidth)
    spell.render.setOriginToCenter()
    spell.setInitialPosition(position)
    projectile = spell.projectile
    projectile.setOwner(player.entity)
    projectile.lifetime.reset(seconds(10))
    projectile.attackData.power = getTuningData(skillTuning, level, 'POWER')
    realm = player.entity.realm
    angle = position.getAngle(targetPosition, realm.size.width, player.entity.loopWidth)
    spell.render.setRotation(angle)
    angle = toRadians(angle)
    moveSpeed = 5.5
    speed = Vector(math.cos(angle) * moveSpeed, math.sin(angle) * moveSpeed)
    spell.physics.setVelocity(speed)
    spell.physics.onTimeStep.listen(partial(projectileTimeStep, spell, projectile))
    World.get().move(spell, realm, Layer.Active)
    concludeSkillUse(player, player.entity.talents.arcane, level, tool, slot, skillTuning)


def projectileTimeStep(entity, projectile, physics, timestepTime):
    projectile.disappearOnHit(physics, timestepTime)
    destroyHitFragileTiles(entity)


class Fire(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(projectileSpell, player, level, tool, slot, "fire", SkillTuning.FIRE)
        castSpell(player, level, tool, slot, SkillTuning.FIRE, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.FIRE)

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

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

    @staticmethod
    def create():
        return createSkill(Fire, SkillTuning.FIRE)


class Blizzard(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(projectileSpell, player, level, tool, slot, "blizzard", SkillTuning.BLIZZARD)
        castSpell(player, level, tool, slot, SkillTuning.BLIZZARD, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.BLIZZARD)

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

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

    @staticmethod
    def create():
        return createSkill(Blizzard, SkillTuning.BLIZZARD)


class Aero(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(projectileSpell, player, level, tool, slot, "aero", SkillTuning.AERO)
        castSpell(player, level, tool, slot, SkillTuning.AERO, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.AERO)

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

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

    @staticmethod
    def create():
        return createSkill(Aero, SkillTuning.AERO)


class Stone(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(projectileSpell, player, level, tool, slot, "stone_spell", SkillTuning.STONE)
        castSpell(player, level, tool, slot, SkillTuning.STONE, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.STONE)

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

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

    @staticmethod
    def create():
        return createSkill(Stone, SkillTuning.STONE)


def handleDealDamageEnchant(damageType, player, results, attacker, target, data):
    if data.damageType is DamageType.Physical:
        data.damageType = damageType


class Enfire(object):
    ICON = SkillTuning.ENFIRE.ICON

    def __init__(self, owner, level, duration):
        self.level = level
        owner.event["deal_damage_start"].listen(partial(handleDealDamageEnchant, DamageType.Fire))
        for effect in [SkillTuning.ENBLIZZARD.NAME, SkillTuning.ENAERO.NAME, SkillTuning.ENSTONE.NAME]:
            owner.effects.remove(effect)

    def onRemove(self, owner):
        owner.event["deal_damage_start"].remove(partial(handleDealDamageEnchant, DamageType.Fire))

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(applyEffectSpell, player, player.entity.talents.arcane, level, tool, slot, player.entity, 'enfire', SkillTuning.ENFIRE.NAME, SkillTuning.ENFIRE, Vector(-1, -10))
        castSpell(player, level, tool, slot, SkillTuning.ENFIRE, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.ENFIRE)

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

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

    @staticmethod
    def create():
        game.effects.register(SkillTuning.ENFIRE.NAME, Enfire)
        return createSkill(Enfire, SkillTuning.ENFIRE)


class Enblizzard(object):
    ICON = SkillTuning.ENBLIZZARD.ICON

    def __init__(self, owner, level, duration):
        self.level = level
        owner.event["deal_damage_start"].listen(partial(handleDealDamageEnchant, DamageType.Water))
        for effect in [SkillTuning.ENFIRE.NAME, SkillTuning.ENAERO.NAME, SkillTuning.ENSTONE.NAME]:
            owner.effects.remove(effect)

    def onRemove(self, owner):
        owner.event["deal_damage_start"].remove(partial(handleDealDamageEnchant, DamageType.Water))

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(applyEffectSpell, player, player.entity.talents.arcane, level, tool, slot, player.entity, 'enblizzard', SkillTuning.ENBLIZZARD.NAME, SkillTuning.ENBLIZZARD, Vector(-1, -10))
        castSpell(player, level, tool, slot, SkillTuning.ENBLIZZARD, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.ENBLIZZARD)

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

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

    @staticmethod
    def create():
        game.effects.register(SkillTuning.ENBLIZZARD.NAME, Enblizzard)
        return createSkill(Enblizzard, SkillTuning.ENBLIZZARD)


class Enaero(object):
    ICON = SkillTuning.ENAERO.ICON

    def __init__(self, owner, level, duration):
        self.level = level
        owner.event["deal_damage_start"].listen(partial(handleDealDamageEnchant, DamageType.Wind))
        for effect in [SkillTuning.ENFIRE.NAME, SkillTuning.ENBLIZZARD.NAME, SkillTuning.ENSTONE.NAME]:
            owner.effects.remove(effect)

    def onRemove(self, owner):
        owner.event["deal_damage_start"].remove(partial(handleDealDamageEnchant, DamageType.Wind))

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(applyEffectSpell, player, player.entity.talents.arcane, level, tool, slot, player.entity, 'enaero', SkillTuning.ENAERO.NAME, SkillTuning.ENAERO, Vector(-1, -10))
        castSpell(player, level, tool, slot, SkillTuning.ENAERO, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.ENAERO)

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

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

    @staticmethod
    def create():
        game.effects.register(SkillTuning.ENAERO.NAME, Enaero)
        return createSkill(Enaero, SkillTuning.ENAERO)


class Enstone(object):
    ICON = SkillTuning.ENSTONE.ICON

    def __init__(self, owner, level, duration):
        self.level = level
        owner.event["deal_damage_start"].listen(partial(handleDealDamageEnchant, DamageType.Earth))
        for effect in [SkillTuning.ENFIRE.NAME, SkillTuning.ENBLIZZARD.NAME, SkillTuning.ENAERO.NAME]:
            owner.effects.remove(effect)

    def onRemove(self, owner):
        owner.event["deal_damage_start"].remove(partial(handleDealDamageEnchant, DamageType.Earth))

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(applyEffectSpell, player, player.entity.talents.arcane, level, tool, slot, player.entity, 'enstone', SkillTuning.ENSTONE.NAME, SkillTuning.ENSTONE, Vector(-1, -10))
        castSpell(player, level, tool, slot, SkillTuning.ENSTONE, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.ENSTONE)

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

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

    @staticmethod
    def create():
        game.effects.register(SkillTuning.ENSTONE.NAME, Enstone)
        return createSkill(Enstone, SkillTuning.ENSTONE)


def applyStormEffect(player, level, tool, slot, effect, skillTuning):
    player.entity.effects.add(effect, level, skillTuning.DURATION)
    concludeSkillUse(player, player.entity.talents.arcane, level, tool, slot, skillTuning)


def createStormEntity(entity, level, contentName, skillTuning):
    # Create projectile with center on player center and it moves with player
    # Every second it changes its attackId
    storm = game.entity.create(game.content.get(contentName))
    offset = Vector(0, -8)
    storm.projectile.setOwner(entity, center=True, offset=offset)
    storm.projectile.lifetime.reset(getTuningData(skillTuning, level, 'DURATION'))
    storm.projectile.attackData.power = getTuningData(skillTuning, level, 'POWER')
    triggerTime = getTuningData(skillTuning, level, 'TRIGGER_TIME')
    triggerTimer = Timer(triggerTime)
    storm.physics.onTimeStep.listen(partial(stormUpdate, storm.projectile, triggerTime, triggerTimer, offset))
    World.get().move(storm, entity.realm, Layer.Active)
    return storm


def stormUpdate(projectile, duration, timer, offset, physics, frameTime):
    projectile.centerOnOwner(offset)
    timer.update(frameTime)
    if timer.expired():
        timer.reset(duration)
        owner = projectile.owner
        owner.combat.finishAttack(projectile.attackId)
        projectile.attackId = owner.combat.startAttack()
    projectile.damageTouchingEnemies(physics, frameTime)


class Firestorm(object):
    ICON = SkillTuning.FIRESTORM.ICON

    def __init__(self, owner, level, duration):
        if NetworkManager.isHost():
            self.storm = createStormEntity(owner, level, 'firestorm', SkillTuning.FIRESTORM)
            for effect in [SkillTuning.SNOWSTORM.NAME, SkillTuning.WINDSTORM.NAME, SkillTuning.SANDSTORM.NAME]:
                owner.effects.remove(effect)

    def onRemove(self, owner):
        if NetworkManager.isHost():
            self.storm.destroy()

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(applyStormEffect, player, level, tool, slot, SkillTuning.FIRESTORM.NAME, SkillTuning.FIRESTORM)
        castSpell(player, level, tool, slot, SkillTuning.FIRESTORM, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.FIRESTORM)

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

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

    @staticmethod
    def create():
        game.effects.register(SkillTuning.FIRESTORM.NAME, Firestorm)
        return createSkill(Firestorm, SkillTuning.FIRESTORM)


class Snowstorm(object):
    ICON = SkillTuning.SNOWSTORM.ICON

    def __init__(self, owner, level, duration):
        if NetworkManager.isHost():
            self.storm = createStormEntity(owner, level, 'snowstorm', SkillTuning.SNOWSTORM)
            for effect in [SkillTuning.FIRESTORM.NAME, SkillTuning.WINDSTORM.NAME, SkillTuning.SANDSTORM.NAME]:
                owner.effects.remove(effect)

    def onRemove(self, owner):
        if NetworkManager.isHost():
            self.storm.destroy()

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(applyStormEffect, player, level, tool, slot, SkillTuning.SNOWSTORM.NAME, SkillTuning.SNOWSTORM)
        castSpell(player, level, tool, slot, SkillTuning.SNOWSTORM, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.SNOWSTORM)

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

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

    @staticmethod
    def create():
        game.effects.register(SkillTuning.SNOWSTORM.NAME, Snowstorm)
        return createSkill(Snowstorm, SkillTuning.SNOWSTORM)


class Windstorm(object):
    ICON = SkillTuning.WINDSTORM.ICON

    def __init__(self, owner, level, duration):
        if NetworkManager.isHost():
            self.storm = createStormEntity(owner, level, 'windstorm', SkillTuning.WINDSTORM)
            for effect in [SkillTuning.FIRESTORM.NAME, SkillTuning.SNOWSTORM.NAME, SkillTuning.SANDSTORM.NAME]:
                owner.effects.remove(effect)

    def onRemove(self, owner):
        if NetworkManager.isHost():
            self.storm.destroy()

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(applyStormEffect, player, level, tool, slot, SkillTuning.WINDSTORM.NAME, SkillTuning.WINDSTORM)
        castSpell(player, level, tool, slot, SkillTuning.WINDSTORM, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.WINDSTORM)

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

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

    @staticmethod
    def create():
        game.effects.register(SkillTuning.WINDSTORM.NAME, Windstorm)
        return createSkill(Windstorm, SkillTuning.WINDSTORM)


class Sandstorm(object):
    ICON = SkillTuning.SANDSTORM.ICON

    def __init__(self, owner, level, duration):
        if NetworkManager.isHost():
            self.storm = createStormEntity(owner, level, 'sandstorm', SkillTuning.SANDSTORM)
            for effect in [SkillTuning.FIRESTORM.NAME, SkillTuning.SNOWSTORM.NAME, SkillTuning.WINDSTORM.NAME]:
                owner.effects.remove(effect)

    def onRemove(self, owner):
        if NetworkManager.isHost():
            self.storm.destroy()

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(applyStormEffect, player, level, tool, slot, SkillTuning.SANDSTORM.NAME, SkillTuning.SANDSTORM)
        castSpell(player, level, tool, slot, SkillTuning.SANDSTORM, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.SANDSTORM)

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

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

    @staticmethod
    def create():
        game.effects.register(SkillTuning.SANDSTORM.NAME, Sandstorm)
        return createSkill(Sandstorm, SkillTuning.SANDSTORM)


def getPillarPosition(player, position, skillTuning, ground):
    return ground.getTileInDirection(position, TileVector(0, 1), solidOnly=True)


def isPillarSpellUsable(player, level, position, skillTuning):
    spellRange = getTuningData(skillTuning, level, 'RANGE')
    if player.entity.getPosition().getDistance(position, player.entity.loopWidth) > spellRange:
        return False
    ground = player.entity.realm.layers[Layer.Ground]
    tilePos = getPillarPosition(player, position, skillTuning, ground)
    worldPos = tilePos.getWorldPosition()
    distance = worldPos.y - position.y
    if distance >= -Sizes.TILE:
        tilePos.move(0, -1)
        return not ground.isSolid(tilePos)
    return distance >= 0 and distance <= 4 * Sizes.TILE


def destroyPillar(entity, name, group):
    if name == "burst":
        entity.destroy()


def pillarSpell(player, level, tool, slot, position, contentName, skillTuning):
    entity = game.entity.create(game.content.get(contentName))
    entity.animation.queue('burst')
    # Determine position the spell is placed at
    tilePos = getPillarPosition(player, position, skillTuning, player.entity.realm.layers[Layer.Ground])
    startPosition = tilePos.getWorldPosition()
    startPosition.x = position.x
    entity.setInitialPosition(startPosition)
    # Setup spell projectile data
    projectile = entity.projectile
    projectile.setOwner(player.entity)
    entity.animation.onFinished.listen(partial(destroyPillar, entity))
    projectile.attackData.power = getTuningData(skillTuning, level, 'POWER')
    entity.physics.onTimeStep.listen(projectile.damageTouchingEnemies)
    World.get().move(entity, player.entity.realm, Layer.Active)
    concludeSkillUse(player, player.entity.talents.arcane, level, tool, slot, skillTuning)


class Flare(object):
    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.FLARE)

    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(pillarSpell, player, level, tool, slot, position, 'flare', SkillTuning.FLARE)
        castSpell(player, level, tool, slot, SkillTuning.FLARE, callback)

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

    @staticmethod
    def isUsable(player, level, position, index, slot, tool):
        return isPillarSpellUsable(player, level, position, SkillTuning.FLARE)

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

    @staticmethod
    def create():
        return createSkill(Flare, SkillTuning.FLARE)


class Freeze(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(pillarSpell, player, level, tool, slot, position, 'freeze', SkillTuning.FREEZE)
        castSpell(player, level, tool, slot, SkillTuning.FREEZE, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.FREEZE)

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

    @staticmethod
    def isUsable(player, level, position, index, slot, tool):
        return isPillarSpellUsable(player, level, position, SkillTuning.FREEZE)

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

    @staticmethod
    def create():
        return createSkill(Freeze, SkillTuning.FREEZE)


class Tornado(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(pillarSpell, player, level, tool, slot, position, 'tornado', SkillTuning.TORNADO)
        castSpell(player, level, tool, slot, SkillTuning.TORNADO, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.TORNADO)

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

    @staticmethod
    def isUsable(player, level, position, index, slot, tool):
        return isPillarSpellUsable(player, level, position, SkillTuning.TORNADO)

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

    @staticmethod
    def create():
        return createSkill(Tornado, SkillTuning.TORNADO)


class Upheaval(object):
    @staticmethod
    def onUse(player, level, position, index, slot, tool):
        callback = partial(pillarSpell, player, level, tool, slot, position, 'upheaval', SkillTuning.UPHEAVAL)
        castSpell(player, level, tool, slot, SkillTuning.UPHEAVAL, callback)

    @staticmethod
    def onClientUse(player, level, position, index, slot, tool):
        castSpellClient(player, level, tool, slot, SkillTuning.UPHEAVAL)

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

    @staticmethod
    def isUsable(player, level, position, index, slot, tool):
        return isPillarSpellUsable(player, level, position, SkillTuning.UPHEAVAL)

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

    @staticmethod
    def create():
        return createSkill(Upheaval, SkillTuning.UPHEAVAL)
