from direct.distributed import DistributedObjectAI
from direct.directnotify import DirectNotifyGlobal
from toontown.toonbase import ToontownGlobals
from otp.otpbase.PythonUtil import nonRepeatingRandomList
import DistributedGagAI, DistributedProjectileAI
from direct.task import Task
import random, time, Racer, RaceGlobals
from direct.distributed.ClockDelta import *

class DistributedRaceAI(DistributedObjectAI.DistributedObjectAI):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedRaceAI')

    def __init__(self, air, trackId, zoneId, avIds, laps, raceType, racerFinishedFunc, raceDoneFunc, circuitLoop, circuitPoints, circuitTimes, qualTimes=[], circuitTimeList={}, circuitTotalBonusTickets={}):
        DistributedObjectAI.DistributedObjectAI.__init__(self, air)
        self.trackId = trackId
        self.direction = self.trackId % 2
        self.zoneId = zoneId
        self.racers = {}
        self.avIds = []
        self.kickedAvIds = []
        self.circuitPoints = circuitPoints
        self.circuitTimes = circuitTimes
        self.finishPending = []
        self.flushPendingTask = None
        self.kickSlowRacersTask = None
        for avId in avIds:
            if avId and avId in self.air.doId2do:
                self.avIds.append(avId)
                self.racers[avId] = Racer.Racer(self, air, avId, zoneId)

        self.toonCount = len(self.racers)
        self.startingPlaces = nonRepeatingRandomList(self.toonCount, 4)
        self.thrownGags = []
        self.ready = False
        self.setGo = False
        self.racerFinishedFunc = racerFinishedFunc
        self.raceDoneFunc = raceDoneFunc
        self.lapCount = laps
        self.raceType = raceType
        if raceType == RaceGlobals.Practice:
            self.gagList = []
        else:
            self.gagList = [
             0] * len(RaceGlobals.TrackDict[trackId][4])
        self.circuitLoop = circuitLoop
        self.qualTimes = qualTimes
        self.circuitTimeList = circuitTimeList
        self.qualTimes.append(RaceGlobals.TrackDict[trackId][1])
        self.circuitTotalBonusTickets = circuitTotalBonusTickets
        return

    def generate(self):
        DistributedObjectAI.DistributedObjectAI.generate(self)
        self.notify.debug('generate %s, id=%s, ' % (self.doId, self.trackId))
        trackFilepath = RaceGlobals.TrackDict[self.trackId][0]
        taskMgr.doMethodLater(0.5, self.enableEntryBarrier, 'enableWaitingBarrier')

    def enableEntryBarrier(self, task):
        self.enterRaceBarrier = self.beginBarrier('waitingForJoin', self.avIds, 60, self.b_racersJoined)
        self.notify.debug('Waiting for Joins!!!!')
        self.sendUpdate('waitingForJoin', [])

    def removeObject(self, object):
        if object:
            self.notify.debug('deleting object: %s' % object.doId)
            object.requestDelete()

    def requestDelete(self, lastRace=True):
        self.notify.debug('requestDelete: %s' % self.doId)
        self.ignoreBarrier('waitingForExit')
        for i in self.thrownGags:
            i.requestDelete()

        del self.thrownGags
        if lastRace:
            for i in self.racers:
                racer = self.racers[i]
                self.ignore(racer.exitEvent)
                if racer.kart:
                    racer.kart.requestDelete()
                    racer.kart = None
                if racer.avatar:
                    racer.avatar.kart = None
                    racer.avatar = None

        self.racers = {}
        if self.flushPendingTask:
            taskMgr.remove(self.flushPendingTask)
            self.flushPendingTask = None
        if self.kickSlowRacersTask:
            taskMgr.remove(self.kickSlowRacersTask)
            self.kickSlowRacersTask = None
        DistributedObjectAI.DistributedObjectAI.requestDelete(self)
        return

    def delete(self):
        self.notify.debug('delete: %s' % self.doId)
        DistributedObjectAI.DistributedObjectAI.delete(self)
        del self.raceDoneFunc
        del self.racerFinishedFunc

    def getTaskZoneId(self):
        return self.zoneId

    def allToonsGone(self):
        self.notify.debug('allToonsGone')
        self.requestDelete()

    def getZoneId(self):
        return self.zoneId

    def getTrackId(self):
        return self.trackId

    def getRaceType(self):
        return self.raceType

    def getCircuitLoop(self):
        return self.circuitLoop

    def getAvatars(self):
        avIds = []
        for i in self.racers:
            avIds.append(i)

        return avIds

    def getStartingPlaces(self):
        return self.startingPlaces

    def getLapCount(self):
        return self.lapCount

    def requestKart(self):
        avId = self.air.getAvatarIdFromSender()
        if avId in self.racers:
            kart = self.racers[avId].kart
            if kart:
                kart.request('Controlled', avId)

    def b_racersJoined(self, avIds):
        self.ignoreBarrier('waitingForJoin')
        racersOut = []
        for i in self.avIds:
            if i not in avIds:
                racersOut.append(i)

        if len(avIds) == 0:
            self.exitBarrier = self.beginBarrier('waitingForExit', self.avIds, 10, self.endRace)
            for i in self.avIds:
                self.d_kickRacer(i)

            return
        for i in racersOut:
            self.d_kickRacer(i)

        self.avIds = avIds
        self.waitingForPrepBarrier = self.beginBarrier('waitingForPrep', self.avIds, 30, self.b_prepForRace)
        avAndKarts = []
        for i in self.racers:
            avAndKarts.append([self.racers[i].avId, self.racers[i].kart.doId])

        self.sendUpdate('setEnteredRacers', [avAndKarts])

    def b_prepForRace(self, avIds):
        self.notify.debug('Prepping!!!')
        self.ignoreBarrier('waitingForPrep')
        racersOut = []
        for i in self.avIds:
            if i not in avIds:
                racersOut.append(i)

        if len(avIds) == 0:
            self.exitBarrier = self.beginBarrier('waitingForExit', self.avIds, 10, self.endRace)
        for i in racersOut:
            self.d_kickRacer(i)

        if len(avIds) == 0:
            return
        self.avIds = avIds
        for i in range(len(self.gagList)):
            self.d_genGag(i)

        self.waitingForReadyBarrier = self.beginBarrier('waitingForReady', self.avIds, 20, self.b_startTutorial)
        self.sendUpdate('prepForRace', [])

    def b_startTutorial(self, avIds):
        self.ignoreBarrier('waitingForReady')
        racersOut = []
        for i in self.avIds:
            if i not in avIds:
                racersOut.append(i)

        if len(avIds) == 0:
            self.exitBarrier = self.beginBarrier('waitingForExit', self.avIds, 10, self.endRace)
        for i in racersOut:
            self.d_kickRacer(i)

        if len(avIds) == 0:
            return
        for avId in avIds:
            av = self.air.doId2do.get(avId, None)
            if not av:
                self.notify.warning('b_racersJoined: Avatar not found with id %s' % avId)
            elif not self.raceType == RaceGlobals.Practice:
                if self.isCircuit() and not self.isFirstRace():
                    continue
                raceFee = RaceGlobals.getEntryFee(self.trackId, self.raceType)
                avTickets = av.getTickets()
                if avTickets < raceFee:
                    self.notify.warning('b_racersJoined: Avatar %s does not own enough tickets for the race!')
                    av.b_setTickets(0)
                else:
                    av.b_setTickets(avTickets - raceFee)

        self.avIds = avIds
        self.readRulesBarrier = self.beginBarrier('readRules', self.avIds, 10, self.b_startRace)
        self.sendUpdate('startTutorial', [])
        return

    def b_startRace(self, avIds):
        self.ignoreBarrier('readRules')
        if self.isDeleted():
            return
        self.notify.debug('Going!!!!!!')
        self.ignoreBarrier(self.waitingForReadyBarrier)
        self.toonCount = len(self.avIds)
        self.baseTime = globalClock.getFrameTime() + 0.5 + RaceGlobals.RaceCountdown
        for i in self.racers:
            self.racers[i].baseTime = self.baseTime

        self.sendUpdate('startRace', [globalClockDelta.localToNetworkTime(self.baseTime)])
        qualTime = RaceGlobals.getQualifyingTime(self.trackId)
        timeout = qualTime + 60 + 3
        self.kickSlowRacersTask = taskMgr.doMethodLater(timeout, self.kickSlowRacers, 'kickSlowRacers')

    def kickSlowRacers(self, task):
        self.kickSlowRacersTask = None
        if self.isDeleted():
            return
        for racer in self.racers.values():
            avId = racer.avId
            av = simbase.air.doId2do.get(avId, None)
            if av and not av.allowRaceTimeout:
                continue
            if not racer.finished and avId not in self.kickedAvIds:
                self.notify.info('Racer %s timed out - kicking.' % racer.avId)
                self.d_kickRacer(avId, RaceGlobals.Exit_Slow)
                self.ignore(racer.exitEvent)
                racer.exited = True
                racer.finished = True
                taskMgr.doMethodLater(10, self.removeObject, 'removeKart-%s' % racer.kart.doId, extraArgs=[racer.kart])
                taskMgr.remove('make %s invincible' % avId)
                self.racers[avId].anvilTarget = True

        self.checkForEndOfRace()
        return

    def d_kickRacer(self, avId, reason=RaceGlobals.Exit_Barrier):
        if avId not in self.kickedAvIds:
            self.kickedAvIds.append(avId)
            if self.isCircuit() and not self.isFirstRace() and reason == RaceGlobals.Exit_Barrier:
                reason = RaceGlobals.Exit_BarrierNoRefund
            self.sendUpdate('goToSpeedway', [self.kickedAvIds, reason])

    def d_genGag(self, slot):
        index = random.randint(0, 5)
        self.gagList[slot] = index
        pos = slot
        self.sendUpdate('genGag', [slot, pos, index])

    def d_dropAnvil(self, ownerId):
        possibleTargets = []
        for i in self.racers:
            if not self.racers[i].anvilTarget:
                possibleTargets.append(self.racers[i])

        while len(possibleTargets) > 1:
            if possibleTargets[0].lapT <= possibleTargets[1].lapT:
                possibleTargets = possibleTargets[1:]
            else:
                possibleTargets = possibleTargets[1:] + possibleTargets[:1]

        if len(possibleTargets):
            id = possibleTargets[0].avId
            if id != ownerId:
                possibleTargets[0].anvilTarget = True
                taskMgr.doMethodLater(4, setattr, 'make %s invincible' % id, extraArgs=[self.racers[id], 'anvilTarget', False])
            self.sendUpdate('dropAnvilOn', [ownerId, id, globalClockDelta.getFrameNetworkTime()])

    def d_makeBanana(self, avId, x, y, z):
        gag = DistributedGagAI.DistributedGagAI(simbase.air, avId, self, 3, x, y, z, 0)
        self.thrownGags.append(gag)
        gag.generateWithRequired(self.zoneId)

    def d_launchPie(self, avId):
        ownerRacer = simbase.air.doId2do.get(avId, None)
        targetId = 0
        type = 0
        targetDist = 10000
        for iiId in self.racers:
            targetRacer = simbase.air.doId2do.get(iiId, None)
            if not (targetRacer and targetRacer.kart and ownerRacer and ownerRacer.kart):
                continue
            if targetRacer.kart.getPos(ownerRacer.kart)[1] < 500 and targetRacer.kart.getPos(ownerRacer.kart)[1] >= 0 and abs(targetRacer.kart.getPos(ownerRacer.kart)[0]) < 50 and avId != iiId and targetDist > targetRacer.kart.getPos(ownerRacer.kart)[1]:
                targetId = iiId
                targetDist = targetRacer.kart.getPos(ownerRacer.kart)[1]

        if targetId == 0:
            for iiId in self.racers:
                targetRacer = simbase.air.doId2do.get(iiId, None)
                if not (targetRacer and targetRacer.kart and ownerRacer and ownerRacer.kart):
                    continue
                if targetRacer.kart.getPos(ownerRacer.kart)[1] > -80 and targetRacer.kart.getPos(ownerRacer.kart)[1] <= 0 and abs(targetRacer.kart.getPos(ownerRacer.kart)[0]) < 50 and avId != iiId:
                    targetId = iiId

        self.sendUpdate('shootPiejectile', [avId, targetId, type])
        return

    def d_makePie(self, avId, x, y, z):
        gag = DistributedProjectileAI.DistributedProjectileAI(simbase.air, self, avId)
        self.thrownGags.append(gag)
        gag.generateWithRequired(self.zoneId)

    def endRace(self, avIds):
        if hasattr(self, 'raceDoneFunc'):
            self.raceDoneFunc(self, False)

    def racerLeft(self, avIdFromClient):
        avId = self.air.getAvatarIdFromSender()
        if self.racers.has_key(avId) and avId == avIdFromClient:
            self.notify.debug('Removing %d from race %d' % (avId, self.doId))
            racer = self.racers[avId]
            taskMgr.doMethodLater(10, self.removeObject, racer.kart.uniqueName('removeIt'), extraArgs=[racer.kart])
            if racer.avatar:
                racer.avatar.kart = None
            self.racers[avId].exited = True
            taskMgr.remove('make %s invincible' % id)
            self.racers[avId].anvilTarget = True
            raceDone = True
            for i in self.racers:
                if not self.racers[i].exited:
                    raceDone = False

            if raceDone:
                self.notify.debug('race over, sending callback to raceMgr')
                self.raceDoneFunc(self)
            if avId in self.finishPending:
                self.finishPending.remove(avId)
        return

    def hasGag(self, slot, type, index):
        avId = self.air.getAvatarIdFromSender()
        if avId in self.racers:
            if self.racers[avId].hasGag:
                return
            if self.gagList[slot] == index:
                self.gagList[slot] = None
                taskMgr.doMethodLater(5, self.d_genGag, 'remakeGag-' + str(slot), extraArgs=[slot])
                self.racers[avId].hasGag = True
                self.racers[avId].gagType = type
            else:
                return
        return

    def requestThrow(self, x, y, z):
        avId = self.air.getAvatarIdFromSender()
        if avId in self.racers:
            racer = self.racers[avId]
            if racer.hasGag:
                if racer.gagType == 1:
                    self.d_makeBanana(avId, x, y, z)
                if racer.gagType == 2:
                    pass
                if racer.gagType == 3:
                    self.d_dropAnvil(avId)
                if racer.gagType == 4:
                    self.d_launchPie(avId)
                racer.hasGag = False
                racer.gagType = 0

    def heresMyT(self, inputAvId, numLaps, t, timestamp):
        avId = self.air.getAvatarIdFromSender()
        if avId in self.racers and avId == inputAvId:
            me = self.racers[avId]
            me.setLapT(numLaps, t, timestamp)
            if me.maxLap == self.lapCount and not me.finished:
                me.finished = True
                taskMgr.remove('make %s invincible' % id)
                me.anvilTarget = True
                someoneIsClose = False
                for racer in self.racers.values():
                    if not racer.exited and not racer.finished:
                        if me.lapT - racer.lapT < 0.15:
                            someoneIsClose = True
                            break

                index = 0
                for racer in self.finishPending:
                    if me.totalTime < racer.totalTime:
                        break
                    index += 1

                self.finishPending.insert(index, me)
                if self.flushPendingTask:
                    taskMgr.remove(self.flushPendingTask)
                    self.flushPendingTask = None
                if someoneIsClose:
                    task = taskMgr.doMethodLater(3, self.flushPending, self.uniqueName('flushPending'))
                    self.flushPendingTask = task
                else:
                    self.flushPending()
        return

    def flushPending(self, task=None):
        for racer in self.finishPending:
            self.racerFinishedFunc(self, racer)

        self.finishPending = []
        self.flushPendingTask = None
        return

    def d_setPlace(self, avId, totalTime, place, entryFee, qualify, winnings, bonus, trophies, circuitPoints, circuitTime):
        self.sendUpdate('setPlace', [avId, totalTime, place, entryFee, qualify, winnings, bonus, trophies, circuitPoints, circuitTime])

    def d_setCircuitPlace(self, avId, place, entryFee, winnings, bonus, trophies):
        self.sendUpdate('setCircuitPlace', [avId, place, entryFee, winnings, bonus, trophies])

    def d_endCircuitRace(self):
        self.sendUpdate('endCircuitRace')

    def unexpectedExit(self, avId):
        self.notify.debug('racer disconnected: %s' % avId)
        racer = self.racers.get(avId, None)
        if racer:
            self.sendUpdate('racerDisconnected', [avId])
            self.ignore(racer.exitEvent)
            racer.exited = True
            taskMgr.doMethodLater(10, self.removeObject, 'removeKart-%s' % racer.kart.doId, extraArgs=[racer.kart])
            taskMgr.remove('make %s invincible' % id)
            self.racers[avId].anvilTarget = True
            self.checkForEndOfRace()
        return

    def checkForEndOfRace(self):
        if self.isCircuit() and self.everyoneDone():
            simbase.air.raceMgr.endCircuitRace(self)
        raceOver = True
        for i in self.racers:
            if not self.racers[i].exited:
                raceOver = False

        if raceOver:
            self.raceDoneFunc(self)

    def sendToonsToNextCircuitRace(self, raceZone, trackId):
        for avId in self.avIds:
            self.notify.debug('Handling Circuit Race transisiton for avatar %s' % avId)
            self.sendUpdateToAvatarId(avId, 'setRaceZone', [raceZone, trackId])

    def isCircuit(self):
        return self.raceType == RaceGlobals.Circuit

    def isLastRace(self):
        return len(self.circuitLoop) == 0

    def isFirstRace(self):
        return len(self.circuitLoop) == 2

    def everyoneDone(self):
        done = True
        for racer in self.racers.values():
            if not racer.exited and racer.avId not in self.playersFinished and racer.avId not in self.kickedAvIds:
                done = False
                break

        return done
