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 MinigameGlobals
from direct.showbase import PythonUtil
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.skippable = True
            self.skipAvIds = []

    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 _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.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)
        self.skippable = False

    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)
        self.checkForSkip()

    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 = []
        for avId in self.avIdList:
            if self.normalExit:
                score = int(self.scoreDict[avId] + 0.5)
            else:
                score = 0
            if simbase.air.newsManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY) or simbase.air.newsManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY_MONTH):
                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()
        self.handleRegularPurchaseManager(scoreList)
        self.frameworkFSM.request('frameworkOff')

    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 requestSkip(self):
        avId = self.air.getAvatarIdFromSender()

        if (not self.skippable) or (avId not in self.avIdList) or (avId in self.skipAvIds):
            return

        self.skipAvIds.append(avId)
        self.checkForSkip()

    def checkForSkip(self):
        if len(self.skipAvIds) >= len(self.avIdList):
            self.skippable = False
            self.setGameAbort()
        else:
            self.sendUpdate('setSkipCount', [len(self.skipAvIds)])