from functools import partial

from core.combat import AttackType

from siege import game, Locale
from siege.component import PotentialAttribute
from siege.util import Random


class AttributeModifier(object):
    def __init__(self, text, threshold):
        self.text = text
        self.threshold = threshold


class StatDetails(object):
    def __init__(self, name, weight, *args):
        self.name = name
        self.weight = weight
        self.modifiers = []
        for modifier in args:
            self.modifiers.append(AttributeModifier(*modifier))


class ChangeStatAttribute(object):
    name = "ChangeStat"
    stats = {
        'HP': StatDetails('HP', 1, ("Stout", 0), ("Robust", 40), ("Behemoth", 100)),
        'SP': StatDetails('SP', 2, ("Blessed", 0), ("Sacred", 20), ("Mystical", 60)),
        'ATK': StatDetails('Attack', 8, ("Strike", 0), ("Onslaught", 10), ("Baritus'", 30)),
        'DEF': StatDetails('Defense', 8, ("Guarded", 0), ("Fortified", 10), ("Gildor's", 30)),
        'INT': StatDetails('Intelligence', 8, ("Enchanted", 0), ("Sorceror's", 10), ("Eutrice's", 30)),
        'MIND': StatDetails('Mind', 8, ("Warded", 0), ("Protective", 10), ("Aegis", 30)),
        'AGI': StatDetails('Agility', 8, ("Lithe", 0), ("Agile", 10), ("Boreous'", 30)),
        'LUCK': StatDetails('Luck', 8, ("Fortuitous", 0), ("Prosperous", 10), ("Golden", 30)),
        'ACC': StatDetails('Accuracy', 12, ("Accurate", 0), ("Precise", 8), ("Fated", 20)),
        'EVA': StatDetails('Evasion', 12, ("Deft", 0), ("Nimble", 8), ("Elusive", 20)),
        'SPD': StatDetails('Attack Speed', 15, ("Sharp", 0), ("Quick", 6), ("Mercurial", 15)),
        'MOVE': StatDetails('Movement Speed', 15, ("Restless", 0), ("Swift", 5), ("Vivacious", 12)),
        'fire': StatDetails('Fire Resistance', 8, ("Cooling", 0), ("Insulated", 10), ("Aegnix's", 30)),
        'water': StatDetails('Water Resistance', 8, ("Absorbant", 0), ("of Evaporation", 10), ("Amnis'", 30)),
        'earth': StatDetails('Earth Resistance', 8, ("", 0), ("", 10), ("Silvan's", 30)),
        'wind': StatDetails('Wind Resistance', 8, ("", 0), ("", 10), ("Aeolus'", 30)),
        'HPRegen': StatDetails('HP Regeneration', 10, ("of Recovery", 0), ("of Restoration", 5), ("of Regenration", 12)),
        'SPRegen': StatDetails('SP Regeneration', 10, ("of Renewal", 0), ("of Recharging", 5), ("of Replenishment", 12)),
        'knockback': StatDetails('Knockback Power', 10, ("Impactful", 0), ("Crushing", 8)),
        'knockbackRes': StatDetails('Knockback Resistance', 10, ("Steady", 0), ("Tenacious", 8)),
        'reach': StatDetails('Reach Radius', 8, ("Expansive", 0)),
        'pickup': StatDetails('Pickup Radius', 8, ("of Snatching", 0)),
        'tool': StatDetails('Tool Power', 8, ("of Breaking", 0)),
        'craft': StatDetails('Craft Skill', 8, ("Resourceful", 0)),
    }

    def __init__(self, stat='', value=0):
        self.statName = stat
        if stat in self.stats:
            self.stat = self.stats[stat]
        self.value = value
        self.changed = 0

    @property
    def text(self):
        return Locale.get("ChangeStatAttr").format(stat=Locale.get(self.stat.name), amount=self.value)

    @property
    def alignment(self):
        return self.value >= 0

    @property
    def quality(self):
        return self.value * self.stat.weight

    def modifyName(self, name):
        modifier = None
        for mod in self.stat.modifiers:
            if self.value < mod.threshold:
                break
            modifier = mod
        return Locale.get(modifier.text).format(name=name) if modifier else name

    def canCombine(self, other):
        return self.statName == other.statName

    def combine(self, other):
        self.value += other.value
        return self.value == 0

    def onEquip(self, player, item, slot, equipment, removedItem):
        stat = player.entity.stats.get(self.statName)
        if stat.hasMax():
            self.changed = stat.adjustMax(self.value)
        else:
            self.changed = stat.adjust(self.value)

    def onUnequip(self, player, item, slot, equipment):
        stat = player.entity.stats.get(self.statName)
        if stat.hasMax():
            stat.adjustMax(-self.changed)
        else:
            stat.adjust(-self.changed)
        self.changed = 0

    def write(self, stream):
        stream.writeString(self.statName)
        stream.writeInt32(self.value)
        stream.writeFloat(self.changed)

    def read(self, stream):
        self.statName = stream.readString()
        self.stat = self.stats[self.statName]
        self.value = stream.readInt32()
        self.changed = stream.readFloat()

    @staticmethod
    def create(stat, values):
        def createInner(component):
            value = values[0]
            if value != values[1]:
                quality = min((Random.get(128) + component.quality) / 255.0, 1.0)
                value = values[0] + int((values[1] - values[0]) * quality)
            return ChangeStatAttribute(stat, value)
        return createInner

    @staticmethod
    def createPotential(stat, values, weight, quality=0):
        onCreate = ChangeStatAttribute.create(stat, values)
        return PotentialAttribute(ChangeStatAttribute.name, onCreate, weight, quality)

    @staticmethod
    def register():
        game.equipment.registerAttribute(ChangeStatAttribute)


class OnHitAttribute(object):
    name = "OnHit"

    def __init__(self, effect, level, chance):
        self.effect = effect
        self.level = level
        self.chance = chance

    @property
    def text(self):
        return Locale.get("OnHitAttr").format(stat=Locale.get(self.effect), level=self.level, chance=self.chance)

    @property
    def alignment(self):
        return True

    @property
    def quality(self):
        return self.chance

    def modifyName(self, name):
        return name

    def onEquip(self, player, item, slot, equipment, removedItem):
        player = player.entity
        #Add callback that is called on hit

    def onUnequip(self, player, item, slot, equipment):
        player = player.entity
        #Remove callback

    def onLoad(self, player, item, slot, equipment):
        self.onEquip(player, item, slot, equipment, None)

    def write(self, stream):
        stream.writeString(self.effect)
        stream.writeInt32(self.level)
        stream.writeInt32(self.chance)

    def read(self, stream):
        self.effect = stream.readString()
        self.level = stream.readInt32()
        self.chance = stream.readInt32()

    @staticmethod
    def create(stat, values):
        def createInner(component):
            value = values[0]
            #TODO: Determine value
            return ChangeStatAttribute(stat, value)
        return createInner

    @staticmethod
    def createPotential(stat, values, weight, quality=0):
        onCreate = ChangeStatAttribute.create(stat, values, quality)
        return PotentialAttribute(ChangeStatAttribute.name, onCreate, weight)


class TpBonusDetails(object):
    def __init__(self, talent, weight, *args):
        self.talent = talent
        self.weight = weight
        self.modifiers = []
        for modifier in args:
            self.modifiers.append(AttributeModifier(*modifier))


class TpBonusAttribute(object):
    name = "TpBonus"
    WEIGHT = 10
    talents = {
        'arms': TpBonusDetails('Arms', 12, ("Talented", 0)),
        'magic': TpBonusDetails('Magic', 12, ("Gifted", 0)),
        'craft': TpBonusDetails('Craft', 12, ("Knack", 0)),
        'gather': TpBonusDetails('Gather', 12, ("Gardener", 0)),
        'all': TpBonusDetails('All', 0)
    }

    def __init__(self, talentName='', bonus=0):
        self.talentName = talentName
        self.bonus = bonus

    @property
    def text(self):
        return Locale.get("TpBonusAttr").format(talent=Locale.get(self.talentName), bonus=self.bonus)

    @property
    def alignment(self):
        return True

    @property
    def quality(self):
        return self.bonus * self.talents[self.talentName].weight

    def modifyName(self, name):
        modifier = None
        for mod in self.talents[self.talentName].modifiers:
            if self.bonus < mod.threshold:
                break
            modifier = mod
        return Locale.get(modifier.text).format(name=name) if modifier else name

    def onEquip(self, player, item, slot, equipment, removedItem):
        player.entity.event["gain_tp"].listen(self.handleTpGain)

    def onUnequip(self, player, item, slot, equipment):
        player.entity.event["gain_tp"].remove(self.handleTpGain)

    def onLoad(self, player, item, slot, equipment):
        self.onEquip(player, item, slot, equipment, None)

    def handleTpGain(self, player, results, talent, points):
        if self.talentName.lower() == "all" or self.talentName.lower() == talent.name.lower():
            results.points = points + int(points * self.bonus / 100.0)

    def write(self, stream):
        stream.writeString(self.talentName)
        stream.writeInt32(self.bonus)

    def read(self, stream):
        self.talentName = stream.readString()
        self.bonus = stream.readInt32()

    @staticmethod
    def create(talent, bonuses):
        def createInner(component):
            bonus = bonuses[0]
            if bonus != bonuses[1]:
                quality = min((Random.get(128) + component.quality) / 255.0, 1.0)
                bonus = bonuses[0] + int((bonuses[1] - bonuses[0]) * quality)
            return TpBonusAttribute(talent, bonus)
        return createInner

    @staticmethod
    def createPotential(talent, bonuses, weight, quality=0):
        onCreate = TpBonusAttribute.create(talent, bonuses)
        return PotentialAttribute(TpBonusAttribute.name, onCreate, weight, quality)

    @staticmethod
    def register():
        game.equipment.registerAttribute(TpBonusAttribute)


class BreathAttribute(object):
    name = "Breath"
    WEIGHT = 0.1

    def __init__(self, bonus=0):
        self.bonus = bonus
        self.changed = 0

    @property
    def text(self):
        return Locale.get("BreathAttr").format(bonus=self.bonus)

    @property
    def alignment(self):
        return True

    @property
    def quality(self):
        return int(self.bonus * self.WEIGHT)

    def modifyName(self, name):
        #return Locale.get("Inspiring").format(name=name)
        return name

    def onEquip(self, player, item, slot, equipment, removedItem):
        capacity = player.entity.breath.capacity
        self.change = int(capacity.get() * self.bonus / 100)
        current = player.entity.breath.current
        current.set(current.get() + self.change)
        capacity.set(capacity.get() + self.change)

    def onUnequip(self, player, item, slot, equipment):
        current = player.entity.breath.current
        current.set(current.get() - self.change)
        capacity = player.entity.breath.capacity
        capacity.set(capacity.get() - self.change)

    def write(self, stream):
        stream.writeInt32(self.bonus)

    def read(self, stream):
        self.bonus = stream.readInt32()

    @staticmethod
    def create(bonuses):
        def createInner(component):
            bonus = bonuses[0]
            if bonus != bonuses[1]:
                quality = min((Random.get(128) + component.quality) / 255.0, 1.0)
                bonus = bonuses[0] + int((bonuses[1] - bonuses[0]) * quality)
            return BreathAttribute(bonus)
        return createInner

    @staticmethod
    def createPotential(bonuses, weight, quality=0):
        onCreate = BreathAttribute.create(bonuses)
        return PotentialAttribute(BreathAttribute.name, onCreate, weight, quality)

    @staticmethod
    def register():
        game.equipment.registerAttribute(BreathAttribute)


class AttackTypeAttribute(object):
    name = "AttackType"
    WEIGHT = 0.5

    def __init__(self, attackType=0, bonus=0):
        self.attackType = attackType
        self.bonus = bonus

    @property
    def text(self):
        attackType = Locale.get(AttackType.getString[self.attackType])
        return Locale.get("AttackTypeAttr").format(type=attackType, bonus=self.bonus)

    @property
    def alignment(self):
        return True

    @property
    def quality(self):
        return int(self.bonus * self.WEIGHT)

    def modifyName(self, name):
        return name

    def onEquip(self, player, item, slot, equipment, removedItem):
        player.entity.event['deal_damage'].listen(self.onDealDamage)

    def onUnequip(self, player, item, slot, equipment):
        player.entity.event['deal_damage'].remove(self.onDealDamage)

    def onLoad(self, player, item, slot, equipment):
        self.onEquip(player, item, slot, equipment, None)

    def onDealDamage(self, player, results, attacker, target, data, isCritical, damage):
        if self.attackType == data.attackType:
            results.damage += results.damage * self.bonus / 100

    def write(self, stream):
        stream.writeInt32(self.attackType)
        stream.writeInt32(self.bonus)

    def read(self, stream):
        self.attackType = stream.readInt32()
        self.bonus = stream.readInt32()

    @staticmethod
    def create(attackType, bonuses):
        def createInner(component):
            bonus = bonuses[0]
            if bonus != bonuses[1]:
                quality = min((Random.get(128) + component.quality) / 255.0, 1.0)
                bonus = bonuses[0] + int((bonuses[1] - bonuses[0]) * quality)
            return AttackTypeAttribute(attackType, bonus)
        return createInner

    @staticmethod
    def createPotential(attackType, bonuses, weight, quality=0):
        onCreate = AttackTypeAttribute.create(attackType, bonuses)
        return PotentialAttribute(AttackTypeAttribute.name, onCreate, weight, quality)

    @staticmethod
    def register():
        game.equipment.registerAttribute(AttackTypeAttribute)


class OnJumpAttribute(object):
    name = "OnJump"
    WEIGHT = 10

    def __init__(self, bonus=0):
        self.bonus = bonus
        self.changed = 0

    @property
    def text(self):
        return Locale.get("OnJumpAttr").format(bonus=self.bonus)

    @property
    def alignment(self):
        return True

    @property
    def quality(self):
        return self.bonus * self.WEIGHT

    def modifyName(self, name):
        return Locale.get("Leaping").format(name=name)

    def onEquip(self, player, item, slot, equipment, removedItem):
        self._onEquip(game.input.getClientInput(player))

    def _onEquip(self, clientInput):
        self.changed = self.bonus * 16
        clientInput.jumpTime += self.changed

    def onUnequip(self, player, item, slot, equipment):
        client = game.input.getClientInput(player)
        client.jumpTime -= self.changed
        self.changed = 0

    def onLoad(self, player, item, slot, equipment):
        game.events['client_input_ready'].listen(partial(self.onLoadCallback, player, item, slot, equipment))

    def onLoadCallback(self, player, item, slot, equipment, clientInput):
        if game.input.isPlayersInput(player, clientInput):
            self._onEquip(clientInput)
            return True

    def write(self, stream):
        stream.writeInt32(self.bonus)

    def read(self, stream):
        self.bonus = stream.readInt32()

    @staticmethod
    def create(bonuses):
        def createInner(component):
            bonus = bonuses[0]
            if bonus != bonuses[1]:
                quality = min((Random.get(128) + component.quality) / 255.0, 1.0)
                bonus = bonuses[0] + int((bonuses[1] - bonuses[0]) * quality)
            return OnJumpAttribute(bonus)
        return createInner

    @staticmethod
    def createPotential(bonuses, weight, quality=0):
        onCreate = OnJumpAttribute.create(bonuses)
        return PotentialAttribute(OnJumpAttribute.name, onCreate, weight, quality)

    @staticmethod
    def register():
        game.equipment.registerAttribute(OnJumpAttribute)


def register():
    ChangeStatAttribute.register()
    TpBonusAttribute.register()
    BreathAttribute.register()
    AttackTypeAttribute.register()
    OnJumpAttribute.register()
