import random

from TrolleyConstants import *
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObjectAI
from direct.distributed.ClockDelta import *
from direct.fsm import ClassicFSM, State
from otp.ai.AIBase import *
from toontown.golf import GolfGlobals
from toontown.golf import GolfManagerAI
from toontown.toonbase.ToontownGlobals import *


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

    def __init__(self, air, golfCourse, x, y, z, h, p, r):
        DistributedObjectAI.DistributedObjectAI.__init__(self, air)
        self.seats = [None, None, None, None]
        self.golfCourse = golfCourse
        self.posHpr = (x, y, z, h, p, r)
        self.chooseColor()
        self.accepting = 0
        self.trolleyCountdownTime = simbase.config.GetFloat(
            'trolley-countdown-time', TROLLEY_COUNTDOWN_TIME)
        self.fsm = ClassicFSM.ClassicFSM(
            'DistributedGolfKartAI',
            [
                State.State('off', self.enterOff, self.exitOff, ['entering']),
                State.State('entering', self.enterEntering, self.exitEntering, ['waitEmpty']),
                State.State('waitEmpty', self.enterWaitEmpty, self.exitWaitEmpty, ['waitCountdown']),
                State.State('waitCountdown', self.enterWaitCountdown, self.exitWaitCountdown, ['waitEmpty', 'allAboard']),
                State.State('allAboard', self.enterAllAboard, self.exitAllAboard, ['leaving', 'waitEmpty']),
                State.State('leaving', self.enterLeaving, self.exitLeaving, ['entering'])
            ], 'off', 'off')
        self.fsm.enterInitialState()

    def delete(self):
        self.fsm.requestFinalState()
        del self.fsm
        DistributedObjectAI.DistributedObjectAI.delete(self)

    def findAvailableSeat(self):
        for i in xrange(len(self.seats)):
            if self.seats[i] is None:
                return i

    def findAvatar(self, avId):
        for i in xrange(len(self.seats)):
            if self.seats[i] == avId:
                return i

    def countFullSeats(self):
        avCounter = 0
        for i in self.seats:
            if i:
                avCounter += 1
        return avCounter

    def rejectingBoardersHandler(self, avId):
        self.rejectBoarder(avId)

    def rejectBoarder(self, avId):
        self.sendUpdateToAvatarId(avId, 'rejectBoard', [avId])

    def acceptingBoardersHandler(self, avId):
        self.notify.debug('acceptingBoardersHandler')
        seatIndex = self.findAvailableSeat()
        if seatIndex is None:
            self.rejectBoarder(avId)
        else:
            self.acceptBoarder(avId, seatIndex)

    def acceptBoarder(self, avId, seatIndex):
        self.notify.debug('acceptBoarder')
        if self.findAvatar(avId) is not None:
            return
        self.seats[seatIndex] = avId
        self.acceptOnce(
            self.air.getAvatarExitEvent(avId),
            self.__handleUnexpectedExit, extraArgs=[avId])
        self.timeOfBoarding = globalClock.getRealTime()
        self.sendUpdate('fillSlot' + str(seatIndex), [avId])
        self.waitCountdown()

    def __handleUnexpectedExit(self, avId):
        self.notify.warning('Avatar: ' + str(avId) + ' has exited unexpectedly')
        seatIndex = self.findAvatar(avId)
        if seatIndex is None:
            return
        self.clearFullNow(seatIndex)
        self.clearEmptyNow(seatIndex)
        if self.countFullSeats() == 0:
            self.waitEmpty()

    def rejectingExitersHandler(self, avId):
        self.rejectExiter(avId)

    def rejectExiter(self, avId):
        pass

    def acceptingExitersHandler(self, avId):
        self.acceptExiter(avId)

    def acceptExiter(self, avId):
        seatIndex = self.findAvatar(avId)
        if seatIndex is None:
            return
        self.clearFullNow(seatIndex)
        self.sendUpdate('emptySlot' + str(seatIndex), [avId, globalClockDelta.getRealNetworkTime()])
        if self.countFullSeats() == 0:
            self.waitEmpty()
        taskMgr.doMethodLater(
            TOON_EXIT_TIME, self.clearEmptyNow,
            self.uniqueName('clearEmpty-%s' % seatIndex),
            extraArgs=(seatIndex,))

    def clearEmptyNow(self, seatIndex):
        self.sendUpdate('emptySlot' + str(seatIndex), [0, globalClockDelta.getRealNetworkTime()])

    def clearFullNow(self, seatIndex):
        avId = self.seats[seatIndex]
        if avId == 0:
            self.notify.warning('Clearing an empty seat index: ' + str(seatIndex) + ' ... Strange...')
        else:
            self.seats[seatIndex] = None
            self.sendUpdate('fillSlot' + str(seatIndex), [0])
            if avId:
                self.ignore(self.air.getAvatarExitEvent(avId))

    def d_setState(self, state):
        self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()])

    def getState(self):
        return self.fsm.getCurrentState().getName()

    def requestBoard(self, *args):
        self.notify.debug('requestBoard')
        avId = self.air.getAvatarIdFromSender()
        if self.findAvatar(avId) != None:
            self.notify.warning('Ignoring multiple requests from %s to board.' % avId)
            return None
        av = self.air.doId2do.get(avId)
        if av:
            newArgs = (avId,) + args
            if av.hp > 0 and self.accepting:
                self.acceptingBoardersHandler(*newArgs)
            else:
                self.rejectingBoardersHandler(*newArgs)
        else:
            self.notify.warning('avid: %s does not exist, but tried to board a trolley' % avId)

    def requestExit(self, *args):
        self.notify.debug('requestExit')
        avId = self.air.getAvatarIdFromSender()
        av = self.air.doId2do.get(avId)
        if av:
            newArgs = (avId,) + args
            if self.accepting:
                self.acceptingExitersHandler(*newArgs)
            else:
                self.rejectingExitersHandler(*newArgs)
        else:
            self.notify.warning('avId: %s does not exist, but tried to exit a trolley' % avId)

    def start(self):
        self.enter()

    def enterOff(self):
        self.accepting = 0
        if hasattr(self, 'doId'):
            for seatIndex in xrange(4):
                taskMgr.remove(self.uniqueName('clearEmpty-' + str(seatIndex)))

    def exitOff(self):
        self.accepting = 0

    def enter(self):
        self.fsm.request('entering')

    def enterEntering(self):
        self.d_setState('entering')
        self.accepting = 0
        self.seats = [None, None, None, None]
        taskMgr.doMethodLater(
            TROLLEY_ENTER_TIME, self.waitEmptyTask,
            self.uniqueName('entering-timer'))

    def exitEntering(self):
        self.accepting = 0
        taskMgr.remove(self.uniqueName('entering-timer'))

    def waitEmptyTask(self, task):
        self.waitEmpty()
        return Task.done

    def waitEmpty(self):
        self.fsm.request('waitEmpty')

    def enterWaitEmpty(self):
        self.d_setState('waitEmpty')
        self.accepting = 1

    def exitWaitEmpty(self):
        self.accepting = 0

    def waitCountdown(self):
        self.fsm.request('waitCountdown')

    def enterWaitCountdown(self):
        self.d_setState('waitCountdown')
        self.accepting = 1
        taskMgr.doMethodLater(
            self.trolleyCountdownTime, self.timeToGoTask,
            self.uniqueName('countdown-timer'))

    def timeToGoTask(self, task):
        if self.countFullSeats() > 0:
            self.allAboard()
        else:
            self.waitEmpty()
        return Task.done

    def exitWaitCountdown(self):
        self.accepting = 0
        taskMgr.remove(self.uniqueName('countdown-timer'))

    def allAboard(self):
        self.fsm.request('allAboard')

    def enterAllAboard(self):
        self.accepting = 0
        currentTime = globalClock.getRealTime()
        elapsedTime = currentTime - self.timeOfBoarding
        self.notify.debug('elapsed time: ' + str(elapsedTime))
        waitTime = max(TOON_BOARD_TIME - elapsedTime, 0)
        taskMgr.doMethodLater(
            waitTime, self.leaveTask, self.uniqueName('waitForAllAboard'))

    def exitAllAboard(self):
        self.accepting = 0
        taskMgr.remove(self.uniqueName('waitForAllAboard'))

    def leaveTask(self, task):
        if self.countFullSeats() > 0:
            self.leave()
        else:
            self.waitEmpty()
        return Task.done

    def leave(self):
        self.fsm.request('leaving')

    def enterLeaving(self):
        self.d_setState('leaving')
        self.accepting = 0
        taskMgr.doMethodLater(
            TROLLEY_EXIT_TIME, self.trolleyLeftTask,
            self.uniqueName('leaving-timer'))

    def trolleyLeftTask(self, task):
        self.trolleyLeft()
        return Task.done

    def trolleyLeft(self):
        numPlayers = self.countFullSeats()
        avIdList = []
        if numPlayers > 0:
            for seatIndex in xrange(len(self.seats)):
                avId = self.seats[seatIndex]
                avIdList.append(avId)
                self.clearFullNow(seatIndex)
            golfManager = GolfManagerAI.GolfManagerAI()
            golfZone = golfManager.readyGolfCourse(avIdList, self.golfCourse)
            for avId in avIdList:
                if avId:
                    self.sendUpdateToAvatarId(avId, 'setGolfZone', [golfZone, 0])
        else:
            self.notify.warning('The trolley left, but was empty.')
        self.enter()

    def exitLeaving(self):
        self.accepting = 0
        self.chooseColor()
        self.sendUpdate('setColor', [self.color[0], self.color[1], self.color[2]])
        taskMgr.remove(self.uniqueName('leaving-timer'))

    def getGolfCourse(self):
        return self.golfCourse

    def getPosHpr(self):
        return self.posHpr

    def getColor(self):
        return self.color

    def chooseColor(self):
        self.color = (
            random.randint(
                GolfGlobals.KartColors[self.golfCourse][0][0],
                GolfGlobals.KartColors[self.golfCourse][0][1]
            ),
            random.randint(
                GolfGlobals.KartColors[self.golfCourse][1][0],
                GolfGlobals.KartColors[self.golfCourse][1][1]
            ),
            random.randint(
                GolfGlobals.KartColors[self.golfCourse][2][0],
                GolfGlobals.KartColors[self.golfCourse][2][1]
            )
        )
        if self.golfCourse == 1:
            if self.color[0] + self.color[1] <= 255:
                self.color = (
                    self.color[0],
                    self.color[0] + self.color[1],
                    self.color[2]
                )
            else:
                self.color = (self.color[0], 255, self.color[2])