import warnings

from core.component.data import DataDefinition
from core.combat import AttackType, DamageType
from core.item.common import useBowWeapon, useMeleeWeapon
import core.item.armor.attribute as attributes
from core.template.animation import Frame, Frames
from core.template import Substitute, Template

from siege import game, InventoryItem
from siege.component import AxisType, SupportType, PlacementAxis, PlacementSupport
from siege.graphic import SpriteFrame, TextureCache
from siege.util import PixelVector, PixelRect, Sizes, WeightedRandomGenerator
from siege.world.realm import Layer


class AttackTypeAttribute(object):
    def __init__(self, attackType=0, bonuses=(0, 0), weight=0):
        self.attackType = attackType
        self.bonuses = bonuses
        self.weight = weight

    def fixed(self):
        return attributes.AttackTypeAttribute.create(self.attackType, self.bonuses)

    def potential(self):
        return attributes.AttackTypeAttribute.createPotential(self.attackType, self.bonuses, self.weight)


class BreathAttribute(object):
    def __init__(self, bonuses=(0, 0), weight=0):
        self.bonuses = bonuses
        self.weight = weight

    def fixed(self):
        return attributes.BreathAttribute.create(self.bonuses)

    def potential(self):
        return attributes.BreathAttribute.createPotential(self.bonuses, self.weight)


class OnJumpAttribute(object):
    def __init__(self, bonuses=(0, 0), weight=0):
        self.bonuses = bonuses
        self.weight = weight

    def fixed(self):
        return attributes.OnJumpAttribute.create(self.bonuses)

    def potential(self):
        return attributes.OnJumpAttribute.createPotential(self.bonuses, self.weight)


class StatAttribute(object):
    def __init__(self, stat='', valueRange=(0, 0), weight=0):
        self.stat = stat
        self.valueRange = valueRange
        self.weight = weight

    def fixed(self):
        return attributes.ChangeStatAttribute.create(self.stat, self.valueRange)

    def potential(self):
        return attributes.ChangeStatAttribute.createPotential(self.stat, self.valueRange, self.weight)


class TpBonusAttribute(object):
    def __init__(self, talentName='', valueRange=(0, 0), weight=0):
        self.talentName = talentName
        self.valueRange = valueRange
        self.weight = weight

    def fixed(self):
        return attributes.TpBonusAttribute.create(self.talentName, self.valueRange)

    def potential(self):
        return attributes.TpBonusAttribute.createPotential(self.talentName, self.valueRange, self.weight)


class Item(Template):
    def __init__(self, **kwargs):
        Template.__init__(self, **kwargs)
        self.isItem(**kwargs)


class Tile(Template):
    sounds = {
        'dirt': ('mods/core/audio/sfx/soil/dirt[1-5].ogg', 'mods/core/audio/sfx/soil/dirtbreak[1-3].ogg'),
        'stone': ('mods/core/audio/sfx/rock/pickrock[1-3].ogg', 'mods/core/audio/sfx/rock/pickrockbreak[1-5].ogg'),
        'ore': ('mods/core/audio/sfx/rock/ore[1-7].ogg', 'mods/core/audio/sfx/rock/orebreak[1-3].ogg'),
        'sand': ('mods/core/audio/sfx/soil/sand[1-3].ogg', 'mods/core/audio/sfx/soil/sandbreak1.ogg'),
        'snow': ('mods/core/audio/sfx/soil/snow[1-3].ogg', 'mods/core/audio/sfx/soil/snowbreak[1-2].ogg'),
        'wood': ('mods/core/audio/sfx/wood/wood[1-3].ogg', 'mods/core/audio/sfx/wood/woodbreak[1-3].ogg'),
        'foliage': ('mods/core/audio/sfx/foliage/grass1.ogg', 'mods/core/audio/sfx/foliage/grass1.ogg')
    }

    def __init__(self, offset=None, **kwargs):
        Template.__init__(self, **kwargs)
        kwargs['groups'] = ['block', self.content.getName()] + kwargs.get('groups', [])
        attrs = [
            'standard',
            'priority',
            'variants',
            'simple',
            'solid',
            'stable',
            'support',
            'fragile',
            'replaceable',
            'collisions',
            'passthrough',
            'durability',
            'groups',
            'opacity',
            'level',
            'layer',
            'onHitSound',
            'onBreakSound',
            'particleColor',
            'canPlace'
        ]
        self.tile.set(attrs, kwargs)
        #TODO: Change to tile.drops
        self.offset = offset or PixelVector(1, 1)
        self.tile.dropped = kwargs.get('drops', self.content.getName())
        self.tile.variants = kwargs.get('variants', self.getVariants())
        if 'tileCount' in kwargs:
            self.tile.variants = kwargs['tileCount']
            self.tile.standard = False
            self.tile.simple = True
            for x in xrange(kwargs['tileCount']):
                self.render.data.frames.append(SpriteFrame(x * Sizes.TILE, 0, Sizes.TILE, Sizes.TILE))
        else:
            frames = 16
            if self.tile.priority:
                frames += 4
            width = 2 * self.offset.x + Sizes.TILE
            height = 2 * self.offset.y + Sizes.TILE
            for y in xrange(self.tile.variants):
                for x in xrange(frames):
                    frame = SpriteFrame(x * width, y * height, width, height)
                    frame.origin.x = self.offset.x
                    frame.origin.y = self.offset.y
                    self.render.data.frames.append(frame)
        if 'sounds' in kwargs:
            sounds = self.sounds[kwargs['sounds']]
            self.tile.onHitSound = sounds[0]
            self.tile.onBreakSound = sounds[1]

    def getVariants(self):
        texture = TextureCache.get(self.render.image)
        return texture.height / (2 * self.offset.y + Sizes.TILE)

    def canPlace(self, func):
        self.tile.canPlace = func

    def isItem(self, **kwargs):
        if 'useTime' not in kwargs:
            kwargs['useTime'] = 10
        Template.isItem(self, **kwargs)


class StorageContainer(Item):
    def __init__(self, **kwargs):
        kwargs.setdefault('stack', 99)
        kwargs.setdefault('useTime', 20)
        Item.__init__(self, **kwargs)
        if 'materials' in kwargs:
            kwargs.setdefault('category', "Basics")
            kwargs.setdefault('subcategory', "Storage")
            self.craftable(**kwargs)
        self.placeable(floor_axis=Axis(animation="closed"))
        self.hasInventory(capacity=kwargs.get('capacity', 5), interactable=True)
        texture = TextureCache.get(self.render.image)
        frameWidth = texture.width / 2
        closed = self.getSpriteFrames(Frame(0, 0, size=(frameWidth, texture.height)))
        opened = self.getSpriteFrames(Frame(frameWidth, 0, size=(frameWidth, texture.height)))
        self.animations(start='closed', closed=Frames(closed()), open=Frames(opened()))
        self.add(DataDefinition({'users': []}))


class ToolItem(Item):
    potentials = [
        StatAttribute('SPD', valueRange=(1, 3), weight=20),
        StatAttribute('SPD', valueRange=(1, 3), weight=20),
        StatAttribute('SPD', valueRange=(1, 3), weight=20),
        StatAttribute('SPD', valueRange=(1, 3), weight=20),
        StatAttribute('reach', valueRange=(2, 4), weight=10),
        StatAttribute('reach', valueRange=(2, 4), weight=10),
        StatAttribute('reach', valueRange=(2, 4), weight=10),
        StatAttribute('reach', valueRange=(2, 4), weight=10),
        StatAttribute('pickup', valueRange=(2, 4), weight=10),
        StatAttribute('pickup', valueRange=(2, 4), weight=10),
        StatAttribute('pickup', valueRange=(2, 4), weight=10),
        StatAttribute('pickup', valueRange=(2, 4), weight=10),
        StatAttribute('tool', valueRange=(1, 3), weight=20),
        StatAttribute('tool', valueRange=(1, 3), weight=20),
        StatAttribute('tool', valueRange=(1, 3), weight=20),
        StatAttribute('tool', valueRange=(1, 3), weight=20)
    ]
    potentialRange = (0, 4)

    def __init__(self, **kwargs):
        kwargs.setdefault('unique', True)
        kwargs.setdefault('useTime', 33)
        kwargs.setdefault('useAnimation', ("swing", "arm_front"))
        kwargs.setdefault('useVisual', Substitute("swing"))
        Item.__init__(self, **kwargs)
        if 'materials' in kwargs:
            kwargs.setdefault('category', "Tools")
            kwargs.setdefault('subcategory', "Mattock")
            self.craftable(**kwargs)
        self.isTool(**kwargs)
        if self.item.unique or 'attributes' in kwargs:
            self.equippable(
                slot = "active",
                attributes = kwargs.get('attributes', []),
                potentialRange = kwargs.get('potentialRange', ToolItem.potentialRange),
                potentials = kwargs.get('potentials', ToolItem.potentials),
            )


class Weapon(Item):
    def __init__(self, category, onUse, **kwargs):
        kwargs.setdefault('unique', True)
        Item.__init__(self, **kwargs)
        if 'materials' in kwargs:
            kwargs['category'] = kwargs.get('category', "Weapons")
            self.craftable(**kwargs)
        self.isWeapon(
            category=category,
            power = kwargs['power'],
            attackType = kwargs.get('attackType', AttackType.Melee),
            damageType = kwargs.get('damageType', DamageType.Physical),
            onUse = onUse
        )
        if self.item.unique or 'attributes' in kwargs:
            self.equippable(
                slot = "active",
                attributes = kwargs.get('attributes', []),
                potentialRange = kwargs.get('potentialRange', (0, 0)),
                potentials = kwargs.get('potentials', [])
            )

    def onUse(self, func):
        self.weapon.onUse = func


class Sword(Weapon):
    def __init__(self, **kwargs):
        kwargs.setdefault('useTime', 38)
        kwargs.setdefault('useAnimation', ("sword", "arm_front"))
        kwargs.setdefault('useVisual', Substitute("sword"))
        if 'materials' in kwargs:
            kwargs.setdefault('subcategory', "Swords")
        Weapon.__init__(self, "Sword", kwargs.get('onUse', useMeleeWeapon), **kwargs)


class Bow(Weapon):
    def __init__(self, **kwargs):
        kwargs.setdefault('useTime', 40)
        kwargs.setdefault('useVisual', Substitute("bow"))
        kwargs.setdefault('attackType', AttackType.Ranged)
        if 'materials' in kwargs:
            kwargs['subcategory'] = kwargs.get('subcategory', "Bows")
        Weapon.__init__(self, "Bow", kwargs.get('onUse', useBowWeapon()), **kwargs)

    def onFinish(self, func):
        self.weapon.onUse = useBowWeapon(func)


class Material(object):
    def __init__(self, item='', quantity=1):
        self.item = item
        self.quantity = quantity


class CraftResult(object):
    def __init__(self, item='', items=None, quantity=1, quality=0):
        if items is None:
            self.items = []
        else:
            self.items = items
        if items is None:
            self.items.append(Material(item, quantity))
        self.quality = quality


class Axis(object):
    types = {
        "none_axis": AxisType.AXIS_NONE,
        "ceiling_axis": AxisType.AXIS_CEILING,
        "floor_axis": AxisType.AXIS_FLOOR,
        "left_axis": AxisType.AXIS_LEFT,
        "right_axis": AxisType.AXIS_RIGHT,
        "backwall_axis": AxisType.AXIS_BACKWALL,
        "wall_axis": AxisType.AXIS_WALL
    }

    def __init__(self, animation='', start=0, length=0, area=PixelRect(), layer=Layer.None, **kwargs):
        self.placementAxis = PlacementAxis(AxisType.AXIS_NONE, area, layer)
        self.placementAxis.animation = animation
        self.placementAxis.range = PixelVector(start, length)
        for key, supportType in Support.types.iteritems():
            if key in kwargs:
                support = kwargs[key]
                self.placementAxis.addSupport(PlacementSupport(supportType, PixelVector(support.start, support.length)))


class Support(object):
    types = {
        'support_top': SupportType.SUPPORT_TOP,
        'support_bottom': SupportType.SUPPORT_BOTTOM,
        'support_left': SupportType.SUPPORT_LEFT,
        'support_right': SupportType.SUPPORT_RIGHT
    }

    def __init__(self, start=0, length=0):
        self.start = start
        self.length = length


class Loot(object):
    def __init__(self, content='', dropRates=None):
        self.content = content
        percentageCovered = sum(dropRates.values())
        remainder = 100 - percentageCovered
        if remainder > 0:
            dropRates[0] = remainder
        elif remainder < 0:
            warnings.warn("Loot has over 100% drop rate.")
        self.dropAmount = dropRates.keys()
        self.wrg = WeightedRandomGenerator([int(rate * 100) for rate in dropRates.values()])
        self.count = 0

    def dropped(self):
        # Updates the count and returns bool if it dropped or not
        self.count = self.dropAmount[self.wrg()]
        return bool(self.count)

    def retrieve(self):
        return InventoryItem(game.content.get(self.content), self.count)
