from core.combat import applyDamage, AttackType, DamageType, getDefense, calculateDamage, factorDamageType
from core.tuning import Tuning

from siege import game
from siege.component import CombatComponent
from siege.graphic import Color, DrawableText, Renderable
from siege.io import DataStream
from siege.network import Message, MessageHandler, NetworkManager
from siege.subsystem import Subsystem
from siege.util import Rect, seconds, Sizes, Timer, Vector
from siege.world import World
from siege.world.realm import LavaCell


class CombatSystem(Subsystem, Renderable):
    NAME = "combat"
    NUMBER_SCREEN_BUFFER = 50

    def getName(self):
        return self.NAME

    def __init__(self):
        Subsystem.__init__(self)
        Renderable.__init__(self)
        self.numbers = []
        self.combatants = {}
        # key, value = entity.id, Timer
        self.lavaTimers = {}
        self.freeze()

    def create(self, entity, type, definition):
        component = CombatComponent(definition)
        entity.add(component)
        if not entity.isContentEntity():
            self.combatants[entity.id] = (entity, component)

    def add(self, entity):
        if not entity.isContentEntity() and entity.has("combat"):
            self.combatants[entity.id] = (entity, entity.combat)

    def remove(self, entity):
        if entity.id in self.combatants:
            del self.combatants[entity.id]

    def clear(self, entity):
        self.numbers = []
        self.combatants.clear()

    def update(self, frameTime):
        numbers = []
        for number in self.numbers:
            number.timer.update(frameTime)
            if not number.timer.expired():
                numbers.append(number)
        self.numbers = numbers
        if not NetworkManager.isHost():
            for entity, component in self.combatants.values():
                component.update(frameTime)
            return
        for entity, component in self.combatants.values():
            component.update(frameTime)
            if component.isAlive.get() and game.isOnTick():
                stats = entity.stats
                #Health regeneration
                hp = stats.HP
                if hp.get() != hp.getMax():
                    hpRegenTotal = stats.HPRegen.get() if stats.has('HPRegen') else 0
                    if entity.combat.timeSinceCombat >= seconds(30):
                        hpRegenTotal += hp.getMax() / 30
                    if hpRegenTotal:
                        value = int(hp.adjust(int(hpRegenTotal)))
                        self.showCombatNumber(entity, value, False)

                if stats.has('SP'):
                    #Skill Points Regeneration
                    se = stats.SP
                    if se.get() != se.getMax():
                        se.adjust(int(stats.SPRegen.get()))
            if entity.id in self.lavaTimers:
                timer = self.lavaTimers[entity.id]
                timer.update(frameTime)
                if timer.expired():
                    del self.lavaTimers[entity.id]
            elif entity.has('physics') and not component.isInvincible.get():
                self.checkLavaCollision(entity, component)

    def checkLavaCollision(self, entity, component):
        physics = entity.physics
        body = physics.getWorldBody()
        cells = World.get().getRealm().automata.getCells(body)
        if not cells:
            return
        amount = 0
        for cell in cells:
            if isinstance(cell, LavaCell):
                amount += cell.quantity
        maxAmount = int(body.width / Sizes.SUBTILE) * int(body.height / Sizes.SUBTILE) * cells[0].getMaxQuantity()
        percentage = float(amount) / maxAmount
        if percentage > 0.2:
            defense = getDefense(entity, AttackType.Melee)
            strength = max(3 * defense / 4, min(Tuning.LAVA_STRENGTH, 5 * defense / 4))
            damage = calculateDamage(strength, defense, Tuning.LAVA_LEVEL, Tuning.LAVA_POWER)
            damage = factorDamageType(entity, DamageType.Fire, damage)
            applyDamage(entity, damage, Vector(), 0, "DeathLava")
            self.lavaTimers[entity.id] = Timer(Tuning.LAVA_DAMAGE_FREQUENCY)

    def showCombatNumber(self, combatant, value, isCritical):
        if value == 0:
            return
        realmUid = combatant.realm.uid
        position = combatant.getPosition() + combatant.combat.numberOffset
        self._showCombatNumber(realmUid, position, value, isCritical)
        if NetworkManager.isHost():
            stream = DataStream(Message.SHOW_COMBAT_NUMBER)
            stream.writeUint32(realmUid)
            stream.writeVector(position)
            stream.writeInt32(value)
            stream.writeBool(isCritical)
            game.network.getServer().broadcast(stream)

    def _showCombatNumber(self, realmUid, position, value, isCritical):
        color = Color(255, 180, 180) if value < 0 else Color(180, 255, 180)
        self.numbers.append(CombatNumber(realmUid, str(abs(value)), position, color, isCritical))

    def handleShowCombatNumber(self, client, stream):
        realmUid = stream.readUint32()
        position = stream.readVector()
        value = stream.readInt32()
        isCritical = stream.readBool()
        self._showCombatNumber(realmUid, position, value, isCritical)

    def render(self, window):
        center = self.view.getCenter()
        size = self.view.getSize()
        viewport = Rect(center.x - (size.x / 2) - self.NUMBER_SCREEN_BUFFER, center.y - (size.y / 2) - self.NUMBER_SCREEN_BUFFER, size.x + self.NUMBER_SCREEN_BUFFER * 2, size.y + self.NUMBER_SCREEN_BUFFER * 2)
        realm = World.get().getRealm()
        for number in self.numbers:
            if number.realmUid != realm.uid:
                continue
            position = number.text.getPosition()
            position.y -= 1
            realX = position.x
            if abs(position.x - center.x) > realm.size.halfWidth:
                position.x = position.x + realm.size.width * (-1 if position.x > center.x else 1)
            if viewport.contains(position):
                number.text.setPosition(position)
                number.render(window, position)
            position.x = realX
            number.text.setPosition(position)

    def onWorldLoaded(self, world):
        game.render.addRenderable(self, 6000)

    @staticmethod
    def register():
        combatSystem = CombatSystem()
        if not NetworkManager.isHost() and game.network.getClient():
            game.network.getClient().register(Message.SHOW_COMBAT_NUMBER, MessageHandler.create(combatSystem.handleShowCombatNumber))
        game.events['world_loaded'].listen(combatSystem.onWorldLoaded)
        game.events['camera_moved'].listen(combatSystem.updateView)
        game.events['camera_centered'].listen(combatSystem.setViewCenter)
        game.registerSubsystem(combatSystem, CombatSystem.NAME, "combat")

    @staticmethod
    def unregister():
        try:
            combatSystem = game.combat
            game.events['world_loaded'].remove(combatSystem.onWorldLoaded)
            game.events['camera_moved'].remove(combatSystem.updateView)
            game.events['camera_centered'].remove(combatSystem.setViewCenter)
        except AttributeError:
            pass


class CombatNumber(object):
    DISPLAY_TIME = 800

    def __init__(self, realmUid, value, position, color, isCritical):
        self.realmUid = realmUid
        size = 20 if isCritical else 14
        self.text = DrawableText(value, "mods/core/ui/fonts/Bitter-Regular.ttf", size)
        self.text.setColor(color)
        self.text.setPosition(position)
        self.timer = Timer(self.DISPLAY_TIME)

    def getPosition(self):
        return self.text.getPosition()

    def render(self, window, position):
        color = self.text.getColor()
        color.a = self.timer.remaining() * 255 / self.DISPLAY_TIME
        self.text.setColor(color)
        self.text.renderOutlined(window, Color(49, 49, 49, color.a))
