from DistributedMinigameAI import *
from toontown.ai.ToonBarrier import *
from direct.fsm import ClassicFSM, State
from direct.directnotify import DirectNotifyGlobal
from toontown.minigame import ToonBlitzGlobals
from math import sqrt

class DistributedTwoDGameAI(DistributedMinigameAI):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTwoDGameAI')

    def __init__(self, air, minigameId):
        try:
            self.DistributedTwoDGameAI_initialized
        except:
            self.DistributedTwoDGame_initialized = 1
            DistributedMinigameAI.__init__(self, air, minigameId)
            self.gameFSM = ClassicFSM.ClassicFSM('DistributedTwoDGameAI', [State.State('inactive', self.enterInactive, self.exitInactive, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, ['inactive'])], 'inactive', 'inactive')
            self.addChildGameFSM(self.gameFSM)
            self.finishedBonusDict = {}
            self.finishedTimeLeftDict = {}
            self.numFallDownDict = {}
            self.numHitByEnemyDict = {}
            self.numSquishDict = {}
            self.treasuresCollectedDict = {}
            self.sectionsSelected = []
            self.enemyHealthTable = []
            self.treasureTakenTable = []
            self.sectionIndexList = []

    def generate(self):
        self.notify.debug('generate')
        DistributedMinigameAI.generate(self)

    def delete(self):
        self.notify.debug('delete')
        del self.gameFSM
        DistributedMinigameAI.delete(self)

    def setTrolleyZone(self, trolleyZone):
        DistributedMinigameAI.setTrolleyZone(self, trolleyZone)
        self.setupSections()

    def setGameReady(self):
        self.notify.debug('setGameReady')
        DistributedMinigameAI.setGameReady(self)
        self.numTreasures = ToonBlitzGlobals.NumTreasures
        self.numEnemies = ToonBlitzGlobals.NumEnemies
        self.numTreasuresTaken = 0
        self.numEnemiesKilled = 0
        for avId in self.scoreDict.keys():
            self.scoreDict[avId] = 0
            self.finishedBonusDict[avId] = 0
            self.finishedTimeLeftDict[avId] = -1
            self.numFallDownDict[avId] = 0
            self.numHitByEnemyDict[avId] = 0
            self.numSquishDict[avId] = 0
            self.treasuresCollectedDict[avId] = [0,
             0,
             0,
             0]

        for i in xrange(len(self.sectionsSelected)):
            sectionIndex = self.sectionsSelected[i][0]
            attribs = ToonBlitzGlobals.SectionTypes[sectionIndex]
            enemiesPool = attribs[3]
            self.enemyHealthTable += [[]]
            enemyIndicesSelected = self.sectionsSelected[i][1]
            for j in xrange(len(enemyIndicesSelected)):
                enemyIndex = enemyIndicesSelected[j]
                enemyType = enemiesPool[enemyIndex][0]
                self.enemyHealthTable[i] += [ToonBlitzGlobals.EnemyBaseHealth]
                self.enemyHealthTable[i][j] *= self.numPlayers
                if enemyType in ToonBlitzGlobals.EnemyHealthMultiplier:
                    self.enemyHealthTable[i][j] *= ToonBlitzGlobals.EnemyHealthMultiplier[enemyType]

            self.treasureTakenTable += [[]]
            treasureIndicesSelected = self.sectionsSelected[i][2]
            for j in xrange(len(treasureIndicesSelected)):
                self.treasureTakenTable[i] += [0]

            enemyIndicesSelected = self.sectionsSelected[i][1]
            for j in xrange(len(enemyIndicesSelected)):
                self.treasureTakenTable[i] += [0]

    def setGameStart(self, timestamp):
        self.notify.debug('setGameStart')
        DistributedMinigameAI.setGameStart(self, timestamp)
        self.gameFSM.request('play')

    def setGameAbort(self):
        self.notify.debug('setGameAbort')
        if self.gameFSM.getCurrentState():
            self.gameFSM.request('cleanup')
        DistributedMinigameAI.setGameAbort(self)

    def gameOver(self):
        self.notify.debug('gameOver')
        scoreList = []
        finishedBonusList = []
        timeLeftList = []
        treasureCollectedList = []
        playerErrorList = []
        for avId in self.avIdList:
            scoreList.append(self.scoreDict[avId])
            finishedBonusList.append(self.finishedBonusDict[avId])
            timeLeftList.append(self.finishedTimeLeftDict[avId])
            treasureCollectedList.append(self.treasuresCollectedDict[avId])
            playerError = [self.numFallDownDict[avId], self.numHitByEnemyDict[avId], self.numSquishDict[avId]]
            playerErrorList.append(playerError)
            self.scoreDict[avId] = max(0, self.scoreDict[avId])
            jellybeans = sqrt(self.scoreDict[avId] * ToonBlitzGlobals.ScoreToJellyBeansMultiplier)
            self.scoreDict[avId] = max(1, int(jellybeans))

        self.air.writeServerEvent('minigame_twoD', self.doId, '%s|%s|%s|%s|%s|%s|%s|%s|%s' % (ToontownGlobals.TwoDGameId,
         self.getSafezoneId(),
         self.avIdList,
         scoreList,
         finishedBonusList,
         timeLeftList,
         treasureCollectedList,
         playerErrorList,
         self.sectionIndexList))
        self.notify.debug('minigame_twoD%s: %s|%s|%s|%s|%s|%s|%s|%s|%s' % (self.doId,
         ToontownGlobals.TwoDGameId,
         self.getSafezoneId(),
         self.avIdList,
         scoreList,
         finishedBonusList,
         timeLeftList,
         treasureCollectedList,
         playerErrorList,
         self.sectionIndexList))
        self.gameFSM.request('cleanup')
        DistributedMinigameAI.gameOver(self)

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

    def exitInactive(self):
        pass

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

        def allToonsDone(self = self):
            self.notify.debug('allToonsDone')
            self.sendUpdate('setEveryoneDone')
            if not ToonBlitzGlobals.EndlessGame:
                self.gameOver()

        def handleTimeout(avIds, self = self):
            self.notify.debug('handleTimeout: avatars %s did not report "done"' % avIds)
            self.setGameAbort()

        self.doneBarrier = ToonBarrier('waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, ToonBlitzGlobals.GameDuration[self.getSafezoneId()] + ToonBlitzGlobals.ShowScoresDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout)

    def exitPlay(self):
        pass

    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        self.doneBarrier.cleanup()
        del self.doneBarrier
        self.gameFSM.request('inactive')

    def exitCleanup(self):
        pass

    def claimTreasure(self, sectionIndex, treasureIndex):
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('treasure %s-%s claimed by %s' % (sectionIndex, treasureIndex, avId))
        if sectionIndex < 0 or sectionIndex >= len(self.sectionsSelected):
            self.air.writeServerEvent('warning', sectionIndex, 'TwoDGameAI.claimTreasure sectionIndex out of range.')
            return
        if treasureIndex < 0 or treasureIndex >= len(self.treasureTakenTable[sectionIndex]):
            self.notify.warning('Treasure %s: TwoDGameAI.claimTreasure treasureIndex out of range.' % treasureIndex)
            self.air.writeServerEvent('warning', treasureIndex, 'TwoDGameAI.claimTreasure treasureIndex out of range.')
            return
        if self.treasureTakenTable[sectionIndex][treasureIndex]:
            return
        initialTreasureList = self.sectionsSelected[sectionIndex][2]
        if treasureIndex < len(initialTreasureList):
            treasureValue = initialTreasureList[treasureIndex][1]
        else:
            treasureValue = self.numPlayers
        self.treasureTakenTable[sectionIndex][treasureIndex] = treasureValue
        self.treasuresCollectedDict[avId][treasureValue - 1] += 1
        self.scoreDict[avId] += ToonBlitzGlobals.ScoreGainPerTreasure * treasureValue
        self.numTreasuresTaken += 1
        self.sendUpdate('setTreasureGrabbed', [avId, sectionIndex, treasureIndex])

    def claimEnemyShot(self, sectionIndex, enemyIndex):
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('enemy %s-%s shot claimed by %s' % (sectionIndex, enemyIndex, avId))
        if sectionIndex < 0 or sectionIndex >= len(self.sectionsSelected):
            self.air.writeServerEvent('warning', sectionIndex, 'TwoDGameAI.claimEnemyShot sectionIndex out of range.')
            return
        if enemyIndex < 0 or enemyIndex >= len(self.sectionsSelected[sectionIndex][1]):
            self.air.writeServerEvent('warning', enemyIndex, 'TwoDGameAI.claimEnemyShot enemyIndex out of range.')
            return
        if self.enemyHealthTable[sectionIndex][enemyIndex] > 0:
            self.enemyHealthTable[sectionIndex][enemyIndex] -= ToonBlitzGlobals.DamagePerBullet
            if self.enemyHealthTable[sectionIndex][enemyIndex] <= 0:
                self.numEnemiesKilled += 1
            self.sendUpdate('setEnemyShot', [avId,
             sectionIndex,
             enemyIndex,
             self.enemyHealthTable[sectionIndex][enemyIndex]])

    def reportDone(self):
        if self.gameFSM.getCurrentState() == None or self.gameFSM.getCurrentState().getName() != 'play':
            return
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('reportDone: avatar %s is done' % avId)
        self.doneBarrier.clear(avId)
        return

    def toonVictory(self, avId, timestamp):
        if self.gameFSM.getCurrentState() == None or self.gameFSM.getCurrentState().getName() != 'play':
            msg = 'TwoDGameAI.toonVictory not in play state!'
            self.notify.warning('suspicious: ' + str(avId) + ' ' + msg)
            self.air.writeServerEvent('suspicious: ', avId, msg)
            return
        if avId not in self.scoreDict.keys():
            self.notify.warning('Avatar %s not in list.' % avId)
            self.air.writeServerEvent('suspicious: ', avId, 'TwoDGameAI.toonVictory toon not in list.')
            return
        curTime = self.getCurrentGameTime()
        timeLeft = ToonBlitzGlobals.GameDuration[self.getSafezoneId()] - curTime
        self.notify.debug('curTime =%s timeLeft = %s' % (curTime, timeLeft))
        addBonus = int(ToonBlitzGlobals.BaseBonusOnCompletion[self.getSafezoneId()] + ToonBlitzGlobals.BonusPerSecondLeft * timeLeft)
        self.notify.debug('addBOnus = %d' % addBonus)
        if addBonus < 0:
            addBonus = 0
        self.finishedBonusDict[avId] = addBonus
        timeLeftStr = '%.1f' % timeLeft
        self.finishedTimeLeftDict[avId] = timeLeftStr
        self.scoreDict[avId] += addBonus
        self.sendUpdate('addVictoryScore', [avId, addBonus])
        self.doneBarrier.clear(avId)
        return

    def toonFellDown(self, avId, timestamp):
        if avId not in self.scoreDict.keys():
            self.notify.warning('Avatar %s not in list.' % avId)
            self.air.writeServerEvent('warning', avId, 'TwoDGameAI.toonFellDown toon not in list.')
            return
        self.numFallDownDict[avId] += 1
        self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerFallDown[self.getSafezoneId()]

    def toonHitByEnemy(self, avId, timestamp):
        if avId not in self.scoreDict.keys():
            self.notify.warning('Avatar %s not in list.' % avId)
            self.air.writeServerEvent('warning', avId, 'TwoDGameAI.toonHitByEnemy toon not in list.')
            return
        self.numHitByEnemyDict[avId] += 1
        self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerEnemyCollision[self.getSafezoneId()]

    def toonSquished(self, avId, timestamp):
        if avId not in self.scoreDict.keys():
            self.notify.warning('Avatar %s not in list.' % avId)
            self.air.writeServerEvent('warning', avId, 'TwoDGameAI.toonSquished toon not in list.')
            return
        self.numSquishDict[avId] += 1
        self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerStomperSquish[self.getSafezoneId()]

    def setupSections(self):
        szId = self.getSafezoneId()
        sectionWeights = ToonBlitzGlobals.SectionWeights[szId]
        numSections = ToonBlitzGlobals.NumSections[szId]
        difficultyPool = []
        difficultyList = []
        sectionsPool = ToonBlitzGlobals.SectionsPool
        sectionTypes = ToonBlitzGlobals.SectionTypes
        sectionsPoolByDifficulty = [[],
         [],
         [],
         [],
         [],
         []]
        sectionsSelectedByDifficulty = [[],
         [],
         [],
         [],
         [],
         []]
        sectionIndicesSelected = []
        for weight in sectionWeights:
            difficulty, probability = weight
            difficultyPool += [difficulty] * probability

        for i in xrange(numSections):
            difficulty = random.choice(difficultyPool)
            difficultyList.append(difficulty)

        difficultyList.sort()
        for sectionIndex in sectionsPool:
            difficulty = sectionTypes[sectionIndex][0]
            sectionsPoolByDifficulty[difficulty] += [sectionIndex]

        for targetDifficulty in difficultyList:
            whileCount = 0
            difficulty = targetDifficulty
            while not len(sectionsPoolByDifficulty[difficulty]) > 0:
                difficulty += 1
                if difficulty >= 5:
                    difficulty = 0
                    whileCount += 1
                    if whileCount > 1:
                        break
            else:
                sectionIndexChoice = random.choice(sectionsPoolByDifficulty[difficulty])
                sectionsSelectedByDifficulty[difficulty] += [sectionIndexChoice]
                sectionsPoolByDifficulty[difficulty].remove(sectionIndexChoice)

            if whileCount > 1:
                self.notify.debug('We need more sections than we have choices. We have to now repeat.')

        for i in xrange(len(sectionsSelectedByDifficulty)):
            for j in xrange(len(sectionsSelectedByDifficulty[i])):
                sectionIndicesSelected.append(sectionsSelectedByDifficulty[i][j])

        for i in xrange(len(sectionIndicesSelected)):
            sectionIndex = sectionIndicesSelected[i]
            self.sectionIndexList.append(sectionIndex)
            attribs = sectionTypes[sectionIndex]
            difficulty = attribs[0]
            length = attribs[1]
            blocksPool = attribs[2]
            enemiesPool = attribs[3]
            treasuresPool = attribs[4]
            spawnPointsPool = attribs[5]
            stompersPool = attribs[6]
            enemyIndicesPool = []
            enemyIndicesSelected = []
            if enemiesPool != None:
                minEnemies, maxEnemies = attribs[7]
                for i in xrange(len(enemiesPool)):
                    enemyIndicesPool += [i]

                numEnemies = maxEnemies * ToonBlitzGlobals.PercentMaxEnemies[szId] / 100
                numEnemies = max(numEnemies, minEnemies)
                for j in xrange(int(numEnemies)):
                    if len(enemyIndicesPool) == 0:
                        break
                    enemyIndex = random.choice(enemyIndicesPool)
                    enemyIndicesSelected.append(enemyIndex)
                    enemyIndicesPool.remove(enemyIndex)

                enemyIndicesSelected.sort()
            treasureIndicesPool = []
            treasureValuePool = []
            for value in xrange(1, 5):
                treasureValuePool += [value] * ToonBlitzGlobals.TreasureValueProbability[value]

            treasureIndicesSelected = []
            if treasuresPool != None:
                minTreasures, maxTreasures = attribs[8]
                for i in xrange(len(treasuresPool)):
                    treasureIndicesPool += [i]

                numTreasures = maxTreasures * ToonBlitzGlobals.PercentMaxTreasures[szId] / 100
                numTreasures = max(numTreasures, minTreasures)
                for i in xrange(int(numTreasures)):
                    if len(treasureIndicesPool) == 0:
                        break
                    treasureIndex = random.choice(treasureIndicesPool)
                    treasureValue = random.choice(treasureValuePool)
                    treasure = (treasureIndex, treasureValue)
                    treasureIndicesPool.remove(treasureIndex)
                    treasureIndicesSelected.append(treasure)

                treasureIndicesSelected.sort()
            spawnPointIndicesPool = []
            spawnPointIndicesSelected = []
            if spawnPointsPool != None:
                minSpawnPoints, maxSpawnPoints = attribs[9]
                for i in xrange(len(spawnPointsPool)):
                    spawnPointIndicesPool += [i]

                numSpawnPoints = maxSpawnPoints * ToonBlitzGlobals.PercentMaxSpawnPoints[szId] / 100
                numSpawnPoints = max(numSpawnPoints, minSpawnPoints)
                for i in xrange(int(numSpawnPoints)):
                    if len(spawnPointIndicesPool) == 0:
                        break
                    spawnPoint = random.choice(spawnPointIndicesPool)
                    spawnPointIndicesSelected.append(spawnPoint)
                    spawnPointIndicesPool.remove(spawnPoint)

                spawnPointIndicesSelected.sort()
            stomperIndicesPool = []
            stomperIndicesSelected = []
            if stompersPool != None:
                minStompers, maxStompers = attribs[10]
                for i in xrange(len(stompersPool)):
                    stomperIndicesPool += [i]

                numStompers = maxStompers * ToonBlitzGlobals.PercentMaxStompers[szId] / 100
                numStompers = max(numStompers, minStompers)
                for i in xrange(int(numStompers)):
                    if len(stomperIndicesPool) == 0:
                        break
                    stomper = random.choice(stomperIndicesPool)
                    stomperIndicesSelected.append(stomper)
                    stomperIndicesPool.remove(stomper)

                stomperIndicesSelected.sort()
            sctionTuple = (sectionIndex,
             enemyIndicesSelected,
             treasureIndicesSelected,
             spawnPointIndicesSelected,
             stomperIndicesSelected)
            self.sectionsSelected.append(sctionTuple)

        return

    def getSectionsSelected(self):
        return self.sectionsSelected