from core.helper import callEvent, sendMessage

from siege import Cooldown, game, Skill, SkillType
from siege.component import CombatStatus, ToolbarSlot
from siege.graphic import PixelCheck
from siege.network import NetworkManager
# from siege.sfml import SoundStatus
from siege.util import Random, Rect, Timer, Vector
from siege.world import World
from siege.world.realm import Layer


def chanceToGainTalentPoints(player, talent, level, rate, amount):
    points = calculateTpPoints(player, talent, level, rate, amount)
    gainTalentPoints(player, talent, points)


def calculateTpPoints(player, talent, level, rate, amount):
    talentLevel = talent.level.get()
    diff = talentLevel - level
    if diff >= 5:
        return 0
    elif diff > 0:
        rate /= 2
    ratio = max(1.6, float(level) / talentLevel)
    rate = int(rate * ratio + player.entity.stats.LUCK.get() / 16.0)
    if rate > Random.get(255):
        if diff > 0:
            amount /= diff + 1
        variance = max(amount / 5, 1)
        return int(min(ratio, 2) * amount + Random.get(-variance, variance))
    return 0


def gainTalentPoints(player, talent, points):
    points = callEvent("gain_tp", player, talent, points=points).points
    if not points:
        return
    talentLevel = talent.level.get()
    talent.gainPoints(points)
    sendMessage("general", "System", "GainTp", player.networkId, talent=talent.name, amount=points)
    if talent.level.get() > talentLevel:
        for _ in xrange(talent.level.get() - talentLevel):
            sendMessage("general", "System", "TalentLevelUp", player.networkId, talent=talent.name)


def createSkill(skillClass, skillTuning):
    skillType = SkillType.Active if hasattr(skillClass, 'onUse') else SkillType.Passive
    skill = Skill(skillTuning.NAME, skillType, skillTuning.ICON, skillTuning.LEVEL_COSTS)
    skill.unlockLevel = skillTuning.UNLOCK_LEVEL
    if skillType == SkillType.Active:
        skill.onUse = skillClass.onUse
        if not NetworkManager.isHost() and hasattr(skillClass, 'onClientUse'):
            skill.onUse = skillClass.onClientUse
        if hasattr(skillClass, 'isAvailable'):
            skill.isAvailable = skillClass.isAvailable
        if hasattr(skillClass, 'isUsable'):
            skill.isUsable = skillClass.isUsable
    else:
        skill.onActivate = skillClass.onActivate
        skill.onDeactivate = skillClass.onDeactivate
    skill.description = skillClass.description
    cooldown = getTuningData(skillTuning, 1, 'COOLDOWN', default=0)
    if cooldown:
        cooldownName = skillTuning.COOLDOWN_NAME if 'COOLDOWN_NAME' in skillTuning else skillTuning.NAME
        skill.cooldown = Cooldown(cooldownName, cooldown)
    return skill


def usableSkillCost(player, level, skillTuning):
    return player.entity.stats.SP.get() >= getTuningData(skillTuning, level, 'SP_COST')


def getTuningData(tuning, level, constant, levelBased="", default=None):
    if hasattr(tuning, constant):
        return getattr(tuning, constant)
    if not levelBased:
        levelBased = constant + 'S'
    if hasattr(tuning, levelBased):
        return getattr(tuning, levelBased)[level - 1]
    else:
        return default


def getAllyTarget(player, position, spellRange, prioritizePlayer=True):
    area = Rect(position.x, position.y, 1, 1)
    if prioritizePlayer and PixelCheck.contains(player.entity.render, area, player.entity.loopWidth):
        return player.entity
    realm = player.entity.realm
    team = player.entity.combat.team
    targets = realm.layers[Layer.Active].getNearby(position, spellRange)
    for target in targets:
        if target.has('combat') and target.combat.team == team and PixelCheck.contains(target.render, area, target.loopWidth):
            return target
    return None


def getTarget(player, position, spellRange, prioritizePlayer=True):
    area = Rect(position.x, position.y, 1, 1)
    if prioritizePlayer and PixelCheck.contains(player.entity.render, area, player.entity.loopWidth):
        return player.entity
    realm = player.entity.realm
    targets = realm.layers[Layer.Active].getNearby(position, spellRange)
    for target in targets:
        if target.has('combat') and PixelCheck.contains(target.render, area, target.loopWidth):
            return target
    return None


def getSkill(tool, slot):
    return tool.left.skill if slot is ToolbarSlot.LEFT else tool.right.skill


def setSkillCooldown(player, level, tool, slot, skillTuning):
    skill = getSkill(tool, slot)
    cooldown = skill.cooldown
    cooldown.duration = getTuningData(skillTuning, level, 'COOLDOWN', default=0)
    events = player.entity.event
    if events.has('skill_cooldown'):
        cooldown = Cooldown(cooldown)
        events["skill_cooldown"].invoke(skill, cooldown)
    player.entity.combat.setCooldown(cooldown)


def castSpell(player, level, tool, slot, spellTuning, spellCallback, isInterruptible=True, onInterrupt=None):
    castTime = getTuningData(spellTuning, level, 'CAST_TIME')
    if castTime is None or castTime == 0:
        spellCallback()
        setSkillCooldown(player, level, tool, slot, spellTuning)
        return
    timer = Timer(castTime)
    combat = player.entity.combat
    center = player.entity.physics.getCenter()
    spellSound = [] if 'SOUND' in spellTuning else [None]
    castSound = game.audio.playAt("mods/core/audio/sfx/magic/magic_cast_loop.ogg", center, broadcast=True)
    castSound.setLoop(True)

    def updateCasting(physics, timestepTime):
        timer.update(timestepTime)
        if not spellSound and spellTuning.SOUND_TIMING >= timer.remaining():
            spellSound.append(game.audio.playAt(spellTuning.SOUND, center, broadcast=True))
        if timer.expired():
            castSound.stop()
            spellCallback()
            setSkillCooldown(player, level, tool, slot, spellTuning)
            combat.status = CombatStatus.Idle
            player.entity.animation.stop("arm_front")
            if isInterruptible:
                combat.onHit.remove(onInterrupt)
            return True

    combat.status = CombatStatus.Casting
    player.entity.animation.play("casting", "arm_front")
    player.entity.physics.onTimeStep.listen(updateCasting)

    if isInterruptible:
        if onInterrupt is None:
            def interruptCasting(attacker, target, attackData, damage):
                if attackData.interrupts and combat.status is CombatStatus.Casting:
                    castSound.stop()
                    if spellSound and spellSound[0] is not None:
                        spellSound[0].stop()
                    combat.status = CombatStatus.Idle
                    player.entity.animation.stop("arm_front")
                    player.entity.physics.onTimeStep.remove(updateCasting)
                    return True
            onInterrupt = interruptCasting
        player.entity.combat.onHit.listen(onInterrupt)


def castSpellClient(player, level, tool, slot, skillTuning):
    def handleStatusChange(previous, status):
        player.entity.animation.stop("arm_front")
        return True
    player.entity.combat.status = CombatStatus.Casting
    player.entity.animation.play("casting", "arm_front")
    player.entity.combat.onStatusChange.listen(handleStatusChange)
    setSkillCooldown(player, level, tool, slot, skillTuning)


def concludeSkillUse(player, talent, level, tool, slot, skillTuning):
    cost = getTuningData(skillTuning, level, 'SP_COST')
    if cost is not None:
        player.entity.stats.SP.adjust(-cost)
    tpChance = getTuningData(skillTuning, level, 'TP_CHANCE')
    if tpChance is not None:
        skillLevel = skillTuning.UNLOCK_LEVEL + level - 1
        chanceToGainTalentPoints(player, talent, skillLevel, tpChance, getTuningData(skillTuning, level, 'TP_AMOUNT'))
    setSkillCooldown(player, level, tool, slot, skillTuning)


def visualSpell(player, talent, level, tool, slot, target, contentName, skillTuning, offset=Vector()):
    entity = game.entity.create(game.content.get(contentName))
    position = target.physics.getCenter() + offset
    entity.setInitialPosition(position)
    entity.animation.onFinished.listen(lambda name, group: entity.destroy())
    attachToEntity(entity, target, offset)
    World.get().move(entity, target.realm, Layer.Active)
    concludeSkillUse(player, talent, level, tool, slot, skillTuning)


def attachToEntity(attacher, entity, offset):
    def _attachToEntity(physics, timestepTime):
        attacher.setPosition(entity.physics.getCenter() + offset)
    attacher.physics.onTimeStep.listen(_attachToEntity)


def applyEffectSpell(player, talent, level, tool, slot, target, contentName, effect, skillTuning, offset=Vector()):
    visualSpell(player, talent, level, tool, slot, target, contentName, skillTuning, offset)
    target.effects.add(effect, level, getTuningData(skillTuning, level, 'DURATION'))
