from functools import partial

from core.combat import AttackType, DamageType
from core.component.ammo import Ammo
from core.component.data import DataDefinition
from core.component.projectile import Projectile
from core.item.armor import createEquipment
from core.item.common import interactInventory, upgradeEquipment
from core.template.animation import Frames, SpriteSheet
import core.template

from siege import game
from siege.component import ModularRender, Render, Surface
from siege.graphic import Frame, SpriteAnimation, SpriteFrame, Substitution
from siege.util import AssetCache, Vector


class Template(object):
    def __init__(self, name='', image='', giveRender=True, isModular=False, **kwargs):
        entityManager = game.entity
        self.name = name
        # Associate self with EntityManager and get back the content that is being created
        self.content = entityManager.associate(self)
        self.components = []
        if giveRender:
            if isModular:
                self.render = ModularRender()
                self.add(self.render)
            else:
                if not image:
                    # Automatically set the image to be name of content (eg. 'lumber' + '.png')
                    image = self.content.getName() + '.png'
                self.add(Render(image, self.content.getPath()))
            attrs = ['data', 'scale', 'rotation', 'color', 'flipX', 'flipY']
            self.render.set(attrs, kwargs)
            if 'icon' in kwargs:
                self.render.icon = AssetCache.getFullPath(kwargs['icon'], self.content.getPath())
            if 'wicon' in kwargs:
                self.render.wicon = AssetCache.getFullPath(kwargs['wicon'], self.content.getPath())
            self.spritesheet = SpriteSheet()
        self.isUpgradeItem = False
        self._onCreate = None
        self._onDestroy = None

    def onCreate(self, func):
        self._onCreate = func

    def onDestroy(self, func):
        self._onDestroy = func

    def add(self, component):
        self.components.append(component)
        setattr(self, component.getType(), component)

    def __getattr__(self, attr):
        # Automagically create a component definition the first time it is accessed
        entityManager = game.entity
        if entityManager.hasDefinition(attr):
            component = entityManager.getDefinition(attr)()
            self.add(component)
            return component
        raise AttributeError("{} object has no attribute {}".format(type(self).__name__, attr))

    def animations(self, start='', disableLooping=None, **kwargs):
        self.animation.start = start

        if disableLooping:
            for animation in disableLooping:
                self.animation.setLooping(animation, False)

        for frame in self.spritesheet.frames:
            origin = Vector(frame.origin.x, frame.origin.y)
            self.render.data.frames.append(SpriteFrame(frame.x, frame.y, frame.width, frame.height, origin))

        for name, frameLists in kwargs.iteritems():
            frames = []
            if isinstance(frameLists, Frames):
                frameLists = (frameLists,)
            for frameList in frameLists:
                for frame in frameList.frames:
                    frames.append(Frame(frame.index, frameList.time))
            self.render.data.animations[name] = SpriteAnimation(name, frames)

    def getSpriteFrames(self, *args, **kwargs):
        return self.spritesheet.get(*args, **kwargs)

    def isItem(self, **kwargs):
        if 'useTime' in kwargs:
            kwargs['useTime'] = kwargs['useTime'] * 16
        attrs = ['stack', 'cooldown', 'unique', 'useTime']
        self.item.set(attrs, kwargs)
        if 'useAnimation' in kwargs:
            self.item.setUseAnimation(*kwargs['useAnimation'])
        if 'useVisual' in kwargs:
            substitute = kwargs['useVisual']
            if substitute.replacement is None:
                substitute.replacement = self.render.image
            self.item.use = Substitution(substitute.base, substitute.replacement, substitute.origin)
        return self.item

    def craftable(self, upgradeFrom='', **kwargs):
        attrs = ['category', 'subcategory', 'level', 'experience', 'isResearchable', 'serviceRequired', 'onCraft']
        self.craft.set(attrs, kwargs)
        if upgradeFrom:
            self.craft.material(upgradeFrom, 1)
            self.isUpgradeItem = True
        for material in kwargs['materials']:
            self.craft.material(material.item, material.quantity)
        if 'results' in kwargs:
            for result in kwargs['results']:
                if len(result.items) == 1:
                    material = result.items[0]
                    if material.item:
                        self.craft.result(material.item, material.quantity, result.quality)
                    else:
                        self.craft.result(material.quantity, result.quality)
                else:
                    self.craft.result(result.items, result.quality)
        return self.craft

    def equippable(self, **kwargs):
        attrs = ['slot', 'paths', 'bodies', 'canEquip', 'onEquip', 'onUnequip', 'levelRequired']
        self.equipment.set(attrs, kwargs)
        if self.isUpgradeItem and self.craft.onCraft is None:
            self.craft.onCraft = upgradeEquipment
        self.setComponentVisuals(self.equipment, kwargs.get('visuals', []))
        attributes = []
        if 'attributes' in kwargs:
            for attribute in kwargs['attributes']:
                attributes.append(attribute.fixed())
        if 'potentials' in kwargs:
            for attribute in kwargs['potentials']:
                self.equipment.potentials.append(attribute.potential())
        potentialRange = kwargs.get('potentialRange', (0, 0))
        self.equipment.onCreate = createEquipment(potentialRange[0], potentialRange[1], attributes)
        return self.equipment

    def setComponentVisuals(self, component, visuals):
        for substitute in visuals:
            if substitute.replacement is None:
                substitute.replacement = self.render.image
            component.addSub(substitute.base, substitute.replacement, substitute.origin)

    def placeable(self, **kwargs):
        attrs = ['dropped', 'allowCollection', 'allowSupportRemoval']
        self.placement.set(attrs, kwargs)
        for key, axisType in core.template.item.Axis.types.iteritems():
            if key in kwargs:
                axis = kwargs[key]
                axis.placementAxis.axis = axisType
                self.placement.addAxis(axis.placementAxis)
        return self.placement

    def isWeapon(self, **kwargs):
        attrs = ['category', 'power', 'attackType', 'damageType', 'onUse']
        self.weapon.set(attrs, kwargs)
        return self.weapon

    def isTool(self, **kwargs):
        kwargs['compatible'] = ['block', 'harvest'] + kwargs.get('compatible', [])
        attrs = ['power', 'reach', 'compatible']
        self.tool.set(attrs, kwargs)
        return self.tool

    def usable(self, handler, *args, **kwargs):
        if args:
            handler = partial(handler, *args, **kwargs)
        self.event['use'].listen(handler)

    def hasLightSource(self, **kwargs):
        attrs = ['enabled', 'center', 'intensity', 'brightness', 'falloff', 'size', 'color']
        self.light.set(attrs, kwargs)
        return self.light

    def hasPhysics(self, **kwargs):
        attrs = ['active', 'collision', 'passthrough', 'immovable', 'gravity', 'friction', 'groundFriction', 'restitution', 'fallCap', 'body', 'slope', 'hasGroundCollision']
        self.physics.set(attrs, kwargs)
        return self.physics

    def usesAmmo(self, content):
        self.add(Ammo(content))
        return self.ammo

    def isProjectile(self, power=0, attackType=AttackType.Ranged, damageType=DamageType.Physical, knockback=0, interrupts=False, **kwargs):
        self.add(Projectile(power, attackType, damageType, knockback, interrupts))
        attrs = ['hitFrames']
        self.projectile.set(attrs, kwargs)
        return self.projectile

    def hasEvent(self, func):
        self.event[func.__name__].listen(func)

    def events(self, name):
        def eventDecorator(func):
            self.event[name].listen(func)
        return eventDecorator

    def provideServices(self, *args):
        self.add(Surface(list(args)))

    def interactable(self, handler, *args):
        if args:
            handler = partial(handler, *args)
        self.event['interact'].listen(handler)

    def onCraft(self, func):
        self.craft.onCraft = func

    def hasInventory(self, capacity=1, interactable=False, carryable=False):
        # Automatically sets up Bag(), Inventory(), Event('interact') and Data(users)
        self.bag.capacity = capacity
        self.bag.canCarry = carryable
        if interactable:
            self.inventory.bags.append(self.content.getName())
            self.data = DataDefinition({'users': []})
            self.interactable(interactInventory)


class Substitute(object):
    def __init__(self, base, replacement=None, origin=Vector()):
        self.base = base
        self.replacement = replacement
        self.origin = origin


class Hide(Substitute):
    def __init__(self, base):
        Substitute.__init__(self, base, '')
