import os

from core.terraform.terraform import RealmGenerator
from core.ui import boolStr
from core.ui.option import OptionUi

from siege import CustomizationInfo, Entity, game, Locale, Player, States
from siege.graphic import Color, Renderable, TextureCache
from siege.io import DataStream, File
from siege.log import Log
from siege.network import Message, MessageHandler, NetworkManager
from siege.particle import ParticleEmitter
from siege.sfml import PrimitiveType, Sprite, Vertex, VertexArray
from siege.util import hours, ObjectMap, PixelRect, Random, RangeColor, RangeInt, RangeVector, seconds, Timer, Vector, Vector3
from siege.world import World
from siege.world.realm import RealmInfo, RealmInfoList

import siege.subsystem


class WorldInfo(object):
    def __init__(self):
        self.name = ''
        self.path = ''
        self.uid = 0
        self.realms = RealmInfoList()


class MenuWorldBackground(Renderable):
    def __init__(self):
        Renderable.__init__(self)
        self.logo = Sprite(TextureCache.get("images/crea_logo.png").get())
        self.world = Sprite(TextureCache.get("images/crea_world.png").get())
        windowSize = game.render.getWindowSize()
        worldRect = self.world.getTextureRect()
        self.world.setOrigin(worldRect.width / 2, worldRect.height / 2)
        self.background = VertexArray(PrimitiveType.Quads)
        windowSize = game.render.getWindowSize()
        self.onResize(windowSize.x, windowSize.y)
        game.render.onResize.listen(self.onResize)

    def update(self, frameTime):
        self.emitter.update(frameTime)
        rotation = self.world.getRotation()
        rotation += 0.05
        if rotation > 360.0:
            rotation -= 360.0
        self.world.setRotation(rotation)

    def render(self, target):
        target.draw(self.background)
        self.emitter.render(target)
        target.draw(self.world)
        target.draw(self.logo)

    def onResize(self, width, height):
        self.setViewCenter(Vector(width / 2.0, height / 2.0))
        self.background.clear()
        topColor = Color(38, 69, 87)
        bottomColor = Color(56, 99, 108)
        self.background.append(Vertex(Vector(0, 0).toSfml(), topColor))
        self.background.append(Vertex(Vector(width, 0).toSfml(), topColor))
        self.background.append(Vertex(Vector(width, height).toSfml(), bottomColor))
        self.background.append(Vertex(Vector(0, height).toSfml(), bottomColor))
        # Create emitter here to use window size
        position = Vector(width / 2, height - 128)
        self.emitter = ParticleEmitter(0, 0, position, hours(99), 500)
        self.emitter.amount = Random.get(3, 6)
        self.emitter.particlePosition = RangeVector(Vector(-width / 2, -3 * height / 4), Vector(width / 2, 0))
        self.emitter.setTexture(TextureCache.getAbsolute("mods/core/monster/death_particle.png"))
        self.emitter.velocityStart = RangeVector(Vector(-0.1, -0.5), Vector(0.1, -0.2))
        self.emitter.colorStart = RangeColor(Color(255, 255, 255), Color(255, 255, 255))
        self.emitter.colorEnd = RangeColor(Color(255, 255, 255, 0), Color(255, 255, 255, 0))
        self.emitter.scaleChange = RangeVector(Vector(-1, -1), Vector(-0.3, -0.3))
        self.emitter.particleLife = RangeInt(seconds(8), seconds(10))
        self.emitter.scaleStart = RangeVector(Vector(2, 2), Vector(2, 2))
        #Update World and Logo positions
        self.world.setPosition(Vector(width / 2, height).toSfml())
        logoRect = self.logo.getTextureRect()
        self.logo.setPosition(Vector((width / 2) - (logoRect.width / 2), height - logoRect.height - 32).toSfml())


class MainMenu(Renderable):
    QUICK_DATA_PATH = 'recent.dat'

    def __init__(self):
        Renderable.__init__(self)
        self.characterPath = ''
        self.worldPath = ''
        self.joinMenu = None
        self.characterCreationMenu = None
        self.characterSelectionMenu = None
        self.worldSelectionMenu = None
        self.worldCreationMenu = None
        self.worldInfo = WorldInfo()
        #Stack of which pages have been loaded. Used for going back.
        self.page = 'html/main.html'
        self.pages = [self.page]
        self.populators = {}
        self.loadQuickData()
        self.background = MenuWorldBackground()

    def register(self):
        self.uiView = game.gui.createView('main', PixelRect(0, 0, 400, 300))
        windowSize = game.render.getWindowSize()
        self.onResize(windowSize.x, windowSize.y)
        game.render.onResize.listen(self.onResize)

        self.uiView.register('playSound', self.onPlaySound)
        self.uiView.register('populate', self.onPopulate)
        self.uiView.register('load', self.onLoad)
        self.uiView.register('back', self.onBack)
        self.uiView.register('selectWorld', self.onSelectWorld)
        self.uiView.register('selectCharacter', self.onSelectCharacter)
        self.uiView.register('quickLoad', self.onQuickLoad)

        self.uiView.register('applyClick', OptionUi.onApplyClick)
        self.uiView.register('okClick', self.onSettingsOk)
        self.uiView.register('cancelClick', self.onBack)
        self.uiView.register('exit', self.onExit)

        self.worldSelectionMenu = WorldSelectionMenu(self.uiView)
        self.worldCreationMenu = WorldCreationMenu(self.uiView, self.onBack)
        self.hostMenu = HostMenu(self.uiView, self.handleWorldInfo)
        self.joinMenu = JoinMenu(self.uiView, self.loadPage, self.handleWorldInfo)
        self.characterCreationMenu = CharacterCreationMenu(self.uiView)
        self.characterSelectionMenu = CharacterSelectionMenu(self.uiView)
        game.render.addRenderable(self.background, 1000)
        game.render.addRenderable(self, 10001)

        self.populators['main'] = self.populate
        self.populators['character_creation'] = self.characterCreationMenu.onPopulate
        self.populators['character_selection'] = self.characterSelectionMenu.onPopulate
        self.populators['world_creation'] = self.worldCreationMenu.onPopulate
        self.populators['world_selection'] = self.worldSelectionMenu.onPopulate
        self.populators['host'] = self.hostMenu.onPopulate
        self.populators['join'] = self.joinMenu.onPopulate
        self.populators['menu_settings'] = OptionUi.onPopulate

        self.uiView.load(self.page)
        self.uiView.show()
        game.gui.focusView(self.uiView)

    def update(self, frameTime):
        self.worldCreationMenu.update()
        self.joinMenu.update(frameTime)
        self.background.update(frameTime)

    def hide(self):
        game.gui.destroyView('main')

    def render(self, window):
        if self.uiView.isLoaded:
            if self.characterCreationMenu.isActive:
                self.characterCreationMenu.render(window)
            elif self.characterSelectionMenu.isActive:
                self.characterSelectionMenu.render(window)

    def onResize(self, width, height):
        viewSize = self.uiView.getSize()
        self.uiView.setPosition(Vector((width / 2) - (viewSize.x / 2), (height / 4) - (viewSize.y / 2)))

    def onPlaySound(self, view, args):
        game.audio.play(args[1].toString())

    def onPopulate(self, view, args):
        popKey = os.path.splitext(os.path.basename(self.pages[-1]))[0]
        if popKey in self.populators:
            self.populators[popKey](view, args)

    def populate(self, view, args):
        canQuickPlay = bool(self.worldSelectionMenu.worlds and self.characterSelectionMenu.players)
        view.execute("App.view.set('canQuickPlay', {});".format(boolStr(canQuickPlay)))

    def onLoad(self, view, args):
        self.loadPage(args[1].toString())

    def loadPage(self, page):
        self.pages.append(page)
        if len(self.pages) == 2 and 'world' in page:
            game.network.setupPassthrough()
            game.network.getClient().register(Message.WORLD_INFO, MessageHandler.create(self.handleWorldInfo))
        self.uiView.load(page)

    def onBack(self, view, args):
        if len(self.pages) == 1:
            # Disallow moving back from main page
            return
        self.pages.pop()
        if len(self.pages) == 1 or 'join' in self.pages[-1] or 'host' in self.pages[-1]:
            game.network.reset()
        view.load(self.pages[-1])

    def loadQuickData(self):
        if os.path.exists(self.QUICK_DATA_PATH):
            stream = DataStream()
            File.load(self.QUICK_DATA_PATH, stream)
            self.worldPath = stream.readString()
            self.characterPath = stream.readString()

    def saveQuickData(self):
        stream = DataStream()
        stream.writeString(self.worldInfo.path)
        stream.writeString(self.characterPath)
        File.save(self.QUICK_DATA_PATH, stream)

    def onQuickLoad(self, view, args):
        #Select the first world and first character immediately
        if self.worldSelectionMenu.worlds and self.characterSelectionMenu.players:
            self.selectWorld(self.worldSelectionMenu.worlds.keys()[0])
            #Just use the first player...
            for name, player in self.characterSelectionMenu.players.iteritems():
                game.network.setupPassthrough()
                game.network.getClient().register(Message.WORLD_INFO, MessageHandler.create(self.handleWorldInfo))
                self.selectCharacter(player.path)
                break

    def onSelectWorld(self, view, args):
        self.selectWorld(args[1].toString())

    def onSelectCharacter(self, view, args):
        character = args[1].toObject()
        self.selectCharacter(character.getProperty('path').toString())
        self.characterSelectionMenu.isActive = False

    def onExit(self, view, args):
        game.exit()

    def selectWorld(self, worldPath):
        self.worldInfo.path = worldPath

    def selectCharacter(self, characterPath):
        self.characterPath = characterPath
        # self.saveQuickData()
        if NetworkManager.isHost():
            self.enterWorld()
            self.sendCharacter()
        else:
            self.sendCharacter()
            self.enterWorld()

    def handleWorldInfo(self, client, packet):
        Log.info("Received World Information")
        game.content.unpack(packet)
        if not game.content.validate():
            Log.warn("Failed to verify package contents!")
            #TODO: Send a failed to validate content error to the server
            return
        self.worldInfo.name = packet.readString()
        self.worldInfo.uid = packet.readUint32()
        realmCount = packet.readUint32()
        for _ in xrange(realmCount):
            realmInfo = RealmInfo()
            realmInfo.unpack(packet)
            self.worldInfo.realms.append(realmInfo)
        Locale.setLocale("en", game.content.getPackages().getOrdered())
        siege.subsystem.registration(game)

    def sendCharacter(self):
        response = DataStream(Message.CHARACTER_INFO)
        stream = DataStream()
        File.load(self.characterPath, stream)
        response.append(stream)
        response.writePixelVector(game.render.getWindowSize())
        game.network.getClient().send(response)

    def enterWorld(self):
        #Have to clear renderables so that GUI can be rendered on top in gameplay
        game.render.clearRenderables()
        game.entity.initialize()
        if game.network.isHost():
            #Load the world from the provided path
            World.load(game, self.worldInfo.path)
        else:
            #Join an existing world. Details will be provided.
            World.join(game, self.worldInfo.uid, self.worldInfo.name, self.worldInfo.realms)
        game.scene.transition(States.GAMEPLAY)

    def onSettingsOk(self, view, args):
        OptionUi.onApplyClick(view, args)
        self.onBack(view, args)


class WorldSelectionMenu(object):
    def __init__(self, view):
        self.worlds = {}
        self.findWorlds()

    def findWorlds(self):
        worldsDir = os.path.join(os.getcwd(), "worlds")
        if os.path.exists(worldsDir):
            for world in [f for f in os.listdir(worldsDir) if os.path.isdir(os.path.join(worldsDir, f))]:
                worldPath = os.path.join(worldsDir, world).replace("\\", "/")
                if worldPath in self.worlds:
                    continue
                worldInfo = os.path.join(worldPath, 'info')
                if os.path.exists(worldInfo):
                    infoStream = DataStream()
                    File.load(worldInfo, infoStream)
                    #Append world information to worlds list
                    self.worlds[worldPath] = infoStream.readString()

    def onPopulate(self, view, args):
        #Iterate through the world save directory and populate worlds array
        self.findWorlds()
        if self.worlds:
            commands = ['var worlds = [];']
            for worldPath, name in self.worlds.iteritems():
                commands.append("worlds.push(App.World.create({name: '%s', path: '%s'}));" % (name, worldPath))
            commands.append("App.worldsController.pushObjects(worlds);")
            view.execute(commands)


class WorldCreationMenu(object):
    def __init__(self, view, backCallback):
        self.view = view
        self.backCallback = backCallback
        self.generator = None
        self.options = []
        self.status = ''

        #Register view handlers
        view.register('selectWorldOption', self.onSelectWorldOption)

    def onPopulate(self, view, args):
        #Go through and populate the terraforming options from all mods
        self.options = []
        game.events["terraform_options"].invoke(self.options)
        commands = ['var terraformOptions = [];']
        for index, option in enumerate(self.options):
            commands.append("terraformOptions.push(App.Option.create({name: '%s', index: '%s'}));" % (option.name, index))
        commands.append("App.optionsController.pushObjects(terraformOptions);")
        view.execute(commands)

    def onSelectWorldOption(self, view, args):
        #TODO: Consider reporting back to the UI that world already exists?
        if self.generator:
            Log.info("Attempting to start world generation while it is already running.")
            return
        name = args[1].toString().strip()
        option = args[2].toObject()
        index = option.getProperty('index').toInteger()
        self.startWorldGen(name, self.options[index])

    def update(self):
        if self.generator:
            if self.generator.resume():
                #Finished world generation
                self.finishWorldGen()
            elif self.status != self.generator.status:
                self.status = self.generator.status
                self.view.execute("App.nameValue.set('status', '{}');".format(Locale.getEscaped(self.status)))

    def startWorldGen(self, name, option):
        self.world = World.create(game, name, option.size)
        self.generator = RealmGenerator(self.world, self.world.realm)
        self.generator.start()

    def finishWorldGen(self):
        if not os.path.exists(self.world.path):
            os.makedirs(self.world.path)
            #Save the World info file
            infoStream = DataStream()
            infoStream.writeString(self.world.name)
            infoStream.writeUint32(self.world.uid)
            File.save(os.path.join(self.world.path, 'info'), infoStream)

            #Save the content file
            contentStream = DataStream()
            game.content.write(contentStream)
            File.save(os.path.join(self.world.path, 'content'), contentStream)
            #Save the world!
            self.world.save()

        #Reset some things...
        World.reset()
        self.generator = None
        self.backCallback(self.view, None)


class CharacterSelectionPlayer(object):
    def __init__(self, path):
        self.player = Player()
        self.path = path
        self.isVisible = False


class CharacterSelectionMenu(object):
    def __init__(self, view):
        #Create a mapping between character name and tuple(Player, isVisible)
        self.players = {}
        self.isActive = False

        #Register UI callbacks
        view.register('changeCharacterVisibility', self.onVisibilityChange)
        view.register('onExitCharacterSelection', self.onExit)
        self.findPlayers()

    def findPlayers(self):
        charactersDir = os.path.join(os.getcwd(), "characters")
        if os.path.exists(charactersDir):
            for character in [f for f in os.listdir(charactersDir) if os.path.isfile(os.path.join(charactersDir, f)) and f.endswith('.cpf')]:
                characterPath = os.path.join(charactersDir, character).replace("\\", "/")
                #Check to see if this character already exists
                alreadyHas = False
                for player in self.players.values():
                    if characterPath == player.path:
                        alreadyHas = True
                if alreadyHas:
                    continue
                player = CharacterSelectionPlayer(characterPath)
                player.player.entity = Entity("", 0, game.content.get('player'))
                player.player.loadInfo(game.content, game.entity, characterPath)
                player.player.entity.animation.play('idle')
                self.players[player.player.info.name] = player

    def onPopulate(self, view, args):
        self.isActive = True
        self.findPlayers()
        #Iterate through the world save directory and populate worlds array
        commands = []
        for player in self.players.values():
            #Append character information to characters list
            commands.append("var tempCharacter = App.Character.create();")
            commands.append("tempCharacter.name = '%s';" % (player.player.info.name,))
            commands.append("tempCharacter.path = '%s';" % (player.path,))
            commands.append("App.charactersController.pushObject(tempCharacter);")
        if commands:
            view.execute(commands)

    def onVisibilityChange(self, view, args):
        name = args[1].toString()
        isVisible = args[2].toBoolean()
        if isVisible:
            x = args[2].toInteger()
            y = args[3].toInteger()
            self.players[name].player.entity.setPosition(x, y)
        self.players[name].isVisible = isVisible

    def onExit(self, view, args):
        self.isActive = False

    def render(self, window):
        for player in self.players.values():
            if player.isVisible:
                player.player.entity.render.render(window)


class CustomizationGroup(object):
    def __init__(self, customization):
        #The identifier of the customization type
        self.identifier = customization.group
        self.mapping = customization.mapping
        #List of customization entities
        self.entities = []
        #If this customization group supports coloring
        self.supportsColor = customization.supportsColor
        #The current color for this customization
        self.hsv = Vector3(360.0, 1.0, 1.0)
        #If this customization is optional or not
        self.isOptional = customization.isOptional
        #The index of the currently selected customization
        self.index = 0

    def isHidden(self):
        return self.isOptional and self.index == 0

    def getCurrent(self):
        return self.entities[self.index - (1 if self.isOptional else 0)]

    def getMax(self):
        return len(self.entities) + (1 if self.isOptional else 0)


class CharacterBody(object):
    def __init__(self, identifier, entity):
        #The identifier for the body type
        self.identifier = identifier
        #The entity for this body type
        self.entity = entity
        #A mapping of customization types to the associated customization group
        self.customizations = {}
        for customization in entity.body.customizations:
            self.customizations[customization.group] = CustomizationGroup(customization)

    def add(self, entity, component):
        #component is a CustomizationComponent
        if component.group in self.customizations:
            #Only add the component if it is supported by this body type
            self.customizations[component.group].entities.append(entity)


class CharacterCreationMenu(object):
    DEFAULT_BODY_IDENTIFIER = 'hm'

    def __init__(self, view):
        #Character creation is based off of body types
        #A body type can be something like 'human male' or 'robot'
        #Each body type has a list of customization types that it can use such as 'Hair' or 'Right Arm'
        #The actual customizations (such as a type of hair) have a type and a list of compatable body types
        #This makes it possible to have a hair customization that is only for females
        #The body types are treated as a special type of customization
        #When the body type changes it changes which customizations are available
        #A customization type can support coloring and
        #if it does then the individual customizations of the type can choose to support it or not

        #Register view callbacks
        view.register('nextCustomization', self.onNextCustomization)
        view.register('previousCustomization', self.onPreviousCustomization)
        view.register('colorChange', self.onChangeColor)
        view.register('setCharacterPosition', self.onSetCharacterPosition)
        view.register('confirmCharacterCreate', self.onConfirmation)
        view.register('exitCharacterCreation', self.onExit)

        #Setup variables
        self.isActive = False
        #Mapping of body identifier to a CharacterBody
        self.bodies = {}
        #List of the body identifier items so we can index them
        self.bodyList = []
        #The currently selected body index
        self.bodyIndex = 0
        #The color of the body
        self.bodyColor = Vector3(360.0, 1.0, 1.0)
        #Character entity
        self.character = Entity('', 0, game.content.get('player'))

        #Populate the bodies and customization maps
        for body in game.body.getBodies():
            try:
                identifier = body.data().identifier
                self.bodies[identifier] = CharacterBody(identifier, body.key())
                self.bodyList.append(identifier)
            except AttributeError:
                pass

        #Go through all of the customizations and add them to the supported body types
        for customization in game.customization.getCustomizations():
            try:
                component = customization.data()
                if component.bodies:
                    for bodyIdentifier in component.bodies:
                        #Ensure the body type exists
                        if bodyIdentifier in self.bodies:
                            self.bodies[bodyIdentifier].add(customization.key(), component)
                else:
                    # If no bodies are set then use all of them
                    for body in self.bodies.values():
                        body.add(customization.key(), component)
            except AttributeError:
                pass

        #Attempt to set the body type to the default
        if self.DEFAULT_BODY_IDENTIFIER in self.bodies:
            for index in xrange(len(self.bodyList)):
                if self.bodyList[index] == self.DEFAULT_BODY_IDENTIFIER:
                    self.bodyIndex = index
                    break
        elif not self.bodyList:
                Log.warn("No available bodies to choose from.")

    def getCurrentBody(self):
        #Gets the currently selected CharacterBody
        return self.bodies[self.bodyList[self.bodyIndex]]

    def canColor(self, customizationIdentifier):
        #Given a customization type check if it can be colored based off of
        #If the group supports color and if the current customization of the group supports color
        group = self.getCurrentBody().customizations[customizationIdentifier]
        return group.supportsColor and group.getCurrent().supportsColor

    def onPopulate(self, view, args):
        self.isActive = True
        self.changeBody(view)

    def onSetCharacterPosition(self, view, args):
        x = args[1].toInteger()
        y = args[2].toInteger()
        viewPosition = view.getPosition()
        self.character.setPosition(Vector(x + viewPosition.x, y + viewPosition.y))

    def onSelectCustomization(self, view, args):
        pass

    def onNextCustomization(self, view, args):
        self.changeCustomization(view, args[1].toString(), 1)

    def onPreviousCustomization(self, view, args):
        self.changeCustomization(view, args[1].toString(), -1)

    def onConfirmation(self, view, args):
        player = Player()
        name = args[1].toString()
        player.info.name = name
        body = self.getCurrentBody()
        player.info.body = body.entity.content.getName()
        for identifier, group in body.customizations.iteritems():
            customizationInfo = CustomizationInfo()
            customizationInfo.group = identifier
            if group.isHidden():
                customizationInfo.content = ""
            else:
                customizationInfo.content = group.getCurrent().content.getName()
                customizationInfo.hsv = group.hsv
            player.info.customizations.append(customizationInfo)

        #Add in the player's body color as a customization
        customizationInfo = CustomizationInfo()
        customizationInfo.content = "body"
        customizationInfo.hsv = self.bodyColor
        player.info.customizations.append(customizationInfo)
        player.setup(game.content, game.entity, 1)
        game.events["player_created"].invoke(player)
        player.save(0, game.content, ObjectMap())
        game.entity.destroy(player.entity)
        self.onExit(view, args)

    def onExit(self, view, args):
        #If args are ever used then note that they can come from onConfirmation
        self.isActive = False

    def changeBody(self, view):
        body = self.getCurrentBody()
        #Save current character position if exists
        position = None
        try:
            position = self.character.getPosition()
        except AttributeError:
            pass
        #Update character render
        bodyComponent = body.entity.body
        for component in bodyComponent.components:
            componentType = component.getType()
            if componentType == "modularRender" or componentType == "render":
                game.render.create(self.character, componentType, component)
            elif componentType == "animation":
                game.animation.create(self.character, componentType, component)
        self.character.animation.play('idle')
        self.character.render.changeSprites(bodyComponent.substitutions)
        if position:
            self.character.setPosition(position)

        #Populate the available customizations according to the body
        #Always have the body customization
        commands = ["var customizations = [];"]
        commands.append("var bodyCustomization = App.Customization.create();")
        commands.append("bodyCustomization.identifier = 'Body';")
        commands.append("bodyCustomization.canColor = true;")
        commands.append("bodyCustomization.count = %d;" % len(self.bodyList))
        #Default to having the body customization selected (body only changes if already selected)
        commands.append("bodyCustomization.isSelected = true;")
        commands.append("customizations.push(bodyCustomization);")
        commands.append("App.customizationView.selectedItem = bodyCustomization;")

        for customization in body.entity.body.customizations:
            group = body.customizations[customization.group]
            if not group.entities:
                #Skip over customization types that do not have any customizations
                continue
            self.updateStyle(group)
            if not group.isHidden():
                self.character.render.changeSprites(group.getCurrent().customization.substitutions)

            commands.append("var customizationGroup = App.Customization.create();")
            commands.append("customizationGroup.identifier = '%s';" % (group.identifier,))
            commands.append("customizationGroup.canColor = %s;" % (boolStr(group.supportsColor)))
            count = len(group.entities) + (1 if group.isOptional else 0)
            commands.append("customizationGroup.count = %d;" % count)
            commands.append("customizations.push(customizationGroup);")
        commands.append("App.customizationsController.clear();")
        commands.append("App.customizationsController.pushObjects(customizations);")
        view.execute(commands)

    def changeCustomization(self, view, customizationIdentifier, direction):
        def loopIndex(index, maxValue):
            index += direction
            if index == maxValue:
                index = 0
            elif index == -1:
                index = maxValue - 1
            return index

        #Special case for body customization
        if customizationIdentifier == 'Body':
            #Can't change body if there is less than 2
            if self.bodyList < 2:
                return
            self.bodyIndex = loopIndex(self.bodyIndex, len(self.bodyList))
            self.changeBody(view)
        else:
            group = self.getCurrentBody().customizations[customizationIdentifier]
            maxIndex = group.getMax()
            if maxIndex > 1:
                group.index = loopIndex(group.index, maxIndex)
            self.updateStyle(group)
        #TODO: Update the available of coloring based on customization

    def onChangeColor(self, view, args):
        customization = args[1].toObject()
        identifier = customization.getProperty('identifier').toString()

        def updateColor(hsv):
            hsv.x = float(customization.getProperty('hue').toInteger())
            hsv.y = customization.getProperty('saturation').toInteger() / 100.0
            hsv.z = customization.getProperty('value').toInteger() / 100.0

        if identifier == "Body":
            updateColor(self.bodyColor)
            self.character.render.changeSpriteColor('body', self.bodyColor)
        else:
            group = self.getCurrentBody().customizations[identifier]
            updateColor(group.hsv)
            if not group.isHidden():
                for substitution in group.getCurrent().customization.substitutions:
                    self.character.render.changeSpriteColor(substitution.data().base, group.hsv)

    def updateStyle(self, group):
        if group.isHidden():
            self.character.render.hideSprite(group.mapping)
        else:
            for substitution in group.getCurrent().customization.substitutions:
                self.character.render.changeSprite(substitution.data())

    def render(self, window):
        self.character.render.update(0)
        self.character.render.render(window)


class HostMenu(object):
    def __init__(self, view, worldInfoCallback):
        view.register('submitHost', self.onSubmitHost)
        self.worldInfoCallback = worldInfoCallback

    def getHostPath(self):
        #XXX: Assuming preferences are in same directory (pwd)
        return 'host'

    def onPopulate(self, view, args):
        #Retrieve the host options from preferences
        hostPath = self.getHostPath()
        if os.path.exists(hostPath):
            commands = []
            try:
                hostStream = DataStream()
                File.load(hostPath, hostStream)
                port = hostStream.readString()
                save = hostStream.readBool()
                show = hostStream.readBool()
                password = ''
                if save:
                    password = hostStream.loadString()
                commands.append('App.options.port = {};'.format(port))
                commands.append('App.options.password = {};'.format(password))
                commands.append('App.options.save = {};'.format(boolStr(save)))
                commands.append('App.options.show = {};'.format(boolStr(show)))
                view.execute(commands)
            except:
                #If we fail to read the file then just don't populate the fields
                Log.error("Couldn't read JoinMenu file.")
                pass

    def onSubmitHost(self, view, args):
        #Retrieve the host options
        options = args[1].toObject()
        port = options.getProperty('port').toString()
        password = options.getProperty('password').toString()
        saveHost = options.getProperty('save').toBoolean()
        showHost = options.getProperty('show').toBoolean()

        #Save the options out to the host file
        hostStream = DataStream()
        hostStream.writeString(port)
        hostStream.writeBool(saveHost)
        hostStream.writeBool(showHost)
        if saveHost:
            hostStream.writeString(password)
        File.save(self.getHostPath(), hostStream)
        #Setup the server
        game.network.setupServer(int(port), password)
        game.network.getClient().register(Message.WORLD_INFO, MessageHandler.create(self.worldInfoCallback))


class JoinMenuOption(object):
    def __init__(self):
        self.ip = ''
        self.port = 0
        self.password = ''
        self.save = False
        self.show = False

    def __eq__(self, other):
        return self.ip == other.ip and self.port == other.port

    @staticmethod
    def fromJavascript(obj):
        option = JoinMenuOption()
        option.ip = obj.getProperty('ip').toString()
        option.port = int(obj.getProperty('port').toString())
        option.password = obj.getProperty('password').toString()
        option.save = obj.getProperty('save').toBoolean()
        option.show = obj.getProperty('show').toBoolean()
        option.isFavorite = obj.getProperty('isFavorite').toBoolean()
        return option


class JoinMenu(object):
    MAX_OPTION_COUNT = 5

    def __init__(self, view, loadPage, worldInfoCallback):
        self.view = view
        view.register('submitJoin', self.onSubmitJoin)
        self.current = None
        self.loadPage = loadPage
        self.worldInfoCallback = worldInfoCallback
        self.connectionTimer = Timer()

    def update(self, frameTime):
        if not self.connectionTimer.expired():
            self.connectionTimer.update(frameTime)
            if self.connectionTimer.expired():
                commands = []
                commands.append("App.set('attemptingConnection', false);")
                commands.append("App.set('message', 'Failed to connect to the specified server!');")
                self.view.execute(commands)

    def getJoinPath(self):
        return 'servers'

    def getPassword(self):
        return self.current.password if self.current else ''

    def onPopulate(self, view, args):
        #Retrieve the join options from preferences
        joinPath = self.getJoinPath()
        if os.path.exists(joinPath):
            try:
                commands = ['var joinOptions = [];']
                joinStream = DataStream()
                File.load(joinPath, joinStream)
                count = joinStream.readInt8()
                for x in xrange(0, count):
                    option = JoinMenuOption()
                    option.ip = joinStream.readString()
                    option.port = joinStream.readInt32()
                    option.save = joinStream.readBool()
                    option.show = joinStream.readBool()
                    option.isFavorite = joinStream.readBool()
                    if option.save:
                        option.password = joinStream.readString()
                    toString = boolStr
                    options = (option.ip, option.port, option.password, toString(option.show), toString(option.save), toString(option.isFavorite))
                    options = "{ ip: '%s', port: %d, password: '%s', show: %s, save: %s, isFavorite: %s}" % options
                    commands.append("joinOptions.push(App.Option.create(%s));" % (options,))
                commands.append("App.optionsController.pushObjects(joinOptions);")
                view.execute(commands)
            except:
                #If we fail to read the file then just don't populate the recents
                Log.error("Couldn't read HostMenu file.")
                pass

    def onSubmitJoin(self, view, args):
        #Retrieve the join options
        try:
            self.current = JoinMenuOption.fromJavascript(args[1].toObject())
        except ValueError:
            commands = []
            commands.append("App.set('attemptingConnection', false);")
            commands.append("App.set('message', 'Failed to connect to the specified server!');")
            self.view.execute(commands)
            return

        options = []
        optionObjs = args[2].toArray()
        for x in xrange(optionObjs.size()):
            options.append(JoinMenuOption.fromJavascript(optionObjs[x].toObject()))

        # The options are stored first by favorite and then by recent
        # Only MAX_OPTION_COUNT options are displayed at a time and favorites take recent places
        # Always reserve one spot for the most recent if it is not already in the list
        recent = []
        alreadyHas = self.current in options
        if self.current.isFavorite and not alreadyHas:
            recent.append(self.current)
        recent.extend([o for o in options if o.isFavorite and o != self.current])
        if not self.current.isFavorite and not alreadyHas:
            recent.append(self.current)
        for option in options:
            if len(recent) > self.MAX_OPTION_COUNT:
                break
            elif not option.isFavorite and option != self.current:
                recent.append(option)

        #Save recent out to file
        joinStream = DataStream()
        joinStream.writeInt8(len(recent))
        for option in recent:
            joinStream.writeString(option.ip)
            joinStream.writeInt32(option.port)
            joinStream.writeBool(option.save)
            joinStream.writeBool(option.show)
            joinStream.writeBool(option.isFavorite)
            if option.save:
                joinStream.writeString(option.password)
        File.save(self.getJoinPath(), joinStream)

        #Setup the client
        game.network.setupClient(self.current.ip, int(self.current.port), self.getPassword())
        game.network.getClient().register(Message.WORLD_INFO, MessageHandler.create(self.handleWorldInfo))
        self.connectionTimer.reset(seconds(6))

    def handleWorldInfo(self, client, packet):
        self.worldInfoCallback(client, packet)
        self.loadPage("html/character_selection.html")


# class JoinMenu(object):
#     def __init__(self, view, backCallback):
#         self.view = view
#         view.register('submitJoin', self.onSubmitJoin)

#     def onPopulate(self, view, args):
