from core.combat import AttackData, damageEnemies, getHitEnemies
from core.item.common import rotateWithVelocity

from siege import game, Entity
from siege.component import Component, ComponentDefinition, ComponentFactory, CombatTeam
from siege.util import Timer, Vector
from siege.world.realm import Layer


class Projectile(ComponentDefinition):
    def __init__(self, power, attackType, damageType, knockback=0, interrupts=False):
        ComponentDefinition.__init__(self)
        self.power = power
        self.attackType = attackType
        self.damageType = damageType
        self.knockback = knockback
        self.interrupts = interrupts
        self.hitFrames = []
        self.freeze()

    def getType(self):
        return ProjectileComponent.TYPE


class ProjectileComponent(Component):
    TYPE = "projectile"
    CID = 0
    VERSION = 1

    @staticmethod
    def create(entity, componentType, definition):
        projectile = ProjectileComponent(definition)
        entity.add(projectile)
        return projectile

    def __init__(self, definition):
        Component.__init__(self)
        self.attackData = AttackData(definition.power, definition.attackType, definition.damageType, definition.knockback, definition.interrupts)
        self.owner = None
        self.attackId = 0
        self.lifetime = Timer()
        self.attached = None
        self.attachedOffset = Vector()
        self.hitFrames = list(definition.hitFrames)
        self.freeze()

    def update(self, frameTime):
        if not self.lifetime.expired():
            self.lifetime.update(frameTime)
            if self.lifetime.expired():
                self.getParent().destroy()

    def destroy(self):
        if self.owner and self.owner.has("combat"):
            self.owner.combat.finishAttack(self.attackId)

    def setOwner(self, owner, center=False, offset=Vector()):
        self.owner = owner
        self.attackId = owner.combat.startAttack()
        if center:
            self.centerOnOwner(offset)

    def centerOnOwner(self, offset=Vector()):
        center = self.owner.physics.getCenter()
        parent = self.getParent()
        parent.render.setOriginToCenter()
        parent.setInitialPosition(center + offset)

    def disappearOnHit(self, physics, timestepTime):
        area = physics.getWorldBody()
        area.adjust(-1, -1, 2, 2)
        parent = self.getParent()
        if parent.realm.layers[Layer.Ground].overlaps(area, True, True) or self.damageTouchingEnemies(physics, timestepTime):
            #TODO: Replace this with some 'hit' animation and have it disappear
            parent.destroy()

    def damageTouchingEnemies(self, physics, timestepTime):
        if self.hitFrames and self.getParent().render.getFrame().index not in self.hitFrames:
            return False
        enemies = getHitEnemies(self.getParent(), self.owner.combat.team)
        if enemies:
            damageEnemies(self.attackId, self.owner, enemies, self.attackData)
        return bool(enemies)

    def rotateToDirection(self, physics, timestepTime):
        self.getParent().render.setRotation(physics.getVelocity().getAngle())

    def _clearAttached(self, physics):
        self.attached = None
        self.attachedOffset.x = 0
        self.attachedOffset.y = 0
        physics.active = True
        physics.setVelocity(Vector())

    def _unattachFromGround(self, tileLayer, tilePos, previousId, tileId):
        if tileId == 0 and tileLayer.type == Layer.Ground and tilePos == self.attached:
            self._clearAttached(self.getParent().physics)
            game.events["tile_changed"].remove(self._unattachFromGround)

    def attachOnHit(self, physics, timestepTime):
        parent = self.getParent()
        if self.attached and isinstance(self.attached, Entity):
            # Touching entity - keep position and move toward center
            if not self.attached.isContentEntity():
                center = self.attached.physics.getCenter()
                distance = center - physics.getCenter()
                position = Vector(center)
                position.x += distance.x / 8
                position.y += distance.y / 8
                parent.setPosition(position)
            return

        # Not attached to an entity so first check if touching entity now
        touchingEnemies = getHitEnemies(parent, CombatTeam.NONE)
        if touchingEnemies and touchingEnemies[0] == self.owner:
            del touchingEnemies[0]
        if touchingEnemies:
            if self.attached:
                # Currently attached to the ground so remove tile listener
                game.events["tile_changed"].remove(self._unattachFromGround)
            else:
                physics.active = False
                physics.onTimeStep.remove(rotateWithVelocity)
            self.attached = touchingEnemies[0]
            self.attachedOffset = self.attached.physics.getCenter() - physics.getCenter()
            self.attached.onDestroyed.listen(lambda entity, unload: self._clearAttached(physics))

        # Still not attached to anything. Check if touching ground and attach
        if not self.attached:
            area = physics.getWorldBody()
            area.adjust(-1, -1, 2, 2)
            overlaps = parent.realm.layers[Layer.Ground].overlaps(area, True, True)
            if overlaps:
                physics.active = False
                physics.onTimeStep.remove(rotateWithVelocity)
                self.attached = overlaps[0]
                game.events["tile_changed"].listen(self._unattachFromGround)

    @staticmethod
    def register():
        ProjectileComponent.CID = game.entity.requestCid(ProjectileComponent.TYPE, Projectile)
        game.registerComponent(ProjectileComponent.TYPE, ComponentFactory.create(ProjectileComponent.create))
