from pandac.PandaModules import *
from toontown.toonbase.ToonBaseGlobal import *
from toontown.toonbase.ToontownGlobals import GlobalDialogColor
from .DistributedMinigame import *
from direct.fsm import ClassicFSM, State
from direct.fsm import State
from toontown.toonbase import TTLocalizer
from toontown.toonbase import ToontownTimer
from . import TravelGameGlobals
import math
from pandac.PandaModules import rad2Deg
from toontown.toontowngui import TTDialog
from direct.interval.IntervalGlobal import *
from . import VoteResultsPanel
from . import VoteResultsTrolleyPanel
IconDict = {ToontownGlobals.RaceGameId: 'mg_trolley_sign_race',
 ToontownGlobals.CannonGameId: 'mg_trolley_sign_cannon',
 ToontownGlobals.TagGameId: 'mg_trolley_sign_tag',
 ToontownGlobals.PatternGameId: 'mg_trolley_sign_minnie',
 ToontownGlobals.RingGameId: 'mg_trolley_sign_ring',
 ToontownGlobals.MazeGameId: 'mg_trolley_sign_maze',
 ToontownGlobals.TugOfWarGameId: 'mg_trolley_sign_tugawar',
 ToontownGlobals.CatchGameId: 'mg_trolley_sign_catch',
 ToontownGlobals.DivingGameId: 'mg_trolley_sign_dive',
 ToontownGlobals.TargetGameId: 'mg_trolley_sign_umbrella',
 ToontownGlobals.PairingGameId: 'mg_trolley_sign_card',
 ToontownGlobals.VineGameId: 'mg_trolley_sign_vine',
 ToontownGlobals.IceGameId: 'mg_trolley_sign_ice',
 ToontownGlobals.PhotoGameId: 'mg_trolley_sign_photo',
 ToontownGlobals.TwoDGameId: 'mg_trolley_sign_2d',
 ToontownGlobals.CogThiefGameId: 'mg_trolley_sign_theif'}
MinigameNameDict = {ToontownGlobals.RaceGameId: TTLocalizer.RaceGameTitle,
 ToontownGlobals.CannonGameId: TTLocalizer.CannonGameTitle,
 ToontownGlobals.TagGameId: TTLocalizer.TagGameTitle,
 ToontownGlobals.PatternGameId: TTLocalizer.PatternGameTitle,
 ToontownGlobals.RingGameId: TTLocalizer.RingGameTitle,
 ToontownGlobals.MazeGameId: TTLocalizer.MazeGameTitle,
 ToontownGlobals.TugOfWarGameId: TTLocalizer.TugOfWarGameTitle,
 ToontownGlobals.CatchGameId: TTLocalizer.CatchGameTitle,
 ToontownGlobals.DivingGameId: TTLocalizer.DivingGameTitle,
 ToontownGlobals.TargetGameId: TTLocalizer.TargetGameTitle,
 ToontownGlobals.PairingGameId: TTLocalizer.PairingGameTitle,
 ToontownGlobals.VineGameId: TTLocalizer.VineGameTitle,
 ToontownGlobals.TravelGameId: TTLocalizer.TravelGameTitle,
 ToontownGlobals.IceGameId: TTLocalizer.IceGameTitle,
 ToontownGlobals.PhotoGameId: TTLocalizer.PhotoGameTitle,
 ToontownGlobals.TwoDGameId: TTLocalizer.TwoDGameTitle,
 ToontownGlobals.CogThiefGameId: TTLocalizer.CogThiefGameTitle}

def makeLabel(itemName, itemNum, *extraArgs):
    intVersion = int(itemName)
    if intVersion < 0:
        textColor = Vec4(0, 0, 1, 1)
        intVersion = -intVersion
    elif intVersion == 0:
        textColor = Vec4(0, 0, 0, 1)
    else:
        textColor = Vec4(1, 0, 0, 1)
    return DirectLabel(text=str(intVersion), text_fg=textColor, relief=DGG.RIDGE, frameSize=(-1.2,
     1.2,
     -0.225,
     0.8), scale=1.0)


def map3dToAspect2d(node, point):
    p3 = base.cam.getRelativePoint(node, point)
    p2 = Point2()
    if not base.camLens.project(p3, p2):
        return None
    r2d = Point3(p2[0], 0, p2[1])
    a2d = aspect2d.getRelativePoint(render2d, r2d)
    return a2d


def invertTable(table):
    index = {}
    for key in list(table.keys()):
        value = table[key]
        if value not in index:
            index[value] = key

    return index


class DistributedTravelGame(DistributedMinigame):
    notify = directNotify.newCategory('DistributedTravelGame')
    idToNames = MinigameNameDict
    TrolleyMoveDuration = 3
    UseTrolleyResultsPanel = True
    FlyCameraUp = True
    FocusOnTrolleyWhileMovingUp = False

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedTravelGame', [State.State('off', self.enterOff, self.exitOff, ['inputChoice']),
         State.State('inputChoice', self.enterInputChoice, self.exitInputChoice, ['waitServerChoices', 'displayVotes', 'cleanup']),
         State.State('waitServerChoices', self.enterWaitServerChoices, self.exitWaitServerChoices, ['displayVotes', 'cleanup']),
         State.State('displayVotes', self.enterDisplayVotes, self.exitDisplayVotes, ['moveTrolley', 'cleanup']),
         State.State('moveTrolley', self.enterMoveTrolley, self.exitMoveTrolley, ['inputChoice', 'winMovie', 'cleanup']),
         State.State('winMovie', self.enterWinMovie, self.exitWinMovie, ['cleanup']),
         State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.currentVotes = {}
        self.cameraTopView = (100, -20, 280, 0, -89, 0)
        self.timer = None
        self.timerStartTime = None
        self.currentSwitch = 0
        self.destSwitch = 0
        self.minigameLabels = []
        self.minigameIcons = []
        self.bonusLabels = []
        self.trolleyAwaySfx = base.loader.loadSfx('phase_4/audio/sfx/SZ_trolley_away.ogg')
        self.trolleyBellSfx = base.loader.loadSfx('phase_4/audio/sfx/SZ_trolley_bell.ogg')
        self.turntableRotateSfx = base.loader.loadSfx('phase_4/audio/sfx/MG_sfx_travel_game_turntble_rotate_2.ogg')
        self.wonGameSfx = base.loader.loadSfx('phase_4/audio/sfx/MG_sfx_travel_game_bonus.ogg')
        self.lostGameSfx = base.loader.loadSfx('phase_4/audio/sfx/MG_sfx_travel_game_no_bonus_2.ogg')
        self.noWinnerSfx = base.loader.loadSfx('phase_4/audio/sfx/MG_sfx_travel_game_no_bonus.ogg')
        self.boardIndex = 0
        self.avNames = []
        self.disconnectedAvIds = []
        return

    def getTitle(self):
        return TTLocalizer.TravelGameTitle

    def getInstructions(self):
        return TTLocalizer.TravelGameInstructions

    def getMaxDuration(self):
        return 0

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.sky = loader.loadModel('phase_3.5/models/props/TT_sky')
        self.gameBoard = loader.loadModel('phase_4/models/minigames/toon_cannon_gameground')
        self.gameBoard.setPosHpr(100, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        station = loader.loadModel('phase_4/models/modules/trolley_station_TT.bam')
        self.trolleyCar = station.find('**/trolley_car')
        self.trolleyCar.reparentTo(hidden)
        self.trolleyCarOrigPos = self.trolleyCar.getPos()
        self.trolleyCarOrigHpr = self.trolleyCar.getHpr()
        self.trolleyCar.setPosHpr(0, 0, 0, 0, 0, 0)
        self.trolleyCar.setScale(1.0)
        self.trolleyCar.setX(self.trolleyCar.getX() - TravelGameGlobals.xInc)
        station.removeNode()
        self.keys = self.trolleyCar.findAllMatches('**/key')
        self.numKeys = self.keys.getNumPaths()
        self.keyInit = []
        self.keyRef = []
        for i in range(self.numKeys):
            key = self.keys[i]
            key.setTwoSided(1)
            ref = self.trolleyCar.attachNewNode('key' + repr(i) + 'ref')
            ref.setPosHpr(key, 0, 0, 0, 0, 0, 0)
            self.keyRef.append(ref)
            self.keyInit.append(key.getTransform())

        self.frontWheels = self.trolleyCar.findAllMatches('**/front_wheels')
        self.numFrontWheels = self.frontWheels.getNumPaths()
        self.frontWheelInit = []
        self.frontWheelRef = []
        for i in range(self.numFrontWheels):
            wheel = self.frontWheels[i]
            ref = self.trolleyCar.attachNewNode('frontWheel' + repr(i) + 'ref')
            ref.setPosHpr(wheel, 0, 0, 0, 0, 0, 0)
            self.frontWheelRef.append(ref)
            self.frontWheelInit.append(wheel.getTransform())

        self.backWheels = self.trolleyCar.findAllMatches('**/back_wheels')
        self.numBackWheels = self.backWheels.getNumPaths()
        self.backWheelInit = []
        self.backWheelRef = []
        for i in range(self.numBackWheels):
            wheel = self.backWheels[i]
            ref = self.trolleyCar.attachNewNode('backWheel' + repr(i) + 'ref')
            ref.setPosHpr(wheel, 0, 0, 0, 0, 0, 0)
            self.backWheelRef.append(ref)
            self.backWheelInit.append(wheel.getTransform())

        trolleyAnimationReset = Func(self.resetAnimation)
        self.trainSwitches = {}
        self.trainTracks = {}
        self.tunnels = {}
        self.extraTrainTracks = []
        turnTable = loader.loadModel('phase_4/models/minigames/trolley_game_turntable')
        minPoint = Point3(0, 0, 0)
        maxPoint = Point3(0, 0, 0)
        turnTable.calcTightBounds(minPoint, maxPoint)
        self.fullLength = maxPoint[0]
        for key in list(TravelGameGlobals.BoardLayouts[self.boardIndex].keys()):
            info = TravelGameGlobals.BoardLayouts[self.boardIndex][key]
            switchModel = turnTable.find('**/turntable1').copyTo(render)
            switchModel.setPos(*info['pos'])
            switchModel.reparentTo(hidden)
            self.trainSwitches[key] = switchModel
            zAdj = 0
            for otherSwitch in info['links']:
                info2 = TravelGameGlobals.BoardLayouts[self.boardIndex][otherSwitch]
                x1, y1, z1 = info['pos']
                x2, y2, z2 = info2['pos']
                linkKey = (key, otherSwitch)
                trainTrack = self.loadTrainTrack(x1, y1, x2, y2, zAdj)
                trainTrack.reparentTo(hidden)
                self.trainTracks[linkKey] = trainTrack
                zAdj += 0.005

        rootInfo = TravelGameGlobals.BoardLayouts[self.boardIndex][0]
        rootX, rootY, rootZ = rootInfo['pos']
        startX = rootX - TravelGameGlobals.xInc
        trainTrack = self.loadTrainTrack(startX, rootY, rootX, rootY)
        self.extraTrainTracks.append(trainTrack)
        tunnelX = None
        for key in list(TravelGameGlobals.BoardLayouts[self.boardIndex].keys()):
            if self.isLeaf(key):
                info = TravelGameGlobals.BoardLayouts[self.boardIndex][key]
                switchX, switchY, switchZ = info['pos']
                endX = switchX + TravelGameGlobals.xInc
                trainTrack = self.loadTrainTrack(switchX, switchY, endX, switchY)
                self.extraTrainTracks.append(trainTrack)
                tempModel = loader.loadModel('phase_4/models/minigames/trolley_game_turntable')
                tunnel = tempModel.find('**/tunnel1')
                tunnel.reparentTo(render)
                tempModel.removeNode()
                if not tunnelX:
                    minTrackPoint = Point3(0, 0, 0)
                    maxTrackPoint = Point3(0, 0, 0)
                    trainTrack.calcTightBounds(minTrackPoint, maxTrackPoint)
                    tunnelX = maxTrackPoint[0]
                tunnel.setPos(tunnelX, switchY, 0)
                tunnel.wrtReparentTo(trainTrack)
                self.tunnels[key] = tunnel

        turnTable.removeNode()
        self.loadGui()
        self.introMovie = self.getIntroMovie()
        self.music = base.loader.loadMusic('phase_4/audio/bgm/MG_Travel.ogg')
        self.flashWinningBeansTrack = None
        return

    def loadTrainTrack(self, x1, y1, x2, y2, zAdj = 0):
        turnTable = loader.loadModel('phase_4/models/minigames/trolley_game_turntable')
        trainPart = turnTable.find('**/track_a2')
        trackHeight = 0.03
        trainTrack = render.attachNewNode('trainTrack%d%d%d%d' % (x1,
         y1,
         x2,
         y2))
        trainTrack.setPos(x1, y1, trackHeight)
        xDiff = abs(x2 - x1)
        yDiff = abs(y2 - y1)
        angleInRadians = math.atan((float(y2) - y1) / (x2 - x1))
        angle = rad2Deg(angleInRadians)
        desiredLength = math.sqrt(xDiff * xDiff + yDiff * yDiff)
        lengthToGo = desiredLength
        partIndex = 0
        lengthCovered = 0
        while lengthToGo > self.fullLength / 2.0:
            onePart = trainPart.copyTo(trainTrack)
            onePart.setX(lengthCovered)
            lengthToGo -= self.fullLength
            lengthCovered += self.fullLength

        trainTrack.setH(angle)
        newX = x1 + (x2 - x1) / 2.0
        newY = y1 + (y2 - y1) / 2.0
        trainTrack.setPos(x1, y1, trackHeight + zAdj)
        turnTable.removeNode()
        return trainTrack

    def loadGui(self):
        scoreText = [str(self.currentVotes[self.localAvId])]
        self.gui = DirectFrame()
        self.remainingVotesFrame = DirectFrame(parent=self.gui, relief=None, geom=DGG.getDefaultDialogGeom(), geom_color=GlobalDialogColor, geom_scale=(7, 1, 1), pos=(-0.9, 0, 0.8), scale=0.1, text=TTLocalizer.TravelGameRemainingVotes, text_align=TextNode.ALeft, text_scale=TTLocalizer.DTGremainingVotesFrame, text_pos=(-3.4, -0.1, 0.0))
        self.localVotesRemaining = DirectLabel(parent=self.remainingVotesFrame, relief=None, text=scoreText, text_fg=VBase4(0, 0.5, 0, 1), text_align=TextNode.ARight, text_scale=0.7, pos=(3.2, 0, -0.15))
        guiModel = loader.loadModel('phase_3.5/models/gui/friendslist_gui')
        self.choiceFrame = DirectFrame(parent=self.gui, relief=None, pos=(-0.55, 0, -0.85), image=DGG.getDefaultDialogGeom(), image_scale=(1.4, 1, 0.225), image_color=GlobalDialogColor)
        self.useLabel = DirectLabel(text=TTLocalizer.TravelGameUse, parent=self.choiceFrame, pos=(-0.59, 0, -0.01), text_scale=TTLocalizer.DTGuseLabel, relief=None)
        self.votesPeriodLabel = DirectLabel(text=TTLocalizer.TravelGameVotesWithPeriod, parent=self.choiceFrame, pos=(-0.21, 0, -0.01), text_scale=TTLocalizer.DTGvotesPeriodLabel, relief=None, text_align=TextNode.ALeft)
        self.votesToGoLabel = DirectLabel(text=TTLocalizer.TravelGameVotesToGo, parent=self.choiceFrame, pos=(-0.21, 0, -0.01), text_scale=TTLocalizer.DTGvotesToGoLabel, relief=None, text_align=TextNode.ALeft)
        self.upLabel = DirectLabel(text=TTLocalizer.TravelGameUp, parent=self.choiceFrame, pos=(0.31, 0, -0.01), text_scale=TTLocalizer.DTGupLabel, text_fg=Vec4(0, 0, 1, 1), relief=None, text_align=TextNode.ALeft)
        self.downLabel = DirectLabel(text=TTLocalizer.TravelGameDown, parent=self.choiceFrame, pos=(0.31, 0, -0.01), text_scale=TTLocalizer.DTGdownLabel, text_fg=Vec4(1, 0, 0, 1), relief=None, text_align=TextNode.ALeft)
        self.scrollList = DirectScrolledList(parent=self.choiceFrame, relief=None, pos=(-0.36, 0, -0.02), incButton_image=(guiModel.find('**/FndsLst_ScrollUp'),
         guiModel.find('**/FndsLst_ScrollDN'),
         guiModel.find('**/FndsLst_ScrollUp_Rllvr'),
         guiModel.find('**/FndsLst_ScrollUp')), incButton_relief=None, incButton_pos=(0.0, 0.0, -0.04), incButton_image3_color=Vec4(0.6, 0.6, 0.6, 0.6), incButton_scale=(1.0, 1.0, -1.0), decButton_image=(guiModel.find('**/FndsLst_ScrollUp'),
         guiModel.find('**/FndsLst_ScrollDN'),
         guiModel.find('**/FndsLst_ScrollUp_Rllvr'),
         guiModel.find('**/FndsLst_ScrollUp')), decButton_relief=None, decButton_pos=(0.0, 0.0, 0.095), decButton_image3_color=Vec4(0.6, 0.6, 0.6, 0.6), itemFrame_pos=(0.0, 0.0, 0.0), itemFrame_relief=DGG.GROOVE, numItemsVisible=1, itemMakeFunction=makeLabel, items=[], scrollSpeed=3.0, itemFrame_scale=0.1, command=self.scrollChoiceChanged)
        self.putChoicesInScrollList()
        buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui')
        okImageList = (buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr'))
        self.voteButton = DirectButton(parent=self.choiceFrame, relief=None, image=okImageList, image_scale=3.0, pos=(0.85, 0, 0.0), text=TTLocalizer.TravelGameVoteWithExclamation, text_scale=TTLocalizer.DTGvoteButton, text_pos=(0, 0), command=self.handleInputChoice)
        self.waitingChoicesLabel = DirectLabel(text=TTLocalizer.TravelGameWaitingChoices, text_fg=VBase4(1, 1, 1, 1), relief=None, pos=(-0.2, 0, -0.85), scale=0.075)
        self.waitingChoicesLabel.hide()
        self.gui.hide()
        return

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        self.introMovie.finish()
        del self.introMovie
        self.gameBoard.removeNode()
        del self.gameBoard
        self.sky.removeNode()
        del self.sky
        self.trolleyCar.removeNode()
        del self.trolleyCar
        for key in list(self.trainSwitches.keys()):
            self.trainSwitches[key].removeNode()
            del self.trainSwitches[key]

        self.trainSwitches = {}
        for key in list(self.tunnels.keys()):
            self.tunnels[key].removeNode()
            del self.tunnels[key]

        self.tunnels = {}
        for key in list(self.trainTracks.keys()):
            self.trainTracks[key].removeNode()
            del self.trainTracks[key]

        self.trainTracks = {}
        for trainTrack in self.extraTrainTracks:
            trainTrack.removeNode()
            del trainTrack

        self.extraTrainTracks = []
        self.gui.removeNode()
        del self.gui
        self.waitingChoicesLabel.destroy()
        del self.waitingChoicesLabel
        if self.flashWinningBeansTrack:
            self.flashWinningBeansTrack.finish()
            del self.flashWinningBeansTrack
        for label in self.minigameLabels:
            label.destroy()
            del label

        self.minigameLabels = []
        for icon in self.minigameIcons:
            icon.destroy()
            icon.removeNode()

        self.minigameIcons = []
        if hasattr(self, 'mg_icons'):
            del self.mg_icons
        for label in self.bonusLabels:
            label.destroy()
            del label

        self.bonusLabels = []
        self.scrollList.destroy()
        del self.scrollList
        self.voteButton.destroy()
        del self.voteButton
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM
        del self.music

    def moveCameraToTop(self):
        camera.reparentTo(render)
        p = self.cameraTopView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])

    def moveCameraToTrolley(self):
        camera.reparentTo(self.trolleyCar)
        camera.setPos(-25, 0, 7.5)
        camera.setHpr(-90, 0, 0)

    def onstage(self):
        self.notify.debug('onstage')
        NametagGlobals.setOnscreenChatForced(1)
        DistributedMinigame.onstage(self)
        self.gameBoard.reparentTo(render)
        self.sky.reparentTo(render)
        self.moveCameraToTop()
        self.trolleyCar.reparentTo(render)
        for key in list(self.trainSwitches.keys()):
            self.trainSwitches[key].reparentTo(render)

        for key in list(self.trainTracks.keys()):
            self.trainTracks[key].reparentTo(render)

        for trainTrack in self.extraTrainTracks:
            trainTrack.reparentTo(render)

        base.transitions.irisIn(0.4)
        base.setBackgroundColor(0.1875, 0.7929, 0)
        base.playMusic(self.music, looping=1, volume=0.9)
        self.introMovie.start()

    def offstage(self):
        self.notify.debug('offstage')
        NametagGlobals.setOnscreenChatForced(0)
        base.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor)
        self.introMovie.finish()
        self.gameBoard.hide()
        self.sky.hide()
        self.trolleyCar.hide()
        self.gui.hide()
        self.hideMinigamesAndBonuses()
        for key in list(self.trainSwitches.keys()):
            self.trainSwitches[key].hide()

        for key in list(self.trainTracks.keys()):
            self.trainTracks[key].hide()

        for trainTrack in self.extraTrainTracks:
            trainTrack.hide()

        DistributedMinigame.offstage(self)
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.start()
        self.music.stop()

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        for index in range(self.numPlayers):
            avId = self.avIdList[index]
            name = ''
            avatar = self.getAvatar(avId)
            if avatar:
                avatar.reparentTo(self.trolleyCar)
                avatar.animFSM.request('Sit')
                avatar.setPosHpr(-4, -4.5 + index * 3, 2.8, 90, 0, 0)
                name = avatar.getName()
            self.avNames.append(name)

        self.trolleyCar.setH(90)

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        self.introMovie.finish()
        self.gameFSM.request('inputChoice')

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterInputChoice(self):
        self.notify.debug('enterInputChoice')
        NametagGlobals.setOnscreenChatForced(1)
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.hide()
        if self.timerStartTime != None:
            self.startTimer()
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.stop()
        self.gui.show()
        self.showMinigamesAndBonuses()
        return

    def exitInputChoice(self):
        NametagGlobals.setOnscreenChatForced(0)
        if self.timer != None:
            self.timer.destroy()
            self.timer = None
        self.timerStartTime = None
        self.gui.hide()
        return

    def enterWaitServerChoices(self):
        self.notify.debug('enterWaitServerChoices')
        self.waitingChoicesLabel.show()
        self.gui.hide()

    def exitWaitServerChoices(self):
        self.waitingChoicesLabel.hide()

    def enterDisplayVotes(self, votes, directions, directionToGo, directionReason):
        if self.UseTrolleyResultsPanel:
            self.moveCameraToTrolley()
            self.hideMinigamesAndBonuses()
        else:
            self.moveCameraToTop()
        self.resultVotes = votes
        self.resultDirections = directions
        self.directionToGo = directionToGo
        self.directionReason = directionReason
        self.resultsStr = ''
        directionTotals = [0] * TravelGameGlobals.MaxDirections
        for index in range(len(votes)):
            if index < len(self.avNames):
                avId = self.avIdList[index]
                dir = directions[index]
                numVotes = votes[index]
                directionTotals[dir] += numVotes
                curStr = TTLocalizer.TravelGameOneToonVote % {'name': self.avNames[index],
                 'numVotes': numVotes,
                 'dir': TTLocalizer.TravelGameDirections[dir]}
                if not (numVotes == 0 and avId in self.disconnectedAvIds):
                    self.resultsStr += curStr

        directionStr = TTLocalizer.TravelGameTotals
        for index in range(len(directionTotals)):
            directionStr += ' ' + TTLocalizer.TravelGameDirections[index] + ':'
            directionStr += str(directionTotals[index])

        directionStr += '\n'
        self.resultsStr += directionStr
        reasonStr = ''
        if directionReason == TravelGameGlobals.ReasonVote:
            if directionToGo == 0:
                losingDirection = 1
            else:
                losingDirection = 0
            diffVotes = directionTotals[directionToGo] - directionTotals[losingDirection]
            reasonStr = ''
            if diffVotes > 1:
                reasonStr = TTLocalizer.TravelGameReasonVotesPlural % {'dir': TTLocalizer.TravelGameDirections[directionToGo],
                 'numVotes': diffVotes}
            else:
                reasonStr = TTLocalizer.TravelGameReasonVotesSingular % {'dir': TTLocalizer.TravelGameDirections[directionToGo],
                 'numVotes': diffVotes}
        elif directionReason == TravelGameGlobals.ReasonRandom:
            reasonStr = TTLocalizer.TravelGameReasonRandom % {'dir': TTLocalizer.TravelGameDirections[directionToGo],
             'numVotes': directionTotals[directionToGo]}
        elif directionReason == TravelGameGlobals.ReasonPlaceDecider:
            reasonStr = TravelGameReasonPlace % {'name': 'TODO NAME',
             'dir': TTLocalizer.TravelGameDirections[directionToGo]}
        self.resultsStr += reasonStr
        self.dialog = TTDialog.TTDialog(text=self.resultsStr, command=self.__cleanupDialog, style=TTDialog.NoButtons, pos=(0, 0, 1))
        self.dialog.hide()
        if self.UseTrolleyResultsPanel:
            self.votesPanel = VoteResultsTrolleyPanel.VoteResultsTrolleyPanel(len(self.avIdList), self.avIdList, votes, directions, self.avNames, self.disconnectedAvIds, directionToGo, directionReason, directionTotals)
        else:
            self.votesPanel = VoteResultsPanel.VoteResultsPanel(len(self.avIdList), self.avIdList, votes, directions, self.avNames, self.disconnectedAvIds, directionToGo, directionReason, directionTotals)
        self.votesPanel.startMovie()
        numPlayers = len(self.avIdList)
        if TravelGameGlobals.SpoofFour:
            numPlayers = 4
        delay = TravelGameGlobals.DisplayVotesTimePerPlayer * (numPlayers + 1)
        taskMgr.doMethodLater(delay, self.displayVotesTimeoutTask, self.taskName('displayVotes-timeout'))
        curSwitch = TravelGameGlobals.BoardLayouts[self.boardIndex][self.currentSwitch]
        self.destSwitch = curSwitch['links'][directionToGo]
        self.updateCurrentVotes()

    def exitDisplayVotes(self):
        taskMgr.remove(self.taskName('displayVotes-timeout'))
        self.__cleanupDialog(0)
        if not self.UseTrolleyResultsPanel:
            self.showMinigamesAndBonuses()
        self.votesPanel.destroy()

    def enterMoveTrolley(self):
        self.notify.debug('enterMoveTrolley')
        camera.wrtReparentTo(render)
        keyAngle = round(self.TrolleyMoveDuration) * 360
        dist = Vec3(self.trainSwitches[self.destSwitch].getPos() - self.trainSwitches[self.currentSwitch].getPos()).length()
        wheelAngle = dist / (2.0 * math.pi * 0.95) * 360
        trolleyAnimateInterval = LerpFunctionInterval(self.animateTrolley, duration=self.TrolleyMoveDuration, blendType='easeInOut', extraArgs=[keyAngle, wheelAngle], name='TrolleyAnimate')
        moveTrolley = Sequence()
        moveTrolley.append(Func(self.resetAnimation))
        newPos = self.trainSwitches[self.destSwitch].getPos()
        linkKey = (self.currentSwitch, self.destSwitch)
        origHeading = self.trainTracks[linkKey].getH()
        heading = origHeading + 90
        firstTurn = Parallel()
        firstTurn.append(LerpHprInterval(self.trolleyCar, 1, Vec3(heading, 0, 0)))
        firstTurn.append(LerpHprInterval(self.trainSwitches[self.currentSwitch], 1, Vec3(origHeading, 0, 0)))
        firstTurn.append(LerpHprInterval(self.trainSwitches[self.destSwitch], 1, Vec3(origHeading, 0, 0)))
        moveTrolley.append(firstTurn)
        moveTrolley.append(Parallel(LerpPosInterval(self.trolleyCar, self.TrolleyMoveDuration, newPos, blendType='easeInOut'), trolleyAnimateInterval))
        secondTurn = Parallel()
        secondTurn.append(LerpHprInterval(self.trolleyCar, 1, Vec3(90, 0, 0)))
        secondTurn.append(LerpHprInterval(self.trainSwitches[self.currentSwitch], 1, Vec3(0, 0, 0)))
        secondTurn.append(LerpHprInterval(self.trainSwitches[self.destSwitch], 1, Vec3(0, 0, 0)))
        moveTrolley.append(secondTurn)
        soundTrack = Sequence()
        trolleyExitBellInterval = Parallel(SoundInterval(self.trolleyBellSfx, duration=1), SoundInterval(self.turntableRotateSfx, duration=1, volume=0.5))
        trolleyExitAwayInterval = SoundInterval(self.trolleyAwaySfx, duration=3)
        soundTrack.append(trolleyExitBellInterval)
        soundTrack.append(trolleyExitAwayInterval)
        soundTrack.append(trolleyExitBellInterval)
        self.moveTrolleyIval = Parallel(moveTrolley, soundTrack)
        duration = self.moveTrolleyIval.getDuration()

        def focusOnTrolley(t, self = self):
            pos = self.trolleyCar.getPos()
            pos.setZ(pos.getZ() + 7.5)
            camera.lookAt(pos)
            self.lastFocusHpr = camera.getHpr()

        setRightHprTime = 0
        if self.FlyCameraUp:
            setRightHprTime = 1.0
        camIval1 = Parallel()
        camIval1.append(LerpFunc(focusOnTrolley, duration - setRightHprTime, name='focusOnTrolley'))
        finalPos = Vec3(self.cameraTopView[0], self.cameraTopView[1], self.cameraTopView[2])
        finalHpr = Vec3(self.cameraTopView[3], self.cameraTopView[4], self.cameraTopView[5])
        if self.FlyCameraUp:
            if self.FocusOnTrolleyWhileMovingUp:
                camIval1.append(LerpPosInterval(camera, duration - setRightHprTime, finalPos, name='cameraMove'))
                camIval2 = Sequence(LerpHprInterval(camera, setRightHprTime, finalHpr, name='cameraHpr'))
            else:
                camIval2 = Sequence(LerpPosHprInterval(camera, setRightHprTime, finalPos, finalHpr, blendType='easeIn', name='cameraHpr'))
            camIval = Sequence(camIval1, camIval2)
        else:
            camIval = Sequence(camIval1)
        if self.UseTrolleyResultsPanel:
            self.moveTrolleyIval.append(camIval)
        temp = self.moveTrolleyIval
        self.moveTrolleyIval = Sequence(temp)
        if self.isLeaf(self.destSwitch):
            self.moveTrolleyIval.append(Func(self.gameFSM.request, 'winMovie'))
        else:
            self.moveTrolleyIval.append(Func(self.gameFSM.request, 'inputChoice'))
        self.moveTrolleyIval.start()

    def exitMoveTrolley(self):
        self.notify.debug('exitMoveTrolley')
        self.currentSwitch = self.destSwitch
        self.moveTrolleyIval.finish()
        self.moveCameraToTop()
        self.showMinigamesAndBonuses()

    def enterWinMovie(self):
        resultStr = TTLocalizer.TravelGamePlaying % {'game': self.idToNames[self.switchToMinigameDict[self.currentSwitch]]}
        numToons = 0
        for avId in self.avIdList:
            if avId not in self.disconnectedAvIds:
                numToons += 1

        if numToons <= 1:
            resultStr = TTLocalizer.TravelGameGoingBackToShop
        reachedGoalStr = None
        localAvatarWon = False
        localAvatarLost = False
        noWinner = True
        for avId in list(self.avIdBonuses.keys()):
            name = ''
            avatar = self.getAvatar(avId)
            if avatar:
                name = avatar.getName()
                if self.avIdBonuses[avId][0] == self.currentSwitch:
                    noWinner = False
                    reachedGoalStr = TTLocalizer.TravelGameGotBonus % {'name': name,
                     'numBeans': self.avIdBonuses[avId][1]}
                    if avId == base.localAvatar.doId:
                        if not TravelGameGlobals.ReverseWin:
                            self.wonGameSfx.play()
                            bonusLabel = self.switchToBonusLabelDict[self.currentSwitch]
                            self.flashWinningBeansTrack = Sequence(LerpColorScaleInterval(bonusLabel, 0.75, Vec4(0.5, 1, 0.5, 1)), LerpColorScaleInterval(bonusLabel, 0.75, Vec4(1, 1, 1, 1)))
                            self.flashWinningBeansTrack.loop()
                        else:
                            self.lostGameSfx.play()
                    elif not TravelGameGlobals.ReverseWin:
                        self.lostGameSfx.play()
                    else:
                        self.wonGameSfx.play()

        if noWinner:
            self.noWinnerSfx.play()
            resultStr += '\n\n'
            resultStr += TTLocalizer.TravelGameNoOneGotBonus
        if reachedGoalStr:
            resultStr += '\n\n'
            resultStr += reachedGoalStr
        self.winDialog = TTDialog.TTDialog(text=resultStr, command=self.__cleanupWinDialog, style=TTDialog.NoButtons)
        info = TravelGameGlobals.BoardLayouts[self.boardIndex][self.currentSwitch]
        leafX, leafY, leafZ = info['pos']
        endX = leafX + TravelGameGlobals.xInc
        heading = 90
        moveTrolley = Sequence()
        moveTrolley.append(LerpHprInterval(self.trolleyCar, 1, Vec3(heading, 0, 0)))
        moveTrolley.append(LerpPosInterval(self.trolleyCar, 3, Vec3(endX + 20, leafY, 0)))
        soundTrack = Sequence()
        trolleyExitBellInterval = SoundInterval(self.trolleyBellSfx, duration=1)
        trolleyExitAwayInterval = SoundInterval(self.trolleyAwaySfx, duration=3)
        soundTrack.append(trolleyExitBellInterval)
        soundTrack.append(trolleyExitAwayInterval)
        soundTrack.append(trolleyExitBellInterval)
        self.moveTrolleyIval = Parallel(moveTrolley, soundTrack)
        self.moveTrolleyIval.start()
        delay = 8
        taskMgr.doMethodLater(delay, self.gameOverCallback, self.taskName('playMovie'))
        return

    def exitWinMovie(self):
        taskMgr.remove(self.taskName('playMovie'))
        self.moveTrolleyIval.finish()

    def enterCleanup(self):
        self.notify.debug('enterCleanup')

    def exitCleanup(self):
        pass

    def setStartingVotes(self, startingVotesArray):
        if not len(startingVotesArray) == len(self.avIdList):
            self.notify.error('length does not match, startingVotes=%s, avIdList=%s' % (startingVotesArray, self.avIdList))
            return
        for index in range(len(self.avIdList)):
            avId = self.avIdList[index]
            self.startingVotes[avId] = startingVotesArray[index]
            if avId not in self.currentVotes:
                self.currentVotes[avId] = startingVotesArray[index]

        self.notify.debug('starting votes = %s' % self.startingVotes)

    def startTimer(self):
        now = globalClock.getFrameTime()
        elapsed = now - self.timerStartTime
        self.timer.setPos(1.16, 0, -0.83)
        self.timer.setTime(TravelGameGlobals.InputTimeout)
        self.timer.countdown(TravelGameGlobals.InputTimeout - elapsed, self.handleChoiceTimeout)
        self.timer.show()

    def setTimerStartTime(self, timestamp):
        if not self.hasLocalToon:
            return
        self.timerStartTime = globalClockDelta.networkToLocalTime(timestamp)
        if self.timer != None:
            self.startTimer()
        return

    def handleChoiceTimeout(self):
        self.sendUpdate('setAvatarChoice', [0, 0])
        self.gameFSM.request('waitServerChoices')

    def putChoicesInScrollList(self):
        available = self.currentVotes[self.localAvId]
        if len(self.scrollList['items']) > 0:
            self.scrollList.removeAllItems()
        self.indexToVotes = {}
        index = 0
        for vote in range(available)[::-1]:
            self.scrollList.addItem(str(-(vote + 1)))
            self.indexToVotes[index] = vote + 1
            index += 1

        self.scrollList.addItem(str(0))
        self.indexToVotes[index] = 0
        self.zeroVoteIndex = index
        index += 1
        for vote in range(available):
            self.scrollList.addItem(str(vote + 1))
            self.indexToVotes[index] = vote + 1
            index += 1

        self.scrollList.scrollTo(self.zeroVoteIndex)

    def getAbsVoteChoice(self):
        available = self.currentVotes[self.localAvId]
        retval = 0
        if hasattr(self, 'scrollList'):
            selectedIndex = self.scrollList.getSelectedIndex()
            if selectedIndex in self.indexToVotes:
                retval = self.indexToVotes[selectedIndex]
        return retval

    def getAbsDirectionChoice(self):
        selectedIndex = self.scrollList.getSelectedIndex()
        if selectedIndex < self.zeroVoteIndex:
            retval = 0
        elif selectedIndex == self.zeroVoteIndex:
            retval = 0
        else:
            retval = 1
        return retval

    def makeTextMatchChoice(self):
        self.votesPeriodLabel.hide()
        self.votesToGoLabel.hide()
        self.upLabel.hide()
        self.downLabel.hide()
        if not hasattr(self, 'scrollList') or not hasattr(self, 'zeroVoteIndex'):
            return
        selectedIndex = self.scrollList.getSelectedIndex()
        if selectedIndex < self.zeroVoteIndex:
            self.votesToGoLabel.show()
            self.upLabel.show()
        elif selectedIndex == self.zeroVoteIndex:
            self.votesPeriodLabel.show()
        else:
            self.votesToGoLabel.show()
            self.downLabel.show()

    def scrollChoiceChanged(self):
        choiceVotes = self.getAbsVoteChoice()
        if choiceVotes == 1:
            self.votesToGoLabel['text'] = TTLocalizer.TravelGameVoteToGo
        else:
            self.votesToGoLabel['text'] = TTLocalizer.TravelGameVotesToGo
        available = self.currentVotes[self.localAvId]
        self.localVotesRemaining['text'] = str(available - choiceVotes)
        self.makeTextMatchChoice()

    def setAvatarChose(self, avId):
        if not self.hasLocalToon:
            return
        self.notify.debug('setAvatarChose: avatar: ' + str(avId) + ' choose a number')

    def handleInputChoice(self):
        numVotes = self.getAbsVoteChoice()
        direction = self.getAbsDirectionChoice()
        self.sendUpdate('setAvatarChoice', [numVotes, direction])
        self.gameFSM.request('waitServerChoices')

    def setServerChoices(self, votes, directions, directionToGo, directionReason):
        if not self.hasLocalToon:
            return
        self.notify.debug('requesting displayVotes, curState=%s' % self.gameFSM.getCurrentState().getName())
        self.gameFSM.request('displayVotes', [votes,
         directions,
         directionToGo,
         directionReason])

    def __cleanupDialog(self, value):
        if self.dialog:
            self.dialog.cleanup()
            self.dialog = None
        return

    def displayVotesTimeoutTask(self, task):
        self.notify.debug('Done waiting for display votes')
        self.gameFSM.request('moveTrolley')
        return Task.done

    def updateCurrentVotes(self):
        for index in range(len(self.resultVotes)):
            avId = self.avIdList[index]
            oldCurrentVotes = self.currentVotes[avId]
            self.currentVotes[avId] -= self.resultVotes[index]

        self.putChoicesInScrollList()
        self.makeTextMatchChoice()

    def isLeaf(self, switchIndex):
        retval = False
        links = TravelGameGlobals.BoardLayouts[self.boardIndex][switchIndex]['links']
        if len(links) == 0:
            retval = True
        return retval

    def __cleanupWinDialog(self, value):
        if hasattr(self, 'winDialog') and self.winDialog:
            self.winDialog.cleanup()
            self.winDialog = None
        return

    def gameOverCallback(self, task):
        self.__cleanupWinDialog(0)
        self.gameOver()
        return Task.done

    def setMinigames(self, switches, minigames):
        if not self.hasLocalToon:
            return
        self.switchToMinigameDict = {}
        for index in range(len(switches)):
            switch = switches[index]
            minigame = minigames[index]
            self.switchToMinigameDict[switch] = minigame

        self.notify.debug('minigameDict = %s' % self.switchToMinigameDict)
        self.loadMinigameIcons()

    def loadMinigameIcons(self):
        self.mg_icons = loader.loadModel('phase_4/models/minigames/mg_icons')
        for switch in list(self.switchToMinigameDict.keys()):
            minigame = self.switchToMinigameDict[switch]
            switchPos = self.trainSwitches[switch].getPos()
            labelPos = map3dToAspect2d(render, switchPos)
            useText = True
            iconName = None
            if minigame in list(IconDict.keys()):
                iconName = IconDict[minigame]
            icon = None
            if self.mg_icons:
                icon = self.mg_icons.find('**/%s' % iconName)
                if not icon.isEmpty():
                    useText = False
            if labelPos:
                if useText:
                    labelPos.setZ(labelPos.getZ() - 0.1)
                    label = DirectLabel(text=self.idToNames[minigame], relief=None, scale=0.1, pos=labelPos, text_fg=(1.0, 1.0, 1.0, 1.0))
                    label.hide()
                    self.minigameLabels.append(label)
                else:
                    placeHolder = DirectButton(image=icon, relief=None, text=('',
                     '',
                     self.idToNames[minigame],
                     ''), text_scale=0.3, text_pos=(0, -0.7, 0), text_fg=(1, 1, 1, 1), clickSound=None, pressEffect=0)
                    placeHolder.setPos(labelPos)
                    placeHolder.setScale(0.2)
                    placeHolder.hide()
                    self.minigameIcons.append(placeHolder)
                    tunnel = self.tunnels[switch]
                    sign = tunnel.attachNewNode('sign')
                    icon.copyTo(sign)
                    sign.setH(-90)
                    sign.setZ(26)
                    sign.setScale(10)

        return

    def showMinigamesAndBonuses(self):
        for label in self.minigameLabels:
            label.show()

        for label in self.bonusLabels:
            label.show()

        for icon in self.minigameIcons:
            icon.show()

    def hideMinigamesAndBonuses(self):
        for label in self.minigameLabels:
            label.hide()

        for label in self.bonusLabels:
            label.hide()

        for icon in self.minigameIcons:
            icon.hide()

    def loadBonuses(self):
        self.switchToBonusLabelDict = {}
        for avId in list(self.avIdBonuses.keys()):
            if avId == self.localAvId:
                switch = self.avIdBonuses[avId][0]
                beans = self.avIdBonuses[avId][1]
                switchPos = self.trainSwitches[switch].getPos()
                labelPos = map3dToAspect2d(render, switchPos)
                if labelPos:
                    labelPos.setX(labelPos.getX() + 0.1)
                    labelPos.setZ(labelPos.getZ() - 0.02)
                    bonusStr = TTLocalizer.TravelGameBonusBeans % {'numBeans': beans}
                    label = DirectLabel(text=bonusStr, relief=None, scale=0.1, pos=labelPos, text_fg=(1.0, 1.0, 1.0, 1.0), text_align=TextNode.ALeft)
                    label.hide()
                    self.bonusLabels.append(label)
                    self.switchToBonusLabelDict[switch] = label
                break

        return

    def setBonuses(self, switches, beans):
        if not self.hasLocalToon:
            return
        self.avIdBonuses = {}
        for index in range(len(self.avIdList)):
            avId = self.avIdList[index]
            switch = switches[index]
            bean = beans[index]
            self.avIdBonuses[avId] = (switch, bean)

        self.notify.debug('self.avIdBonuses = %s' % self.avIdBonuses)
        self.loadBonuses()

    def handleDisabledAvatar(self, avId):
        self.notify.warning('DistrbutedTravelGame: handleDisabledAvatar: disabled avId: ' + str(avId))
        self.disconnectedAvIds.append(avId)

    def setBoardIndex(self, boardIndex):
        self.boardIndex = boardIndex

    def getIntroMovie(self):
        rootInfo = TravelGameGlobals.BoardLayouts[self.boardIndex][0]
        rootX, rootY, rootZ = rootInfo['pos']
        startX = rootX - TravelGameGlobals.xInc
        heading = 90
        moveTrolley = Sequence()
        moveTrolley.append(Func(self.trolleyCar.setH, 90))
        moveTrolley.append(LerpPosInterval(self.trolleyCar, 3, Vec3(rootX, rootY, 0), startPos=Vec3(startX, rootY, 0)))
        moveTrolley.append(LerpHprInterval(self.trolleyCar, 1, Vec3(heading, 0, 0)))
        soundTrack = Sequence()
        trolleyExitAwayInterval = SoundInterval(self.trolleyAwaySfx, duration=3)
        trolleyExitBellInterval = SoundInterval(self.trolleyBellSfx, duration=1)
        soundTrack.append(trolleyExitAwayInterval)
        soundTrack.append(trolleyExitBellInterval)
        retval = Parallel(moveTrolley, soundTrack)
        return retval

    def animateTrolley(self, t, keyAngle, wheelAngle):
        for i in range(self.numKeys):
            key = self.keys[i]
            ref = self.keyRef[i]
            key.setH(ref, t * keyAngle)

        for i in range(self.numFrontWheels):
            frontWheel = self.frontWheels[i]
            ref = self.frontWheelRef[i]
            frontWheel.setH(ref, t * wheelAngle)

        for i in range(self.numBackWheels):
            backWheel = self.backWheels[i]
            ref = self.backWheelRef[i]
            backWheel.setH(ref, t * wheelAngle)

    def resetAnimation(self):
        for i in range(self.numKeys):
            self.keys[i].setTransform(self.keyInit[i])

        for i in range(self.numFrontWheels):
            self.frontWheels[i].setTransform(self.frontWheelInit[i])

        for i in range(self.numBackWheels):
            self.backWheels[i].setTransform(self.backWheelInit[i])