from otp.ai.AIBase import *
from direct.distributed.ClockDelta import *
from toontown.ai.ToonBarrier import *
from direct.distributed import DistributedObjectAI
from direct.fsm import ClassicFSM, State
from direct.fsm import State
from toontown.shtiker import PurchaseManagerAI
from toontown.shtiker import NewbiePurchaseManagerAI
import MinigameCreatorAI
from direct.task import Task
import random
import MinigameGlobals
from direct.showbase import PythonUtil
import TravelGameGlobals
from toontown.toonbase import ToontownGlobals
EXITED = 0
EXPECTED = 1
JOINED = 2
READY = 3
DEFAULT_POINTS = 1
MAX_POINTS = 7
JOIN_TIMEOUT = 40.0 + MinigameGlobals.latencyTolerance
READY_TIMEOUT = MinigameGlobals.MaxLoadTime + MinigameGlobals.rulesDuration + MinigameGlobals.latencyTolerance
EXIT_TIMEOUT = 20.0 + MinigameGlobals.latencyTolerance

class DistributedMinigameAI(DistributedObjectAI.DistributedObjectAI):
    notify = directNotify.newCategory('DistributedMinigameAI')

    def __init__(self, air, minigameId):
        try:
            self.DistributedMinigameAI_initialized
        except:
            self.DistributedMinigameAI_initialized = 1
            DistributedObjectAI.DistributedObjectAI.__init__(self, air)
            self.minigameId = minigameId
            self.frameworkFSM = ClassicFSM.ClassicFSM('DistributedMinigameAI', [State.State('frameworkOff', self.enterFrameworkOff, self.exitFrameworkOff, ['frameworkWaitClientsJoin']),
             State.State('frameworkWaitClientsJoin', self.enterFrameworkWaitClientsJoin, self.exitFrameworkWaitClientsJoin, ['frameworkWaitClientsReady', 'frameworkWaitClientsExit', 'frameworkCleanup']),
             State.State('frameworkWaitClientsReady', self.enterFrameworkWaitClientsReady, self.exitFrameworkWaitClientsReady, ['frameworkGame', 'frameworkWaitClientsExit', 'frameworkCleanup']),
             State.State('frameworkGame', self.enterFrameworkGame, self.exitFrameworkGame, ['frameworkWaitClientsExit', 'frameworkCleanup']),
             State.State('frameworkWaitClientsExit', self.enterFrameworkWaitClientsExit, self.exitFrameworkWaitClientsExit, ['frameworkCleanup']),
             State.State('frameworkCleanup', self.enterFrameworkCleanup, self.exitFrameworkCleanup, ['frameworkOff'])], 'frameworkOff', 'frameworkOff')
            self.frameworkFSM.enterInitialState()
            self.avIdList = []
            self.stateDict = {}
            self.scoreDict = {}
            self.difficultyOverride = None
            self.trolleyZoneOverride = None
            self.metagameRound = -1
            self.startingVotes = {}

        return

    def addChildGameFSM(self, gameFSM):
        self.frameworkFSM.getStateNamed('frameworkGame').addChild(gameFSM)

    def removeChildGameFSM(self, gameFSM):
        self.frameworkFSM.getStateNamed('frameworkGame').removeChild(gameFSM)

    def setExpectedAvatars(self, avIds):
        self.avIdList = avIds
        self.numPlayers = len(self.avIdList)
        self.notify.debug('BASE: setExpectedAvatars: expecting avatars: ' + str(self.avIdList))

    def setNewbieIds(self, newbieIds):
        self.newbieIdList = newbieIds
        if len(self.newbieIdList) > 0:
            self.notify.debug('BASE: setNewbieIds: %s' % self.newbieIdList)

    def setTrolleyZone(self, trolleyZone):
        self.trolleyZone = trolleyZone

    def setDifficultyOverrides(self, difficultyOverride, trolleyZoneOverride):
        self.difficultyOverride = difficultyOverride
        if self.difficultyOverride is not None:
            self.difficultyOverride = MinigameGlobals.QuantizeDifficultyOverride(difficultyOverride)
        self.trolleyZoneOverride = trolleyZoneOverride
        return

    def setMetagameRound(self, roundNum):
        self.metagameRound = roundNum

    def _playing(self):
        if not hasattr(self, 'gameFSM'):
            return False
        if self.gameFSM.getCurrentState() == None:
            return False
        return self.gameFSM.getCurrentState().getName() == 'play'

    def _inState(self, states):
        if not hasattr(self, 'gameFSM'):
            return False
        if self.gameFSM.getCurrentState() == None:
            return False
        return self.gameFSM.getCurrentState().getName() in makeList(states)

    def generate(self):
        DistributedObjectAI.DistributedObjectAI.generate(self)
        self.frameworkFSM.request('frameworkWaitClientsJoin')

    def delete(self):
        self.notify.debug('BASE: delete: deleting AI minigame object')
        del self.frameworkFSM
        self.ignoreAll()
        DistributedObjectAI.DistributedObjectAI.delete(self)

    def isSinglePlayer(self):
        if self.numPlayers == 1:
            return 1
        else:
            return 0

    def getParticipants(self):
        return self.avIdList

    def getTrolleyZone(self):
        return self.trolleyZone

    def getDifficultyOverrides(self):
        response = [self.difficultyOverride, self.trolleyZoneOverride]
        if response[0] is None:
            response[0] = MinigameGlobals.NoDifficultyOverride
        else:
            response[0] *= MinigameGlobals.DifficultyOverrideMult
            response[0] = int(response[0])
        if response[1] is None:
            response[1] = MinigameGlobals.NoTrolleyZoneOverride
        return response

    def b_setGameReady(self):
        self.setGameReady()
        self.d_setGameReady()

    def d_setGameReady(self):
        self.notify.debug('BASE: Sending setGameReady')
        self.sendUpdate('setGameReady', [])

    def setGameReady(self):
        self.notify.debug('BASE: setGameReady: game ready with avatars: %s' % self.avIdList)
        self.normalExit = 1

    def b_setGameStart(self, timestamp):
        self.d_setGameStart(timestamp)
        self.setGameStart(timestamp)

    def d_setGameStart(self, timestamp):
        self.notify.debug('BASE: Sending setGameStart')
        self.sendUpdate('setGameStart', [timestamp])

    def setGameStart(self, timestamp):
        self.notify.debug('BASE: setGameStart')

    def b_setGameExit(self):
        self.d_setGameExit()
        self.setGameExit()

    def d_setGameExit(self):
        self.notify.debug('BASE: Sending setGameExit')
        self.sendUpdate('setGameExit', [])

    def setGameExit(self):
        self.notify.debug('BASE: setGameExit')

    def setGameAbort(self):
        self.notify.debug('BASE: setGameAbort')
        self.normalExit = 0
        self.sendUpdate('setGameAbort', [])
        self.frameworkFSM.request('frameworkCleanup')

    def handleExitedAvatar(self, avId):
        self.notify.warning('BASE: handleExitedAvatar: avatar id exited: ' + str(avId))
        self.stateDict[avId] = EXITED
        self.setGameAbort()

    def gameOver(self):
        self.notify.debug('BASE: gameOver')
        self.frameworkFSM.request('frameworkWaitClientsExit')

    def enterFrameworkOff(self):
        self.notify.debug('BASE: enterFrameworkOff')

    def exitFrameworkOff(self):
        pass

    def enterFrameworkWaitClientsJoin(self):
        self.notify.debug('BASE: enterFrameworkWaitClientsJoin')
        for avId in self.avIdList:
            self.stateDict[avId] = EXPECTED
            self.scoreDict[avId] = DEFAULT_POINTS
            self.acceptOnce(self.air.getAvatarExitEvent(avId), self.handleExitedAvatar, extraArgs=[avId])

        def allAvatarsJoined(self = self):
            self.notify.debug('BASE: all avatars joined')
            self.b_setGameReady()
            self.frameworkFSM.request('frameworkWaitClientsReady')

        def handleTimeout(avIds, self = self):
            self.notify.debug('BASE: timed out waiting for clients %s to join' % avIds)
            self.setGameAbort()

        self.__barrier = ToonBarrier('waitClientsJoin', self.uniqueName('waitClientsJoin'), self.avIdList, JOIN_TIMEOUT, allAvatarsJoined, handleTimeout)

    def setAvatarJoined(self):
        if self.frameworkFSM.getCurrentState().getName() != 'frameworkWaitClientsJoin':
            self.notify.debug('BASE: Ignoring setAvatarJoined message')
            return
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('BASE: setAvatarJoined: avatar id joined: ' + str(avId))
        self.air.writeServerEvent('minigame_joined', avId, '%s|%s' % (self.minigameId, self.trolleyZone))
        self.stateDict[avId] = JOINED
        self.notify.debug('BASE: setAvatarJoined: new states: ' + str(self.stateDict))
        self.__barrier.clear(avId)

    def exitFrameworkWaitClientsJoin(self):
        self.__barrier.cleanup()
        del self.__barrier

    def enterFrameworkWaitClientsReady(self):
        self.notify.debug('BASE: enterFrameworkWaitClientsReady')

        def allAvatarsReady(self = self):
            self.notify.debug('BASE: all avatars ready')
            self.frameworkFSM.request('frameworkGame')

        def handleTimeout(avIds, self = self):
            self.notify.debug("BASE: timed out waiting for clients %s to report 'ready'" % avIds)
            self.setGameAbort()

        self.__barrier = ToonBarrier('waitClientsReady', self.uniqueName('waitClientsReady'), self.avIdList, READY_TIMEOUT, allAvatarsReady, handleTimeout)
        for avId in self.stateDict.keys():
            if self.stateDict[avId] == READY:
                self.__barrier.clear(avId)

        self.notify.debug('  safezone: %s' % self.getSafezoneId())
        self.notify.debug('difficulty: %s' % self.getDifficulty())

    def setAvatarReady(self):
        if self.frameworkFSM.getCurrentState().getName() not in ['frameworkWaitClientsReady', 'frameworkWaitClientsJoin']:
            self.notify.debug('BASE: Ignoring setAvatarReady message')
            return
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('BASE: setAvatarReady: avatar id ready: ' + str(avId))
        self.stateDict[avId] = READY
        self.notify.debug('BASE: setAvatarReady: new avId states: ' + str(self.stateDict))
        if self.frameworkFSM.getCurrentState().getName() == 'frameworkWaitClientsReady':
            self.__barrier.clear(avId)

    def exitFrameworkWaitClientsReady(self):
        self.__barrier.cleanup()
        del self.__barrier

    def enterFrameworkGame(self):
        self.notify.debug('BASE: enterFrameworkGame')
        self.gameStartTime = globalClock.getRealTime()
        self.b_setGameStart(globalClockDelta.localToNetworkTime(self.gameStartTime))

    def exitFrameworkGame(self):
        pass

    def enterFrameworkWaitClientsExit(self):
        self.notify.debug('BASE: enterFrameworkWaitClientsExit')
        self.b_setGameExit()

        def allAvatarsExited(self = self):
            self.notify.debug('BASE: all avatars exited')
            self.frameworkFSM.request('frameworkCleanup')

        def handleTimeout(avIds, self = self):
            self.notify.debug('BASE: timed out waiting for clients %s to exit' % avIds)
            self.frameworkFSM.request('frameworkCleanup')

        self.__barrier = ToonBarrier('waitClientsExit', self.uniqueName('waitClientsExit'), self.avIdList, EXIT_TIMEOUT, allAvatarsExited, handleTimeout)
        for avId in self.stateDict.keys():
            if self.stateDict[avId] == EXITED:
                self.__barrier.clear(avId)

    def setAvatarExited(self):
        if self.frameworkFSM.getCurrentState().getName() != 'frameworkWaitClientsExit':
            self.notify.debug('BASE: Ignoring setAvatarExit message')
            return
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('BASE: setAvatarExited: avatar id exited: ' + str(avId))
        self.stateDict[avId] = EXITED
        self.notify.debug('BASE: setAvatarExited: new avId states: ' + str(self.stateDict))
        self.__barrier.clear(avId)

    def exitFrameworkWaitClientsExit(self):
        self.__barrier.cleanup()
        del self.__barrier

    def hasScoreMult(self):
        return 1

    def enterFrameworkCleanup(self):
        self.notify.debug('BASE: enterFrameworkCleanup: normalExit=%s' % self.normalExit)
        scoreMult = MinigameGlobals.getScoreMult(self.getSafezoneId())
        if not self.hasScoreMult():
            scoreMult = 1.0
        self.notify.debug('score multiplier: %s' % scoreMult)
        for avId in self.avIdList:
            self.scoreDict[avId] *= scoreMult

        scoreList = []
        if not self.normalExit:
            randReward = random.randrange(DEFAULT_POINTS, MAX_POINTS + 1)
        for avId in self.avIdList:
            if self.normalExit:
                score = int(self.scoreDict[avId] + 0.5)
            else:
                score = randReward
            if ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY in simbase.air.holidayManager.currentHolidays or ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY_MONTH in simbase.air.holidayManager.currentHolidays:
                score *= MinigameGlobals.JellybeanTrolleyHolidayScoreMultiplier
            logEvent = False
            if score > 255:
                score = 255
                logEvent = True
            elif score < 0:
                score = 0
                logEvent = True
            if logEvent:
                self.air.writeServerEvent('suspicious', avId, 'got %s jellybeans playing minigame %s in zone %s' % (score, self.minigameId, self.getSafezoneId()))
            scoreList.append(score)

        self.requestDelete()
        if self.metagameRound > -1:
            self.handleMetagamePurchaseManager(scoreList)
        else:
            self.handleRegularPurchaseManager(scoreList)
        self.frameworkFSM.request('frameworkOff')

    def handleMetagamePurchaseManager(self, scoreList):
        self.notify.debug('self.newbieIdList = %s' % self.newbieIdList)
        votesToUse = self.startingVotes
        if hasattr(self, 'currentVotes'):
            votesToUse = self.currentVotes
        votesArray = []
        for avId in self.avIdList:
            if votesToUse.has_key(avId):
                votesArray.append(votesToUse[avId])
            else:
                self.notify.warning('votesToUse=%s does not have avId=%d' % (votesToUse, avId))
                votesArray.append(0)

        if self.metagameRound < TravelGameGlobals.FinalMetagameRoundIndex:
            newRound = self.metagameRound
            if not self.minigameId == ToontownGlobals.TravelGameId:
                for index in range(len(scoreList)):
                    votesArray[index] += scoreList[index]

            self.notify.debug('votesArray = %s' % votesArray)
            desiredNextGame = None
            if hasattr(self, 'desiredNextGame'):
                desiredNextGame = self.desiredNextGame
            numToons = 0
            lastAvId = 0
            for avId in self.avIdList:
                av = simbase.air.doId2do.get(avId)
                if av:
                    numToons += 1
                    lastAvId = avId

            doNewbie = False
            if numToons == 1 and lastAvId in self.newbieIdList:
                doNewbie = True
            if doNewbie:
                pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI(self.air, lastAvId, self.avIdList, scoreList, self.minigameId, self.trolleyZone)
                MinigameCreatorAI.acquireMinigameZone(self.zoneId)
                pm.generateWithRequired(self.zoneId)
            else:
                pm = PurchaseManagerAI.PurchaseManagerAI(self.air, self.avIdList, scoreList, self.minigameId, self.trolleyZone, self.newbieIdList, votesArray, newRound, desiredNextGame)
                pm.generateWithRequired(self.zoneId)
        else:
            self.notify.debug('last minigame, handling newbies')
            if ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY in simbase.air.holidayManager.currentHolidays or ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY_MONTH in simbase.air.holidayManager.currentHolidays:
                votesArray = map(lambda x: MinigameGlobals.JellybeanTrolleyHolidayScoreMultiplier * x, votesArray)
            for id in self.newbieIdList:
                pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI(self.air, id, self.avIdList, scoreList, self.minigameId, self.trolleyZone)
                MinigameCreatorAI.acquireMinigameZone(self.zoneId)
                pm.generateWithRequired(self.zoneId)

            if len(self.avIdList) > len(self.newbieIdList):
                pm = PurchaseManagerAI.PurchaseManagerAI(self.air, self.avIdList, scoreList, self.minigameId, self.trolleyZone, self.newbieIdList, votesArray=votesArray, metagameRound=self.metagameRound)
                pm.generateWithRequired(self.zoneId)
        return

    def handleRegularPurchaseManager(self, scoreList):
        for id in self.newbieIdList:
            pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI(self.air, id, self.avIdList, scoreList, self.minigameId, self.trolleyZone)
            MinigameCreatorAI.acquireMinigameZone(self.zoneId)
            pm.generateWithRequired(self.zoneId)

        if len(self.avIdList) > len(self.newbieIdList):
            pm = PurchaseManagerAI.PurchaseManagerAI(self.air, self.avIdList, scoreList, self.minigameId, self.trolleyZone, self.newbieIdList)
            pm.generateWithRequired(self.zoneId)

    def exitFrameworkCleanup(self):
        pass

    def requestExit(self):
        self.notify.debug('BASE: requestExit: client has requested the game to end')
        self.setGameAbort()

    def local2GameTime(self, timestamp):
        return timestamp - self.gameStartTime

    def game2LocalTime(self, timestamp):
        return timestamp + self.gameStartTime

    def getCurrentGameTime(self):
        return self.local2GameTime(globalClock.getFrameTime())

    def getDifficulty(self):
        if self.difficultyOverride is not None:
            return self.difficultyOverride
        if hasattr(self.air, 'minigameDifficulty'):
            return float(self.air.minigameDifficulty)
        return MinigameGlobals.getDifficulty(self.getSafezoneId())

    def getSafezoneId(self):
        if self.trolleyZoneOverride is not None:
            return self.trolleyZoneOverride
        if hasattr(self.air, 'minigameSafezoneId'):
            return MinigameGlobals.getSafezoneId(self.air.minigameSafezoneId)
        return MinigameGlobals.getSafezoneId(self.trolleyZone)

    def logPerfectGame(self, avId):
        self.air.writeServerEvent('perfectMinigame', avId, '%s|%s|%s' % (self.minigameId, self.trolleyZone, self.avIdList))

    def logAllPerfect(self):
        for avId in self.avIdList:
            self.logPerfectGame(avId)

    def getStartingVotes(self):
        retval = []
        for avId in self.avIdList:
            if self.startingVotes.has_key(avId):
                retval.append(self.startingVotes[avId])
            else:
                self.notify.warning('how did this happen? avId=%d not in startingVotes %s' % (avId, self.startingVotes))
                retval.append(0)

        return retval

    def setStartingVote(self, avId, startingVote):
        self.startingVotes[avId] = startingVote
        self.notify.debug('setting starting vote of avId=%d to %d' % (avId, startingVote))

    def getMetagameRound(self):
        return self.metagameRound
