from panda3d.core import *
from direct.distributed.ClockDelta import *
from direct.interval.IntervalGlobal import *
from ElevatorConstants import *
from ElevatorUtils import *
from direct.showbase import PythonUtil
from direct.directnotify import DirectNotifyGlobal
from direct.fsm import ClassicFSM
from direct.distributed import DistributedObject
from direct.fsm import State
from toontown.toonbase import TTLocalizer
from toontown.toonbase import ToontownGlobals
from direct.task.Task import Task
from toontown.hood import ZoneUtil
from direct.fsm.FSM import FSM

class DistributedElevatorFSM(DistributedObject.DistributedObject, FSM):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedElevator')
    defaultTransitions = {'Off': ['Opening', 'Closed', 'Off'],
     'Opening': ['WaitEmpty',
                 'WaitCountdown',
                 'Opening',
                 'Closing'],
     'WaitEmpty': ['WaitCountdown', 'Closing', 'Off'],
     'WaitCountdown': ['WaitEmpty', 'AllAboard', 'Closing'],
     'AllAboard': ['WaitEmpty', 'Closing'],
     'Closing': ['Closed',
                 'WaitEmpty',
                 'Closing',
                 'Opening'],
     'Closed': ['Opening']}
    id = 0

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        FSM.__init__(self, 'Elevator_%s_FSM' % self.id)
        self.bldgRequest = None
        self.toonRequests = {}
        self.deferredSlots = []
        self.localToonOnBoard = 0
        self.boardedAvIds = {}
        self.openSfx = base.loadSfx('phase_5/audio/sfx/elevator_door_open.ogg')
        self.finalOpenSfx = None
        self.closeSfx = base.loadSfx('phase_5/audio/sfx/elevator_door_close.ogg')
        self.elevatorFSM = None
        self.finalCloseSfx = None
        self.elevatorPoints = ElevatorPoints
        self.type = ELEVATOR_NORMAL
        self.countdownTime = ElevatorData[self.type]['countdown']
        self.isSetup = 0
        self.__preSetupState = None
        self.bigElevator = 0
        self.offTrack = [None,
         None,
         None,
         None]
        self.boardingParty = None
        return

    def generate(self):
        DistributedObject.DistributedObject.generate(self)

    def setBoardingParty(self, party):
        self.boardingParty = party

    def setupElevator(self):
        collisionRadius = ElevatorData[self.type]['collRadius']
        self.elevatorSphere = CollisionSphere(0, 5, 0, collisionRadius)
        self.elevatorSphere.setTangible(0)
        self.elevatorSphereNode = CollisionNode(self.uniqueName('elevatorSphere'))
        self.elevatorSphereNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.elevatorSphereNode.addSolid(self.elevatorSphere)
        self.elevatorSphereNodePath = self.getElevatorModel().attachNewNode(self.elevatorSphereNode)
        self.elevatorSphereNodePath.hide()
        self.elevatorSphereNodePath.reparentTo(self.getElevatorModel())
        self.elevatorSphereNodePath.stash()
        self.boardedAvIds = {}
        self.openDoors = getOpenInterval(self, self.leftDoor, self.rightDoor, self.openSfx, self.finalOpenSfx, self.type)
        self.closeDoors = getCloseInterval(self, self.leftDoor, self.rightDoor, self.closeSfx, self.finalCloseSfx, self.type)
        self.openDoors = Sequence(self.openDoors, Func(self.onDoorOpenFinish))
        self.closeDoors = Sequence(self.closeDoors, Func(self.onDoorCloseFinish))
        self.finishSetup()

    def finishSetup(self):
        self.isSetup = 1
        if self.__preSetupState:
            self.request(self.__preSetupState, 0)
            self.__preSetupState = None
        for slot in self.deferredSlots:
            self.fillSlot(*slot)

        self.deferredSlots = []
        return

    def disable(self):
        for track in self.offTrack:
            if track:
                if track.isPlaying():
                    track.pause()
                    track = None

        if self.bldgRequest:
            self.cr.relatedObjectMgr.abortRequest(self.bldgRequest)
            self.bldgRequest = None
        for request in self.toonRequests.values():
            self.cr.relatedObjectMgr.abortRequest(request)

        self.toonRequests = {}
        if hasattr(self, 'openDoors'):
            self.openDoors.pause()
        if hasattr(self, 'closeDoors'):
            self.closeDoors.pause()
        self.request('off')
        DistributedObject.DistributedObject.disable(self)
        return

    def delete(self):
        for track in self.offTrack:
            if track:
                if track.isPlaying():
                    track.pause()
                    track = None

        self.ignoreAll()
        if self.isSetup:
            self.elevatorSphereNodePath.removeNode()
            del self.elevatorSphereNodePath
            del self.elevatorSphereNode
            del self.elevatorSphere
            del self.bldg
            del self.leftDoor
            del self.rightDoor
            del self.openDoors
            del self.closeDoors
        del self.openSfx
        del self.closeSfx
        self.isSetup = 0
        DistributedObject.DistributedObject.delete(self)
        return

    def setBldgDoId(self, bldgDoId):
        self.bldgDoId = bldgDoId
        self.bldgRequest = self.cr.relatedObjectMgr.requestObjects([bldgDoId], allCallback=self.gotBldg, timeout=2)

    def gotBldg(self, buildingList):
        self.bldgRequest = None
        self.bldg = buildingList[0]
        if not self.bldg:
            self.notify.error('setBldgDoId: elevator %d cannot find bldg %d!' % (self.doId, self.bldgDoId))
            return
        self.setupElevator()
        return

    def gotToon(self, index, avId, toonList):
        request = self.toonRequests.get(index)
        if request:
            del self.toonRequests[index]
            self.fillSlot(index, avId)
        else:
            self.notify.error('gotToon: already had got toon in slot %s.' % index)

    def setState(self, state, timestamp):
        if self.isSetup:
            self.request(state, globalClockDelta.localElapsedTime(timestamp))
        else:
            self.__preSetupState = state

    def fillSlot0(self, avId):
        self.fillSlot(0, avId)

    def fillSlot1(self, avId):
        self.fillSlot(1, avId)

    def fillSlot2(self, avId):
        self.fillSlot(2, avId)

    def fillSlot3(self, avId):
        self.fillSlot(3, avId)

    def fillSlot4(self, avId):
        self.fillSlot(4, avId)

    def fillSlot5(self, avId):
        self.fillSlot(5, avId)

    def fillSlot6(self, avId):
        self.fillSlot(6, avId)

    def fillSlot7(self, avId):
        self.fillSlot(7, avId)

    def fillSlot(self, index, avId):
        self.notify.debug('%s.fillSlot(%s, %s, ...)' % (self.doId, index, avId))
        request = self.toonRequests.get(index)
        if request:
            self.cr.relatedObjectMgr.abortRequest(request)
            del self.toonRequests[index]
        if avId == 0:
            pass
        elif avId not in self.cr.doId2do:
            func = PythonUtil.Functor(self.gotToon, index, avId)
            self.toonRequests[index] = self.cr.relatedObjectMgr.requestObjects([avId], allCallback=func)
        elif not self.isSetup:
            self.deferredSlots.append((index, avId))
        else:
            if avId == base.localAvatar.getDoId():
                self.localToonOnBoard = 1
                elevator = self.getPlaceElevator()
                elevator.fsm.request('boarding', [self.getElevatorModel()])
                elevator.fsm.request('boarded')
            toon = self.cr.doId2do[avId]
            toon.stopSmooth()
            toon.setZ(self.getElevatorModel(), self.getScaledPoint(index)[2])
            toon.setShadowHeight(0)
            if toon.isDisguised:
                toon.suit.loop('walk')
                animFunc = Func(toon.suit.loop, 'neutral')
            else:
                toon.setAnimState('run', 1.0)
                animFunc = Func(toon.setAnimState, 'neutral', 1.0)
            toon.headsUp(self.getElevatorModel(), apply(Point3, self.getScaledPoint(index)))
            track = Sequence(LerpPosInterval(toon, TOON_BOARD_ELEVATOR_TIME * 0.75, apply(Point3, self.getScaledPoint(index)), other=self.getElevatorModel()), LerpHprInterval(toon, TOON_BOARD_ELEVATOR_TIME * 0.25, Point3(180, 0, 0), other=self.getElevatorModel()), animFunc, name=toon.uniqueName('fillElevator'), autoPause=1)
            track.start()
            self.boardedAvIds[avId] = index

    def emptySlot0(self, avId, timestamp):
        self.emptySlot(0, avId, timestamp)

    def emptySlot1(self, avId, timestamp):
        self.emptySlot(1, avId, timestamp)

    def emptySlot2(self, avId, timestamp):
        self.emptySlot(2, avId, timestamp)

    def emptySlot3(self, avId, timestamp):
        self.emptySlot(3, avId, timestamp)

    def emptySlot4(self, avId, timestamp):
        self.emptySlot(4, avId, timestamp)

    def emptySlot5(self, avId, timestamp):
        self.emptySlot(5, avId, timestamp)

    def emptySlot6(self, avId, timestamp):
        self.emptySlot(6, avId, timestamp)

    def emptySlot7(self, avId, timestamp):
        self.emptySlot(7, avId, timestamp)

    def notifyToonOffElevator(self, toon):
        if self.cr:
            toon.setAnimState('neutral', 1.0)
            if toon == base.localAvatar:
                print 'moving the local toon off the elevator'
                doneStatus = {'where': 'exit'}
                elevator = self.getPlaceElevator()
                elevator.signalDone(doneStatus)
                self.localToonOnBoard = 0
            else:
                toon.startSmooth()
            return

    def emptySlot(self, index, avId, timestamp):
        print 'Emptying slot: %d for %d' % (index, avId)
        if avId == 0:
            pass
        elif not self.isSetup:
            newSlots = []
            for slot in self.deferredSlots:
                if slot[0] != index:
                    newSlots.append(slot)

            self.deferredSlots = newSlots
        elif avId in self.cr.doId2do:
            if hasattr(self, 'clockNode'):
                if timestamp < self.countdownTime and timestamp >= 0:
                    self.countdown(self.countdownTime - timestamp)
                else:
                    self.countdown(self.countdownTime)
            toon = self.cr.doId2do[avId]
            toon.stopSmooth()
            if toon.isDisguised:
                toon.suit.loop('walk')
                animFunc = Func(toon.suit.loop, 'neutral')
            else:
                toon.setAnimState('run', 1.0)
                animFunc = Func(toon.setAnimState, 'neutral', 1.0)
            if self.offTrack[index]:
                if self.offTrack[index].isPlaying():
                    self.offTrack[index].finish()
                    self.offTrack[index] = None
            self.offTrack[index] = Sequence(LerpPosInterval(toon, TOON_EXIT_ELEVATOR_TIME, Point3(0, -ElevatorData[self.type]['collRadius'], 0), startPos=apply(Point3, self.getScaledPoint(index)), other=self.getElevatorModel()), animFunc, Func(self.notifyToonOffElevator, toon), name=toon.uniqueName('emptyElevator'), autoPause=1)
            if avId == base.localAvatar.getDoId():
                messenger.send('exitElevator')
                scale = base.localAvatar.getScale()
                self.offTrack[index].append(Func(base.camera.setScale, scale))
            self.offTrack[index].start()
            if avId in self.boardedAvIds:
                del self.boardedAvIds[avId]
        else:
            self.notify.warning('toon: ' + str(avId) + " doesn't exist, and" + ' cannot exit the elevator!')
        return

    def handleEnterSphere(self, collEntry):
        self.notify.debug('Entering Elevator Sphere....')
        if base.localAvatar.hp > 0:
            self.cr.playGame.getPlace().detectedElevatorCollision(self)
            toon = base.localAvatar
            self.sendUpdate('requestBoard', [])

    def rejectBoard(self, avId, reason = 0):
        print 'rejectBoard %s' % reason
        if hasattr(base.localAvatar, 'elevatorNotifier'):
            if reason == REJECT_PROMOTION:
                base.localAvatar.elevatorNotifier.showMe(TTLocalizer.BossElevatorRejectMessage)
            elif reason == REJECT_BLOCKED_ROOM:
                base.localAvatar.elevatorNotifier.showMe(TTLocalizer.ElevatorBlockedRoom)
        doneStatus = {'where': 'reject'}
        elevator = self.getPlaceElevator()
        if elevator:
            elevator.signalDone(doneStatus)

    def timerTask(self, task):
        countdownTime = int(task.duration - task.time)
        timeStr = str(countdownTime)
        if self.clockNode.getText() != timeStr:
            self.clockNode.setText(timeStr)
        if task.time >= task.duration:
            return Task.done
        else:
            return Task.cont

    def countdown(self, duration):
        countdownTask = Task(self.timerTask)
        countdownTask.duration = duration
        taskMgr.remove(self.uniqueName('elevatorTimerTask'))
        return taskMgr.add(countdownTask, self.uniqueName('elevatorTimerTask'))

    def handleExitButton(self):
        self.sendUpdate('requestExit')

    def enterWaitCountdown(self, ts):
        self.elevatorSphereNodePath.unstash()
        self.accept(self.uniqueName('enterelevatorSphere'), self.handleEnterSphere)
        self.accept('elevatorExitButton', self.handleExitButton)
        self.lastState = self.state

    def exitWaitCountdown(self):
        self.elevatorSphereNodePath.stash()
        self.ignore(self.uniqueName('enterelevatorSphere'))
        self.ignore('elevatorExitButton')
        self.ignore('localToonLeft')
        taskMgr.remove(self.uniqueName('elevatorTimerTask'))
        self.clock.removeNode()
        del self.clock
        del self.clockNode

    def enterClosing(self, ts):
        if self.localToonOnBoard:
            elevator = self.getPlaceElevator()
        if self.closeDoors.isPlaying() or self.lastState == 'closed' or self.openDoors.isPlaying():
            self.doorsNeedToClose = 1
        else:
            self.doorsNeedToClose = 0
        self.closeDoors.start(ts)

    def exitClosing(self):
        pass

    def onDoorOpenFinish(self):
        pass

    def onDoorCloseFinish(self):
        for avId in self.boardedAvIds.keys():
            av = self.cr.doId2do.get(avId)
            if av is not None:
                if av.getParent().compareTo(self.getElevatorModel()) == 0:
                    av.detachNode()

        return

    def enterClosed(self, ts):
        self.__doorsClosed(self.getZoneId())

    def exitClosed(self):
        pass

    def forceDoorsOpen(self):
        openDoors(self.leftDoor, self.rightDoor)

    def forceDoorsClosed(self):
        self.closeDoors.finish()
        closeDoors(self.leftDoor, self.rightDoor)

    def enterOff(self):
        self.lastState = self.state

    def exitOff(self):
        pass

    def enterWaitEmpty(self, ts):
        self.lastState = self.state

    def exitWaitEmpty(self):
        pass

    def enterOpening(self, ts):
        self.openDoors.start(ts)
        self.lastState = self.state

    def exitOpening(self):
        pass

    def startCountdownClock(self, countdownTime, ts):
        self.clockNode = TextNode('elevatorClock')
        self.clockNode.setFont(ToontownGlobals.getSignFont())
        self.clockNode.setAlign(TextNode.ACenter)
        self.clockNode.setTextColor(0.5, 0.5, 0.5, 1)
        self.clockNode.setText(str(int(countdownTime)))
        self.clock = self.getElevatorModel().attachNewNode(self.clockNode)
        self.clock.setPosHprScale(0, 4.4, 6.0, 0, 0, 0, 2.0, 2.0, 2.0)
        if ts < countdownTime:
            self.countdown(countdownTime - ts)

    def __doorsClosed(self, zoneId):
        if self.localToonOnBoard:
            self.localAvatar.stopGlitchKiller()
            hoodId = ZoneUtil.getHoodId(zoneId)
            loader = 'suitInterior'
            where = 'suitInterior'
            if base.cr.wantCogdominiums:
                loader = 'cogdoInterior'
                where = 'cogdoInterior'
            doneStatus = {'loader': loader,
             'where': where,
             'hoodId': hoodId,
             'zoneId': zoneId,
             'shardId': None}
            elevator = self.elevatorFSM
            del self.elevatorFSM
            elevator.signalDone(doneStatus)
        return

    def getElevatorModel(self):
        self.notify.error('getElevatorModel: pure virtual -- inheritors must override')

    def getPlaceElevator(self):
        place = self.cr.playGame.getPlace()
        if not hasattr(place, 'elevator'):
            self.notify.warning("Place was in state '%s' instead of Elevator." % place.state)
            place.detectedElevatorCollision(self)
            return None
        return place.elevator

    def getScaledPoint(self, index):
        point = self.elevatorPoints[index]
        return point

    def getDestName(self):
        return None