from panda3d.core import *
from direct.interval.IntervalGlobal import *
from toontown.battle.BattleProps import *
from GoonGlobals import *
from direct.fsm import FSM
from direct.distributed import ClockDelta
from otp.level import BasicEntities
from otp.level import DistributedEntity
from direct.directnotify import DirectNotifyGlobal
from toontown.coghq import DistributedCrushableEntity
from toontown.toonbase import ToontownGlobals
from toontown.coghq import MovingPlatform
import Goon
from direct.task.Task import Task
from otp.level import PathEntity
import GoonDeath
import random

class DistributedGoon(DistributedCrushableEntity.DistributedCrushableEntity, Goon.Goon, FSM.FSM):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGoon')

    def __init__(self, cr):
        try:
            self.DistributedGoon_initialized
        except:
            self.DistributedGoon_initialized = 1
            DistributedCrushableEntity.DistributedCrushableEntity.__init__(self, cr)
            Goon.Goon.__init__(self)
            FSM.FSM.__init__(self, 'DistributedGoon')

        self.setCacheable(0)
        self.rayNode = None
        self.checkForWalls = 0
        self.triggerEvent = None
        self.animTrack = None
        self.walkTrack = None
        self.pauseTime = 0
        self.paused = 0
        self.path = None
        self.dir = GOON_FORWARD
        self.animMultiplier = 1.0
        self.isDead = 0
        self.isStunned = 0
        self.collapseSound = loader.loadSfx('phase_9/audio/sfx/CHQ_GOON_hunker_down.ogg')
        self.recoverSound = loader.loadSfx('phase_9/audio/sfx/CHQ_GOON_rattle_shake.ogg')
        self.attackSound = loader.loadSfx('phase_9/audio/sfx/CHQ_GOON_tractor_beam_alarmed.ogg')
        return

    def announceGenerate(self):
        DistributedCrushableEntity.DistributedCrushableEntity.announceGenerate(self)
        if hasattr(self, 'goonType'):
            self.initGoon(self.goonType)
        else:
            self.initGoon('pg')
        self.scaleRadar()
        self.colorHat()
        if self.level:
            self.initClipPlanes()
            self.level.setEntityCreateCallback(self.parentEntId, self.initPath)
        else:
            self.enterOff()
            taskMgr.doMethodLater(0.1, self.makeCollidable, self.taskName('makeCollidable'))
        self.setGoonScale(self.scale)
        self.animMultiplier = self.velocity / (ANIM_WALK_RATE * self.scale)
        self.setPlayRate(self.animMultiplier, 'walk')

    def initPath(self):
        self.enterOff()
        self.setPath()
        taskMgr.doMethodLater(0.1, self.makeCollidable, self.taskName('makeCollidable'))

    def makeCollidable(self, task):
        self.initCollisions()
        self.initializeBodyCollisions()
        triggerName = self.uniqueName('GoonTrigger')
        self.trigger.setName(triggerName)
        self.triggerEvent = 'enter%s' % triggerName
        self.startToonDetect()

    def generate(self):
        DistributedCrushableEntity.DistributedCrushableEntity.generate(self)

    def scaleRadar(self):
        Goon.Goon.scaleRadar(self)
        self.trigger = self.radar.find('**/trigger')
        triggerName = self.uniqueName('GoonTrigger')
        self.trigger.setName(triggerName)

    def initCollisions(self):
        self.cSphere = CollisionSphere(0.0, 0.0, 1.0, 1.0)
        self.cSphereNode = CollisionNode('goonCollSphere')
        self.cSphereNode.addSolid(self.cSphere)
        self.cSphereNodePath = self.head.attachNewNode(self.cSphereNode)
        self.cSphereNodePath.hide()
        self.cSphereBitMask = ToontownGlobals.WallBitmask
        self.cSphereNode.setCollideMask(self.cSphereBitMask)
        self.cSphere.setTangible(1)
        self.sSphere = CollisionSphere(0.0, 0.0, self.headHeight + 0.8, 1.2)
        self.sSphereNode = CollisionNode('toonSphere')
        self.sSphereNode.addSolid(self.sSphere)
        self.sSphereNodePath = self.head.attachNewNode(self.sSphereNode)
        self.sSphereNodePath.hide()
        self.sSphereBitMask = ToontownGlobals.WallBitmask
        self.sSphereNode.setCollideMask(self.sSphereBitMask)
        self.sSphere.setTangible(1)

    def initializeBodyCollisions(self):
        self.cSphereNode.setName(self.uniqueName('goonCollSphere'))
        self.sSphereNode.setName(self.uniqueName('toonSphere'))
        self.accept(self.uniqueName('entertoonSphere'), self.__handleStun)

    def disableBodyCollisions(self):
        self.ignore(self.uniqueName('entertoonSphere'))

    def deleteCollisions(self):
        if hasattr(self, 'sSphereNodePath'):
            self.sSphereNodePath.removeNode()
            del self.sSphereNodePath
            del self.sSphereNode
            del self.sSphere
        if hasattr(self, 'cSphereNodePath'):
            self.cSphereNodePath.removeNode()
            del self.cSphereNodePath
            del self.cSphereNode
            del self.cSphere

    def initClipPlanes(self):
        zoneNum = self.getZoneEntity().getZoneNum()
        clipList = self.level.goonClipPlanes.get(zoneNum)
        if clipList:
            for id in clipList:
                clipPlane = self.level.getEntity(id)
                self.radar.setClipPlane(clipPlane.getPlane())

    def disableClipPlanes(self):
        if self.radar:
            self.radar.clearClipPlane()

    def setPath(self):
        self.path = self.level.getEntity(self.parentEntId)
        if self.walkTrack:
            self.walkTrack.pause()
            self.walkTrack = None
        self.walkTrack = self.path.makePathTrack(self, self.velocity, self.uniqueName('goonWalk'), turnTime=T_TURN)
        if self.gridId != None:
            self.sendUpdate('setParameterize', [self.path.pos[0],
             self.path.pos[1],
             self.path.pos[2],
             self.path.pathIndex])

    def disable(self):
        self.notify.debug('DistributedGoon %d: disabling' % self.getDoId())
        self.ignoreAll()
        self.stopToonDetect()
        taskMgr.remove(self.taskName('resumeWalk'))
        taskMgr.remove(self.taskName('recoveryDone'))
        self.request('Off')
        self.disableBodyCollisions()
        self.disableClipPlanes()
        if self.animTrack:
            self.animTrack.finish()
            self.animTrack = None
        if self.walkTrack:
            self.walkTrack.pause()
            self.walkTrack = None
        DistributedCrushableEntity.DistributedCrushableEntity.disable(self)
        return

    def delete(self):
        try:
            self.DistributedSuit_deleted
        except:
            self.DistributedSuit_deleted = 1
            self.notify.debug('DistributedGoon %d: deleting' % self.getDoId())
            taskMgr.remove(self.taskName('makeCollidable'))
            self.deleteCollisions()
            self.head.removeNode()
            del self.head
            del self.attackSound
            del self.collapseSound
            del self.recoverSound
            DistributedCrushableEntity.DistributedCrushableEntity.delete(self)
            Goon.Goon.delete(self)

    def enterOff(self, *args):
        self.nametag3d.stash()
        self.nametag.destroy()
        self.hide()
        self.isStunned = 0
        self.isDead = 0
        if self.animTrack:
            self.animTrack.finish()
            self.animTrack = None
        if self.walkTrack:
            self.walkTrack.pause()
            self.walkTrack = None

    def exitOff(self):
        self.show()

    def enterWalk(self, avId = None, ts = 0):
        self.notify.debug('enterWalk, ts = %s' % ts)
        self.startToonDetect()
        self.loop('walk', 0)
        self.isStunned = 0
        if self.path:
            if not self.walkTrack:
                self.walkTrack = self.path.makePathTrack(self, self.velocity, self.uniqueName('goonWalk'), turnTime=T_TURN)
            self.startWalk(ts)

    def startWalk(self, ts):
        tOffset = ts % self.walkTrack.getDuration()
        self.walkTrack.loop()
        self.walkTrack.pause()
        self.walkTrack.setT(tOffset)
        self.walkTrack.resume()
        self.paused = 0

    def exitWalk(self):
        self.notify.debug('exitWalk')
        self.stopToonDetect()
        if self.walkTrack and not self.paused:
            self.pauseTime = self.walkTrack.pause()
            self.paused = 1
        self.stop()

    def enterBattle(self, avId = None, ts = 0):
        self.notify.debug('enterBattle')
        self.stopToonDetect()
        if self.animTrack:
            self.animTrack.finish()
            self.animTrack = None
        self.isStunned = 0
        if avId == base.localAvatar.doId:
            if self.level:
                self.level.b_setOuch(self.strength)
        self.animTrack = self.makeAttackTrack()
        self.animTrack.loop()
        return

    def exitBattle(self):
        self.notify.debug('exitBattle')
        if self.animTrack:
            self.animTrack.finish()
            self.animTrack = None
        self.head.setHpr(0, 0, 0)
        return

    def enterStunned(self, ts = 0):
        self.ignore(self.uniqueName('entertoonSphere'))
        self.isStunned = 1
        self.notify.debug('enterStunned')
        if self.radar:
            self.radar.hide()
        self.animTrack = Parallel(Sequence(ActorInterval(self, 'collapse'), Func(self.pose, 'collapse', 48)), SoundInterval(self.collapseSound, node=self))
        self.animTrack.start(ts)

    def exitStunned(self):
        self.notify.debug('exitStunned')
        if self.radar:
            self.radar.show()
        if self.animTrack:
            self.animTrack.finish()
            self.animTrack = None
        self.accept(self.uniqueName('entertoonSphere'), self.__handleStun)
        return

    def enterRecovery(self, ts = 0, pauseTime = 0):
        self.notify.debug('enterRecovery')
        self.ignore(self.uniqueName('entertoonSphere'))
        self.isStunned = 1
        if self.animTrack:
            self.animTrack.finish()
            self.animTrack = None
        self.animTrack = self.getRecoveryTrack()
        duration = self.animTrack.getDuration()
        self.animTrack.start(ts)
        delay = max(0, duration - ts)
        taskMgr.remove(self.taskName('recoveryDone'))
        taskMgr.doMethodLater(delay, self.recoveryDone, self.taskName('recoveryDone'), extraArgs=(pauseTime,))
        return

    def getRecoveryTrack(self):
        return Parallel(Sequence(ActorInterval(self, 'recovery'), Func(self.pose, 'recovery', 96)), Func(base.playSfx, self.recoverSound, node=self))

    def recoveryDone(self, pauseTime):
        self.request('Walk', None, pauseTime)
        return

    def exitRecovery(self):
        self.notify.debug('exitRecovery')
        taskMgr.remove(self.taskName('recoveryDone'))
        if self.animTrack:
            self.animTrack.finish()
            self.animTrack = None
        self.accept(self.uniqueName('entertoonSphere'), self.__handleStun)
        return

    def makeAttackTrack(self):
        h = self.head.getH()
        freakDeg = 60
        hatZ = self.hat.getZ()
        track = Parallel(Sequence(LerpColorScaleInterval(self.eye, 0.2, Vec4(1, 0, 0, 1)), LerpColorScaleInterval(self.eye, 0.2, Vec4(0, 0, 1, 1)), LerpColorScaleInterval(self.eye, 0.2, Vec4(1, 0, 0, 1)), LerpColorScaleInterval(self.eye, 0.2, Vec4(0, 0, 1, 1)), Func(self.eye.clearColorScale)), SoundInterval(self.attackSound, node=self, volume=0.4))
        return track

    def doDetect(self):
        pass

    def doAttack(self, avId):
        pass

    def __startResumeWalkTask(self, ts):
        resumeTime = 1.5
        if ts < resumeTime:
            taskMgr.remove(self.taskName('resumeWalk'))
            taskMgr.doMethodLater(resumeTime - ts, self.request, self.taskName('resumeWalk'), extraArgs=('Walk',))
        else:
            self.request('Walk', ts - resumeTime)

    def __reverseWalk(self, task):
        self.request('Walk')
        return Task.done

    def __startRecoverTask(self, ts):
        stunTime = 4.0
        if ts < stunTime:
            taskMgr.remove(self.taskName('resumeWalk'))
            taskMgr.doMethodLater(stunTime - ts, self.request, self.taskName('resumeWalk'), extraArgs=('Recovery',))
        else:
            self.request('Recovery', ts - stunTime)

    def startToonDetect(self):
        self.radar.show()
        if self.triggerEvent:
            self.accept(self.triggerEvent, self.handleToonDetect)

    def stopToonDetect(self):
        if self.triggerEvent:
            self.ignore(self.triggerEvent)

    def handleToonDetect(self, collEntry = None):
        if base.localAvatar.isStunned:
            return
        if self.state == 'Off':
            return
        self.stopToonDetect()
        self.request('Battle', base.localAvatar.doId)
        if self.walkTrack:
            self.pauseTime = self.walkTrack.pause()
            self.paused = 1
        if self.dclass and hasattr(self, 'dclass'):
            self.sendUpdate('requestBattle', [self.pauseTime])
        else:
            self.notify.info('Goon deleted and still trying to call handleToonDetect()')

    def __handleStun(self, collEntry):
        toon = base.localAvatar
        if toon:
            toonDistance = self.getPos(toon).length()
            if toonDistance > self.attackRadius:
                self.notify.warning('Stunned a good, but outside of attack radius')
                return
            else:
                self.request('Stunned')
        if self.walkTrack:
            self.pauseTime = self.walkTrack.pause()
            self.paused = 1
        self.sendUpdate('requestStunned', [self.pauseTime])

    def setMovie(self, mode, avId, pauseTime, timestamp):
        if self.isDead:
            return
        ts = ClockDelta.globalClockDelta.localElapsedTime(timestamp)
        self.notify.debug('%s: setMovie(%s,%s,%s,%s)' % (self.doId,
         mode,
         avId,
         pauseTime,
         ts))
        if mode == GOON_MOVIE_BATTLE:
            if self.state != 'Battle':
                self.request('Battle', avId, ts)
        elif mode == GOON_MOVIE_STUNNED:
            if self.state != 'Stunned':
                toon = base.cr.doId2do.get(avId)
                if toon:
                    toonDistance = self.getPos(toon).length()
                    if toonDistance > self.attackRadius:
                        self.notify.warning('Stunned a goon, but outside of attack radius')
                        return
                    else:
                        self.request('Stunned', ts)
        elif mode == GOON_MOVIE_RECOVERY:
            if self.state != 'Recovery':
                self.request('Recovery', ts, pauseTime)
        elif mode == GOON_MOVIE_SYNC:
            if self.walkTrack:
                self.walkTrack.pause()
                self.paused = 1
            if self.state == 'Off' or self.state == 'Walk':
                self.request('Walk', avId, pauseTime + ts)
        else:
            if self.walkTrack:
                self.walkTrack.pause()
                self.walkTrack = None
            self.request('Walk', avId, pauseTime + ts)
        return

    def stunToon(self, avId):
        self.notify.debug('stunToon(%s)' % avId)
        av = base.cr.doId2do.get(avId)
        if av != None:
            av.stunToon()
        return

    def isLocalToon(self, avId):
        if avId == base.localAvatar.doId:
            return 1
        return 0

    def playCrushMovie(self, crusherId, axis):
        goonPos = self.getPos()
        sx = random.uniform(0.3, 0.8) * self.scale
        sz = random.uniform(0.3, 0.8) * self.scale
        crushTrack = Sequence(GoonDeath.createGoonExplosion(self.getParent(), goonPos, VBase3(sx, 1, sz)), name=self.uniqueName('crushTrack'), autoFinish=1)
        self.dead()
        crushTrack.start()

    def setVelocity(self, velocity):
        self.velocity = velocity
        self.animMultiplier = velocity / (ANIM_WALK_RATE * self.scale)
        self.setPlayRate(self.animMultiplier, 'walk')

    def dead(self):
        if not self.isDead and not self.isDisabled():
            self.stopToonDetect()
            self.detachNode()
            self.isDead = 1

    def undead(self):
        if self.isDead:
            self.startToonDetect()
            self.reparentTo(render)
            self.isDead = 0

    def resync(self):
        if not self.isDead:
            self.sendUpdate('requestResync')

    def setHFov(self, hFov):
        if hFov != self.hFov:
            self.hFov = hFov
            if self.isGenerated():
                self.scaleRadar()

    def setAttackRadius(self, attackRadius):
        if attackRadius != self.attackRadius:
            self.attackRadius = attackRadius
            if self.isGenerated():
                self.scaleRadar()

    def setStrength(self, strength):
        if strength != self.strength:
            self.strength = strength
            if self.isGenerated():
                self.colorHat()

    def setGoonScale(self, scale):
        if scale != self.scale:
            self.scale = scale
            if self.isGenerated():
                self.getGeomNode().setScale(self.scale)
                self.scaleRadar()

    def setupGoon(self, velocity, hFov, attackRadius, strength, scale):
        self.setVelocity(velocity)
        self.setHFov(hFov)
        self.setAttackRadius(attackRadius)
        self.setStrength(strength)
        self.setGoonScale(scale)