import re

from core.ui import DialogUi

from siege import game, Locale
from siege.io import DataStream, Input, Keyboard, KeyInput
from siege.log import Log
from siege.network import Message, MessageHandler, NetworkManager
from siege.util import PixelRect, Vector
from siege.world import World


class ChatUi(DialogUi):
    def __init__(self, player):
        self.player = player
        self.history = []
        self.historyPosition = 0
        DialogUi.__init__(self, 'chat', PixelRect(400, 500, 400, 300))
        self.load('html/chat.html')
        self.view.register('populate', self.onPopulate)
        self.view.register('focus', self.onFocus)
        self.view.register('unfocus', self.onUnfocus)
        self.view.register('send', self.onSend)
        if NetworkManager.isHost():
            game.network.getServer().register(Message.CHAT_PLAYER, MessageHandler.create(self.handleChatOnServer))
        if not NetworkManager.isStandalone():
            game.network.getClient().register(Message.CHAT_PLAYER, MessageHandler.create(self.handleChatOnClient))
            game.network.getClient().register(Message.CHAT_SYSTEM, MessageHandler.create(self.handleSystemChat))

        #Keyboard input
        self.inputs = Input()
        self.enterAction = self.inputs.createAction()
        self.enterAction.inputs.append(KeyInput(Keyboard.Return))
        self.escapeAction = self.inputs.createAction()
        self.escapeAction.inputs.append(KeyInput(Keyboard.Escape))
        self.upAction = self.inputs.createAction()
        self.upAction.inputs.append(KeyInput(Keyboard.Up))
        self.downAction = self.inputs.createAction()
        self.downAction.inputs.append(KeyInput(Keyboard.Down))

        #Handle resize and positioning
        windowSize = game.render.getWindowSize()
        self.onResize(windowSize.x, windowSize.y)
        game.render.onResize.listen(self.onResize)
        self.show(focus=False)

    def update(self, frameTime):
        if not self.view.isVisible() or not game.hasFocus:
            return
        self.inputs.update(frameTime)
        if self.view.hasInputFocus:
            message = ""
            position = self.historyPosition
            if self.upAction.justPressed and self.historyPosition > 0:
                position -= 1
                message = self.history[position]
            elif self.downAction.justPressed and self.historyPosition < len(self.history):
                position += 1
                if position < len(self.history):
                    message = self.history[position]
            if self.historyPosition != position:
                self.historyPosition = position
                self.view.execute("App.entered.set('message', '{}');".format(message))
        if self.enterAction.justPressed:
            self.view.execute("handleEnterInput();")
        elif self.escapeAction.justPressed:
            self.view.execute("handleEscapeInput();")

    def onPopulate(self, view, args):
        if game.render.getWindowSize().x < 1280:
            self.view.execute("$('#chat_area').css('height', '100px');")

    def onFocus(self, view, args):
        game.gui.focusView(view)

    def onUnfocus(self, view, args):
        game.gui.unfocusView(view)

    def onSend(self, view, args):
        message = args[1].toString()
        if NetworkManager.isHost() and game.command.isCommand(message):
            game.command.execute(self.player, message)
        else:
            packet = DataStream(Message.CHAT_PLAYER)
            packet.writeString(message)
            game.network.getClient().send(packet)
        #Append to history and cleanup if history is full
        self.history.append(message)
        if len(self.history) > 100:
            del self.history[0]
        self.historyPosition = len(self.history)

    def handleChatOnServer(self, client, packet):
        message = packet.readString()
        if game.command.isCommand(message):
            game.command.execute(World.get().getPlayer(client), message)
            return
        else:
            clientPacket = DataStream(Message.CHAT_PLAYER)
            clientPacket.writeUint32(World.get().getPlayer(client).entity.id)
            clientPacket.writeString(message)
            game.network.getServer().broadcast(clientPacket)

    def handleChatOnClient(self, client, packet):
        sender = game.entity.get(packet.readUint32()).name
        message = packet.readString()
        self.receive('general', sender, message)

    def handleSystemChat(self, client, packet):
        category = packet.readString()
        sender = packet.readString()
        message = packet.readString()
        argCount = packet.readUint8()
        kwargs = {}
        for _ in xrange(argCount):
            key = packet.readString()
            kwargs[key] = Locale.get(packet.readString())
        message = Locale.get(message).format(**kwargs)
        self.receive(category, sender, message)

    def receive(self, category, sender, message):
        Log.info("Chat {}: {}".format(sender, message))
        commands = []
        chatMax = 200
        message = self.parseMessageHTML(message)
        commands.append("if (App.messages.content.length > {}) {{ App.messages.removeAt(0); }}".format(chatMax))
        escapedMessage = message.replace("'", "\\'")
        commands.append("var message = App.Message.create({{category: '{}', sender: '{}', content: '{}'}});".format(category, sender, escapedMessage))
        if sender == 'System':
            #color = "".join(map(chr, (219, 212, 116))).encode('hex')
            color = "dbd474"
            commands.append("message.color = '{}';".format(color))

        commands.append("App.messages.pushObject(message);")
        commands.append("smartScroll();")
        self.view.execute(commands)

    def onResize(self, width, height):
        if width >= 1280:
            position = Vector(32, height - 251)
            self.view.execute("$('#chat_area').css('height', '180px');")
        else:
            position = Vector(530, height - 245)
            self.view.execute("$('#chat_area').css('height', '100px');")
        self.view.setPosition(position)

    def parseMessageHTML(self, originalMessage):
        #ensure the parse syntax is valid
        message = originalMessage
        if message.count("[") != message.count("]"):
            return originalMessage
        message = message.replace("]", "</span>")
        openingBracketIndex = -1

        while True:
            #Validate all the required markup exists
            openingBracketIndex = message.find("[")
            if openingBracketIndex == -1:
                break

            paraSeperaterIndex = message.find(":", openingBracketIndex)
            if paraSeperaterIndex == -1:
                return originalMessage

            paraEndIndex = message.find(" ", paraSeperaterIndex)
            if paraEndIndex == -1:
                return originalMessage

            keyword = message[openingBracketIndex + 1:paraSeperaterIndex - openingBracketIndex + openingBracketIndex]
            parameter = message[paraSeperaterIndex + 1:paraEndIndex]

            if keyword == "col" or keyword == "bcol":
                #See if colour code or CSS class name is being provided

                #Checks for #FFF or #FFFFFF
                re1 = '(#(?:[A-F0-9]){3})(?![0-9A-F])|(#(?:[A-F0-9]){6}(?![0-9A-F]))'
                rg = re.compile(re1, re.IGNORECASE | re.DOTALL)
                match = rg.search(parameter)
                if match:
                    #A colour code was provided
                    styleAttribute = "color:"
                    if(keyword == "bcol"):
                        styleAttribute = "background-" + styleAttribute
                    message = message[:openingBracketIndex] + "<span style='" + styleAttribute + parameter + "'>" + message[paraEndIndex + 1:]
                else:
                    #A colour code was not provided, default to CSS class name
                    message = message[:openingBracketIndex] + "<span class='" + parameter + "'>" + message[paraEndIndex + 1:]

            elif keyword == "style":
                message = message[:openingBracketIndex] + "<span class='" + parameter + "'>" + message[paraEndIndex + 1:]
            else:
                #Keyword not supported
                return originalMessage

        return message
