from core.ui import boolStr, DialogUi
import core.ui.gameplay

from siege import game, Locale, GrabbedItemLocation
from siege.component import ToolbarSlot
from siege.io import DataStream
from siege.log import Log
from siege.network import Message
from siege.util import PixelRect, Vector
from siege.world import World


class ToolbarUi(DialogUi):
    DELTA_THRESHOLD = 16

    def __init__(self, player, tooltip):
        DialogUi.__init__(self, 'toolbar', PixelRect(0, 0, 850, 120))
        self.player = player
        self.toolbar = player.entity.toolbar
        self.tooltip = tooltip
        self.combat = player.entity.combat
        self.selectedSlot = ToolbarSlot.LEFT
        self.selectedIndex = 0
        self.selectedItem = None
        self.movementDelta = Vector()
        self.skillAvailability = {ToolbarSlot.LEFT: [False] * 10, ToolbarSlot.RIGHT: [False] * 10}
        self.updateSelected()
        #Handle resize and positioning
        windowSize = game.render.getWindowSize()
        self.onResize(windowSize.x, windowSize.y)
        game.render.onResize.listen(self.onResize)

        #Register UI callbacks
        self.view.register('populate', self.onPopulate)
        self.view.register('clickedSlot', self.onClickedSlot)
        self.view.register('showTooltip', tooltip.onShow)
        self.view.register('toggleSystem', self.onToggleSystem)

        self.load('html/toolbar.html')
        self.view.show()

        #Set callbacks to listen for if the toolbar changes at all
        self.toolbar.onChange.listen(self.handleToolChange)
        self.toolbar.onSelectedChange.listen(self.handleSelectedChange)
        stats = self.player.entity.stats
        stats.HP.onChange.listen(self.handleStatChange)
        stats.HP.onChangeMax.listen(self.handleStatMaxChange)
        stats.SP.onChange.listen(self.handleStatChange)
        stats.SP.onChangeMax.listen(self.handleStatMaxChange)
        breath = self.player.entity.breath
        breath.current.onChange.listen(self.handleCurrentBreathChange)
        breath.capacity.onChange.listen(self.handleCapacityBreathChange)
        self.player.entity.playerState.currency.onChange.listen(self.handleCurrencyChange)
        self.combat.onSetCooldown.listen(self.handleSetCooldown)
        self.player.entity.effects.onAddEffect.listen(self.handleAddEffect)
        self.player.entity.effects.onRemoveEffect.listen(self.handleRemoveEffect)
        self.player.entity.render.onMove.listen(self.handlePlayerMove)
        self.player.entity.event["realm_change"].listen(self.handleRealmChange)
        self.handleRealmChange(self.player, None, World.get().getRealm())

    def update(self, frameTime):
        # Update skills availability on toolbar
        commands = []

        def updateAvailability(slot, name):
            for index, tool in enumerate(self.toolbar.tools):
                item = tool.left if slot == ToolbarSlot.LEFT else tool.right
                if item.isSkill():
                    skill = item.skill
                    level = skill.level
                    isSkillAvailable = level > 0 and (not skill.isAvailable or skill.isAvailable(self.player, level, index, slot, tool))
                    if isSkillAvailable != self.skillAvailability[slot][index]:
                        commands.append("var tool = App.toolbarController.content[{}];".format(index))
                        commands.append("tool.{}.set('isSkillAvailable', {});".format(name, boolStr(isSkillAvailable)))
                        self.skillAvailability[slot][index] = isSkillAvailable

        updateAvailability(ToolbarSlot.LEFT, 'left')
        updateAvailability(ToolbarSlot.RIGHT, 'right')
        if commands:
            self.view.execute(commands)

    def updateSelected(self, previousItem=None):
        #TODO: Need to support both slots here
        slot = ToolbarSlot.LEFT
        self.selected = self.toolbar.get(self.toolbar.selected, slot)
        self.player.entity.playerState.activeItem = self.selected
        #Send active item change to the server
        packet = DataStream(Message.ITEM_ACTIVE_TOOLBAR)
        packet.writeUint8(self.selectedSlot)
        packet.writeUint16(self.selectedIndex)
        packet.writeUint8(slot)
        packet.writeUint16(self.toolbar.selected)
        if previousItem is not None:
            packet.writeBool(True)
            previousItem.pack(packet, game.content)
        else:
            packet.writeBool(False)
        game.network.getClient().send(packet)
        # Update selected slot and index
        self.selectedSlot = slot
        self.selectedIndex = self.toolbar.selected

    def onPopulate(self, view, args):
        #Populate the toolbar with the tools
        commands = []
        #First populate the character vitals
        stats = self.player.entity.stats
        hp = stats.HP
        sp = stats.SP
        commands.append("App.vitals.hp.set('value', {});".format(hp.get()))
        commands.append("App.vitals.hp.set('max', {});".format(hp.getMax()))
        commands.append("App.vitals.sp.set('value', {});".format(sp.get()))
        commands.append("App.vitals.sp.set('max', {});".format(sp.getMax()))
        breath = self.player.entity.breath
        commands.append("App.vitals.breath.set('value', {});".format(breath.current.get()))
        commands.append("App.vitals.breath.set('max', {});".format(breath.capacity.get()))
        #Next populate the inventory with the number of tools in the bag
        commands.append("for (var i = 0; i < {}; ++i) {{".format(len(self.toolbar.tools)))
        commands[-1] += "App.toolbarController.pushObject(App.Tool.create()); }"
        #Next go through and update all of the tools in the bag
        for index, tool in enumerate(self.toolbar.tools):
            commands.extend(self.updateTool(index, tool))
        effectsComponent = self.player.entity.effects
        for effect in effectsComponent.getEffects():
            effect = effect.data()
            cooldown = effect.timer.remaining()
            createEffect = "App.Effect.create({{name: '{}', icon: 'asset://crea/{}', cooldown: {}, remaining: {}}})".format(effect.name, effect.effect.ICON, cooldown, cooldown)
            self.view.execute("App.effectsController.pushObject({});".format(createEffect))
        commands.append("App.vitals.set('currency', {});".format(self.player.entity.playerState.currency.get()))

        # Populate the system buttons
        for system, hotkey in [("Inventory", "I"), ("Equipment", "E"), ("Talent", "T"), ("Craft", "C"), ("Research", "R")]:
            tooltip = "{} - ({})".format(Locale.getEscaped(system), hotkey)
            icon = "../images/hud_{}.png".format(system.lower())
            commands.append("App.systems.pushObject(App.SystemButton.create({{ key: '{}', tooltip: '{}', icon: '{}' }}));".format(system.lower(), tooltip, icon))
        # Set research button availability
        isResearchAvailable = game.research.isResearchAvailable(self.player)
        commands.append("App.systems.content[4].set('isAvailable', {});".format(boolStr(isResearchAvailable)))

        view.execute(commands)
        self.handleSelectedChange(0)

    def onClickedSlot(self, view, args):
        index = args[1].toInteger()
        slot = ToolbarSlot.LEFT if args[2].toString() == 'left' else ToolbarSlot.RIGHT
        Log.info("Clicked on toolbar slot: {}".format(index))

        clearSlot = args[3].toBoolean()

        #TODO: Play sounds depending on clearing or setting
        packet = DataStream(Message.ITEM_SWAP_WITH_GRAB)
        packet.writeUint8(GrabbedItemLocation.TOOLBAR)
        packet.writeUint16(index)
        packet.writeUint8(slot)
        packet.writeBool(clearSlot)
        game.network.getClient().send(packet)

    def onToggleSystem(self, view, args):
        system = args[1].toString()
        gameplay = core.ui.gameplay.GameplayUi.get()
        if system == "inventory":
            gameplay.inventory.toggleVisibility()
        elif system == "craft":
            gameplay.craft.toggleVisibility()
        elif system == "talent":
            gameplay.talent.toggleVisibility()
        elif system == "equipment":
            gameplay.equipment.toggleVisibility()
        elif system == "research":
            gameplay.toggleResearch()

    def updateTool(self, index, tool):
        #Handle an tool changing (either item or just quantity)
        commands = []
        #Construct the javascript code that we'll execute in UI context
        commands.append("var tempTool = App.toolbarController.content[{}];".format(index))
        commands.append("tempTool.beginPropertyChanges();")
        commands.append("tempTool.set('index', {});".format(index))

        def updateItem(item, slot):
            slotName = "left" if slot == ToolbarSlot.LEFT else "right"
            commands.append("tempTool.{}.beginPropertyChanges();".format(slotName))
            commands.append("tempTool.{}.set('quantity', {});".format(slotName, item.quantity))
            if item:
                if item.isItem():
                    commands.append("tempTool.{}.set('id', {});".format(slotName, item.entity.id))
                    commands.append("tempTool.{}.set('content', {});".format(slotName, item.content.id))
                    commands.append("tempTool.{}.set('talentName', '');".format(slotName))
                    commands.append("tempTool.{}.set('skillName', '');".format(slotName))
                else:
                    commands.append("tempTool.{}.set('id', 0);".format(slotName))
                    commands.append("tempTool.{}.set('content', 0);".format(slotName))
                    commands.append("tempTool.{}.set('talentName', '{}');".format(slotName, item.talent.name))
                    commands.append("tempTool.{}.set('skillName', '{}');".format(slotName, item.skill.name))
                commands.append("tempTool.{}.set('icon', 'asset://crea/{}');".format(slotName, item.getIconPath()))
                cooldown = item.getCooldown()
                remaining = self.combat.cooldowns[cooldown.key].remaining() if self.combat.hasCooldown(item.getCooldown()) else 0
                commands.append("tempTool.{}.setCooldown({}, {});".format(slotName, remaining, cooldown.duration))
            else:
                commands.append("tempTool.{}.set('id', 0);".format(slotName))
                commands.append("tempTool.{}.set('content', 0);".format(slotName))
                commands.append("tempTool.{}.set('talentName', '');".format(slotName))
                commands.append("tempTool.{}.set('skillName', '');".format(slotName))
                commands.append("tempTool.{}.set('icon', '');".format(slotName))
                commands.append("tempTool.{}.setCooldown(0, 0);".format(slotName))
            isSkillAvailable = False
            if item.isSkill():
                skill = item.skill
                level = skill.level
                isSkillAvailable = level > 0 and (not skill.isAvailable or skill.isAvailable(self.player, level, index, slot, tool))
            commands.append("tempTool.{}.set('isSkillAvailable', {});".format(slotName, boolStr(isSkillAvailable)))
            commands.append("tempTool.{}.endPropertyChanges();".format(slotName))
        updateItem(tool.left, ToolbarSlot.LEFT)
        updateItem(tool.right, ToolbarSlot.RIGHT)
        commands.append("tempTool.endPropertyChanges();")
        return commands

    def handleToolChange(self, index, slot, tool, previousItem, currentItem):
        #Propagate the change to the UI
        self.view.execute(self.updateTool(index, tool))
        if self.toolbar.selected == index:
            self.updateSelected(previousItem=previousItem)

    def handleSelectedChange(self, index):
        #Update which item is currently selected
        commands = []
        tool = self.toolbar.get(index)
        commands.append("var tool = App.toolbarController.content[{}];".format(index))
        commands.append("tool.beginPropertyChanges();")

        def updateCooldown(item, slot):
            slotName = "left" if slot == ToolbarSlot.LEFT else "right"
            if item:
                cooldown = item.getCooldown()
                remaining = self.combat.cooldowns[cooldown.key].remaining() if self.combat.hasCooldown(item.getCooldown()) else 0
                commands.append("tool.{}.setCooldown({}, {});".format(slotName, remaining, cooldown.duration))
            else:
                commands.append("tool.{}.setCooldown(0, 0);".format(slotName))

        updateCooldown(tool.left, ToolbarSlot.LEFT)
        updateCooldown(tool.right, ToolbarSlot.RIGHT)
        commands.append("tool.endPropertyChanges();")
        commands.append("App.toolbar.select({});".format(index))
        self.view.execute(commands)
        self.updateSelected()

    def handleStatChange(self, stat, oldValue):
        self.view.execute("App.vitals.{}.set('value', {});".format(stat.name.lower(), stat.get()))

    def handleStatMaxChange(self, stat, oldValue):
        self.view.execute("App.vitals.{}.set('max', {});".format(stat.name.lower(), stat.getMax()))

    def handleCurrentBreathChange(self, previous, current):
        self.view.execute("App.vitals.breath.set('value', {});".format(current))

    def handleCapacityBreathChange(self, previous, current):
        self.view.execute("App.vitals.breath.set('max', {});".format(current))

    def handleCurrencyChange(self, previous, current):
        self.view.execute("App.vitals.set('currency', {});".format(current))

    def handleSetCooldown(self, key, time):
        commands = []
        for index, tool in enumerate(self.toolbar.tools):
            for slot, item in [("left", tool.left), ("right", tool.right)]:
                if item and item.getCooldown().key == key:
                    commands.append("var tempTool = App.toolbarController.content[{}];".format(index))
                    commands.append("tempTool.{}.setCooldown({}, {});".format(slot, time, time))
        self.view.execute(commands)

    def handleAddEffect(self, name, level, duration, effect):
        cooldown = effect.timer.remaining()
        createEffect = "App.Effect.create({{name: '{}', icon: 'asset://crea/{}', cooldown: {}, remaining: {}}})".format(name, effect.effect.ICON, cooldown, cooldown)
        self.view.execute("App.effectsController.pushObject({});".format(createEffect))

    def handleRemoveEffect(self, name):
        commands = []
        commands.append("var effect = App.effectsController.findProperty('name', '{}');".format(name))
        commands.append("App.effectsController.removeObject(effect);")
        self.view.execute(commands)

    def handleCreatePlacement(self, entity):
        if self.player.entity.getPosition().getDistance(entity.getPosition(), entity.loopWidth) <= self.player.entity.stats.reach.get():
            if entity.has('surface'):
                self.updateAvailableServices()

    def handleDestroyPlacement(self, entity, dropped):
        #Treat this the same as when an entity is created (it just updates the available services and inventories)
        self.handleCreatePlacement(entity)

    def handlePlayerMove(self, component, previous, current):
        self.movementDelta.x += current.x - previous.x
        self.movementDelta.y += current.y - previous.y
        if abs(self.movementDelta.x) > self.DELTA_THRESHOLD or abs(self.movementDelta.y) > self.DELTA_THRESHOLD:
            self.movementDelta.x = 0
            self.movementDelta.y = 0
            self.updateAvailableServices()

    def handleRealmChange(self, player, previousRealm, realm):
        if previousRealm:
            realm.placement.onCreate.remove(self.handleCreatePlacement)
            realm.placement.onDestroy.remove(self.handleDestroyPlacement)
        realm.placement.onCreate.listen(self.handleCreatePlacement)
        realm.placement.onDestroy.listen(self.handleDestroyPlacement)
        self.updateAvailableServices()

    def updateAvailableServices(self):
        #Checks to see if a nearby service has changed. If so then update availability.
        isResearchAvailable = game.research.isResearchAvailable(self.player)
        self.view.execute("App.systems.content[4].set('isAvailable', {});".format(boolStr(isResearchAvailable)))

    def onResize(self, width, height):
        if width >= 1280:
            position = Vector(377, height - 120)
        else:
            position = Vector(32, height - 120)
        self.view.setPosition(position)
