from panda3d.core import *
from direct.interval.IntervalGlobal import *
from direct.showbase.PythonUtil import *
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedSmoothNode
from direct.distributed.ClockDelta import globalClockDelta
from direct.distributed.MsgTypes import *
from direct.task import Task
from otp.otpbase import OTPGlobals
from toontown.pets import Pet, PetBase, PetTraits, PetConstants, PetManager, PetAvatarPanel
from toontown.pets import PetMood, PetTricks
from toontown.hood import ZoneUtil
from toontown.toonbase import TTLocalizer
from toontown.distributed import DelayDelete
from toontown.distributed.DelayDeletable import DelayDeletable
import random
BeanColors = (VBase4(1.0, 0.2, 0.2, 1.0),
 VBase4(0.2, 1.0, 0.2, 1.0),
 VBase4(0.2, 0.2, 1.0, 1.0),
 VBase4(0.0, 1.0, 1.0, 1.0),
 VBase4(1.0, 1.0, 0.0, 1.0),
 VBase4(1.0, 0.6, 1.0, 1.0),
 VBase4(0.6, 0.0, 0.6, 1.0))

class DistributedPet(DistributedSmoothNode.DistributedSmoothNode, Pet.Pet, PetBase.PetBase, DelayDeletable):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPet')
    swallowSfx = None
    callSfx = None
    petSfx = None

    def __init__(self, cr, bFake = False):
        DistributedSmoothNode.DistributedSmoothNode.__init__(self, cr)
        Pet.Pet.__init__(self)
        self.bFake = bFake
        self.isLocalToon = 0
        self.inWater = 0
        self.__funcsToDelete = []
        self.__generateDistTraitFuncs()
        self.__generateDistMoodFuncs()
        self.trickAptitudes = []
        self.avDelayDelete = None
        return

    def generate(self):
        DistributedPet.notify.debug('generate(), fake=%s' % self.bFake)
        if not self.bFake:
            PetManager.acquirePetManager()
        DistributedSmoothNode.DistributedSmoothNode.generate(self)
        self.trickIval = None
        self.movieTrack = None
        self.traitList = [0] * PetTraits.PetTraits.NumTraits
        self.requiredMoodComponents = {}
        return

    def b_setLocation(self, parentId, zoneId):
        if not self.bFake:
            DistributedSmoothNode.DistributedSmoothNode.b_setLocation(self, parentId, zoneId)

    def d_setLocation(self, parentId, zoneId):
        if not self.bFake:
            DistributedSmoothNode.DistributedSmoothNode.d_setLocation(self, parentId, zoneId)

    def setLocation(self, parentId, zoneId):
        if not self.bFake:
            DistributedSmoothNode.DistributedSmoothNode.setLocation(self, parentId, zoneId)

    def getDisplayPrefix(self):
        return 'pet%s' % self.doId

    def display(self, key, value, category = ''):
        if self.bFake:
            return 1
        if len(category) > 0:
            category = '-' + category
        onScreenDebug.add('%s%s-%s' % (self.getDisplayPrefix(), category, key), value)
        return 1

    def clearDisplay(self):
        onScreenDebug.removeAllWithPrefix(self.getDisplayPrefix())
        return 1

    def moodComponentChanged(self, components = []):
        if len(components) == 0:
            components = PetMood.PetMood.Components
        for comp in components:
            self.display(comp, self.mood.getComponent(comp), 'mood')

    def setOwnerId(self, ownerId):
        self.ownerId = ownerId

    def getOwnerId(self):
        return self.ownerId

    def setPetName(self, petName):
        self.petName = petName
        DistributedSmoothNode.DistributedSmoothNode.setName(self, self.petName)
        if self.isGenerated():
            Pet.Pet.setName(self, self.petName)
        messenger.send('petNameChanged', [self])

    def setTraitSeed(self, traitSeed):
        self.traitSeed = traitSeed

    def setSafeZone(self, safeZone):
        self.safeZone = safeZone

    def __generateDistTraitFuncs(self):
        for i in xrange(PetTraits.PetTraits.NumTraits):
            traitName = PetTraits.getTraitNames()[i]
            setterName = self.getSetterName(traitName)

            def traitSetter(value, self = self, i = i):
                self.traitList[i] = value

            self.__dict__[setterName] = traitSetter
            self.__funcsToDelete.append(setterName)

    def setHead(self, head):
        DistributedPet.notify.debug('setHead: %s' % head)
        self.head = head

    def setEars(self, ears):
        DistributedPet.notify.debug('setEars: %s' % ears)
        self.ears = ears

    def setNose(self, nose):
        DistributedPet.notify.debug('setNose: %s' % nose)
        self.nose = nose

    def setTail(self, tail):
        DistributedPet.notify.debug('setTail: %s' % tail)
        self.tail = tail

    def setBodyTexture(self, bodyTexture):
        DistributedPet.notify.debug('setBodyTexture: %s' % bodyTexture)
        self.bodyTexture = bodyTexture

    def setColor(self, color):
        DistributedPet.notify.debug('setColor: %s' % color)
        self.color = color

    def setColorScale(self, colorScale):
        DistributedPet.notify.debug('setColorScale: %s' % colorScale)
        self.colorScale = colorScale

    def setEyeColor(self, eyeColor):
        DistributedPet.notify.debug('setEyeColor: %s' % eyeColor)
        self.eyeColor = eyeColor

    def setGender(self, gender):
        DistributedPet.notify.debug('setGender: %s' % gender)
        self.gender = gender

    def setLastSeenTimestamp(self, timestamp):
        DistributedPet.notify.debug('setLastSeenTimestamp: %s' % timestamp)
        self.lastSeenTimestamp = timestamp

    def getTimeSinceLastSeen(self):
        t = self.cr.getServerTimeOfDay() - self.lastSeenTimestamp
        return max(0.0, t)

    def updateOfflineMood(self):
        self.mood.driftMood(dt=self.getTimeSinceLastSeen(), curMood=self.lastKnownMood)

    def __handleMoodSet(self, component, value):
        if self.isGenerated():
            self.mood.setComponent(component, value)
        else:
            self.requiredMoodComponents[component] = value

    def __generateDistMoodFuncs(self):
        for compName in PetMood.PetMood.Components:
            setterName = self.getSetterName(compName)

            def moodSetter(value, self = self, compName = compName):
                self.__handleMoodSet(compName, value)

            self.__dict__[setterName] = moodSetter
            self.__funcsToDelete.append(setterName)

    def setMood(self, *componentValues):
        for value, name in zip(componentValues, PetMood.PetMood.Components):
            setterName = self.getSetterName(name)
            self.__dict__[setterName](value)

    def doTrick(self, trickId, timestamp):
        if not self.isLockedDown():
            if self.trickIval is not None and self.trickIval.isPlaying():
                self.trickIval.finish()
            self.trickIval = PetTricks.getTrickIval(self, trickId)
            if trickId == PetTricks.Tricks.BALK:
                mood = self.getDominantMood()
                self.trickIval = Parallel(self.trickIval, Sequence(Func(self.handleMoodChange, 'confusion'), Wait(1.0), Func(self.handleMoodChange, mood)))
            self.trickIval.start(globalClockDelta.localElapsedTime(timestamp))
        return

    def getName(self):
        return Pet.Pet.getName(self)

    def announceGenerate(self):
        DistributedPet.notify.debug('announceGenerate(), fake=%s' % self.bFake)
        DistributedSmoothNode.DistributedSmoothNode.announceGenerate(self)
        if hasattr(self, 'petName'):
            Pet.Pet.setName(self, self.petName)
        self.traits = PetTraits.PetTraits(self.traitSeed, self.safeZone)
        self.mood = PetMood.PetMood(self)
        for mood, value in self.requiredMoodComponents.items():
            self.mood.setComponent(mood, value, announce=0)

        self.requiredMoodComponents = {}
        DistributedPet.notify.debug('time since last seen: %s' % self.getTimeSinceLastSeen())
        self.setDNA([self.head,
         self.ears,
         self.nose,
         self.tail,
         self.bodyTexture,
         self.color,
         self.colorScale,
         self.eyeColor,
         self.gender])
        av = self.cr.doId2do.get(self.ownerId)
        if av:
            av.petDNA = self.style
        if self.bFake:
            self.lastKnownMood = self.mood.makeCopy()
            self.updateOfflineMood()
        else:
            self.__initCollisions()
            self.startSmooth()
            self.setActiveShadow(1)
        self.setPetName(self.petName)
        if not self.bFake:
            self.addActive()
            self.startBlink()
            if not self.swallowSfx:
                self.swallowSfx = loader.loadSfx('phase_5.5/audio/sfx/beg_eat_swallow.ogg')
            if not self.callSfx:
                self.callSfx = loader.loadSfx('phase_5.5/audio/sfx/call_pet.ogg')
            if not self.petSfx:
                self.petSfx = loader.loadSfx('phase_5.5/audio/sfx/pet_the_pet.ogg')
            self.handleMoodChange()
            self.accept(self.mood.getDominantMoodChangeEvent(), self.handleMoodChange)
            self.accept(self.mood.getMoodChangeEvent(), self.moodComponentChanged)

    def disable(self):
        DistributedPet.notify.debug('disable(), fake=%s' % self.bFake)
        if self.isLocalToon:
            base.localAvatar.enableSmartCameraViews()
            self.freeAvatar()
        self.ignore(self.mood.getDominantMoodChangeEvent())
        self.ignore(self.mood.getMoodChangeEvent())
        if hasattr(self, 'lastKnownMood'):
            self.lastKnownMood.destroy()
            del self.lastKnownMood
        self.mood.destroy()
        del self.mood
        del self.traits
        self.removeActive()
        if not self.bFake:
            self.stopSmooth()
            self.__cleanupCollisions()
            self.stopAnimations()
            if self.doId == localAvatar.getPetId():
                bboard.post(PetConstants.OurPetsMoodChangedKey, True)
        taskMgr.remove(self.uniqueName('lerpCamera'))
        self.clearDisplay()
        DistributedSmoothNode.DistributedSmoothNode.disable(self)

    def delete(self):
        DistributedPet.notify.debug('delete(), fake=%s' % self.bFake)
        if self.trickIval is not None:
            self.trickIval.finish()
            del self.trickIval
        if self.movieTrack is not None:
            self.movieTrack.finish()
            del self.movieTrack
        taskMgr.remove(self.uniqueName('Pet-Movie-%s' % self.getDoId()))
        self.clearMovie()
        for funcName in self.__funcsToDelete:
            del self.__dict__[funcName]

        Pet.Pet.delete(self)
        DistributedSmoothNode.DistributedSmoothNode.delete(self)
        if not self.bFake:
            PetManager.releasePetManager()
        return

    def __initCollisions(self):
        cRay = CollisionRay(0.0, 0.0, 40000.0, 0.0, 0.0, -1.0)
        cRayNode = CollisionNode('pet-cRayNode-%s' % self.doId)
        cRayNode.addSolid(cRay)
        cRayNode.setFromCollideMask(OTPGlobals.FloorBitmask)
        cRayNode.setIntoCollideMask(BitMask32.allOff())
        self.cRayNodePath = self.attachNewNode(cRayNode)
        self.lifter = CollisionHandlerFloor()
        self.lifter.setInPattern('enter%in')
        self.lifter.setOutPattern('exit%in')
        self.lifter.setOffset(OTPGlobals.FloorOffset)
        self.lifter.setReach(4.0)
        self.lifter.addCollider(self.cRayNodePath, self)
        self.cTrav = base.petManager.cTrav
        self.cTrav.addCollider(self.cRayNodePath, self.lifter)
        taskMgr.add(self._detectWater, self.getDetectWaterTaskName(), priority=32)
        self.initializeBodyCollisions('pet-%s' % self.doId)

    def __cleanupCollisions(self):
        self.disableBodyCollisions()
        taskMgr.remove(self.getDetectWaterTaskName())
        self.cTrav.removeCollider(self.cRayNodePath)
        del self.cTrav
        self.cRayNodePath.removeNode()
        del self.cRayNodePath
        del self.lifter

    def lockPet(self):
        if not self.lockedDown:
            self.prevAnimState = self.animFSM.getCurrentState().getName()
            self.animFSM.request('neutral')
        self.lockedDown += 1

    def isLockedDown(self):
        return self.lockedDown != 0

    def unlockPet(self):
        if self.lockedDown <= 0:
            DistributedPet.notify.warning('%s: unlockPet called on unlockedPet' % self.doId)
        else:
            self.lockedDown -= 1
            if not self.lockedDown:
                self.animFSM.request(self.prevAnimState)
                self.prevAnimState = None
        return

    def smoothPosition(self):
        DistributedSmoothNode.DistributedSmoothNode.smoothPosition(self)
        if not self.lockedDown:
            self.trackAnimToSpeed(self.smoother.getSmoothForwardVelocity(), self.smoother.getSmoothRotationalVelocity())

    def getDetectWaterTaskName(self):
        return self.uniqueName('detectWater')

    def _detectWater(self, task):
        showWake, wakeWaterHeight = ZoneUtil.getWakeInfo()
        self.inWater = 0
        if showWake:
            if self.getZ() <= wakeWaterHeight:
                self.setZ(wakeWaterHeight - PetConstants.SubmergeDistance)
                self.inWater = 1
        return Task.cont

    def isInWater(self):
        return self.inWater

    def isExcited(self):
        return PetBase.PetBase.isExcited(self)

    def isSad(self):
        return PetBase.PetBase.isSad(self)

    def handleMoodChange(self, mood = None):
        if mood is None:
            mood = self.mood.getDominantMood()
        if mood == PetMood.PetMood.Neutral:
            self.clearChat()
            self.clearMood()
        else:
            self.showMood(mood)
        messenger.send('petStateUpdated', [self])
        return

    def getDominantMood(self):
        if not hasattr(self, 'mood'):
            return PetMood.PetMood.Neutral
        return self.mood.getDominantMood()

    def getRequestID(self):
        return CLIENT_GET_PET_DETAILS

    def teleportIn(self, timestamp):
        self.lockPet()
        self.animFSM.request('teleportIn', [timestamp])
        self.unlockPet()

    def teleportOut(self, timestamp):
        self.lockPet()
        self.animFSM.request('teleportOut', [timestamp])
        self.unlockPet()

    def avatarInteract(self, avId):
        place = base.cr.playGame.getPlace()
        place.setState('pet')
        base.localAvatar.disableSmartCameraViews()

    def freeAvatar(self):
        place = base.cr.playGame.getPlace()
        if place:
            place.setState('walk')
        base.localAvatar.unlock()
        messenger.send('pet-interaction-done')

    def setUpMovieAvatar(self, av):
        self.avDelayDelete = DelayDelete.DelayDelete(av, 'Pet.setUpMovieAvatar')
        av.headsUp(self, 0, 0, 0)
        av.stopLookAround()

    def holdPetDownForMovie(self):
        self.lockPet()
        self.stopSmooth()

    def releasePetFromHoldDown(self):
        self.unlockPet()
        self.startSmooth()

    def clearMovieAvatar(self):
        if self.avDelayDelete:
            self.avDelayDelete.destroy()
            self.avDelayDelete = None
        return

    def clearMovie(self):
        self.clearMovieAvatar()
        return Task.done

    def resetAvatarAndPet(self, task = None):
        if self.isLocalToon:
            base.localAvatar.enableSmartCameraViews()
            base.localAvatar.setH(base.localAvatar, 30)
            self.freeAvatar()
            self.isLocalToon = 0
        return Task.done

    def _petMovieStart(self, av):
        if not self.isLocalToon:
            av.stopSmooth()
        self.setUpMovieAvatar(av)
        if self.isLocalToon:
            base.localAvatar.setCameraPosForPetInteraction()
            base.localAvatar.lock()

    def _getPetMovieCompleteIval(self, av):

        def _petMovieComplete(self = self):
            if self.isLocalToon:
                base.localAvatar.unsetCameraPosForPetInteraction()
            else:
                av.startSmooth()

        return Sequence(Func(_petMovieComplete), Wait(0.8), Func(self.resetAvatarAndPet))

    def setMovie(self, mode, avId, timestamp):
        timeStamp = globalClockDelta.localElapsedTime(timestamp)
        if mode in (PetConstants.PET_MOVIE_CALL, PetConstants.PET_MOVIE_SCRATCH, PetConstants.PET_MOVIE_FEED):
            if self.movieTrack is not None and self.movieTrack.isPlaying():
                self.movieTrack.finish()
        if avId != 0:
            self.isLocalToon = avId == base.localAvatar.doId
            av = base.cr.doId2do.get(avId)
            if av is None:
                self.notify.warning('Avatar %d not found in doId' % avId)
                return
        if mode == PetConstants.PET_MOVIE_CLEAR:
            self.clearMovie()
            return
        if mode == PetConstants.PET_MOVIE_CALL:
            try:
                self.movieTrack = Sequence(Func(self._petMovieStart, av), Parallel(av.getCallPetIval(), Sequence(Wait(0.54), SoundInterval(self.callSfx))), self._getPetMovieCompleteIval(av))
                self.movieTrack.start()
            except StandardError, error:
                print str(error)

        if mode == PetConstants.PET_MOVIE_SCRATCH:
            try:
                self.movieTrack = Sequence(Func(self._petMovieStart, av), Func(self.holdPetDownForMovie), Parallel(self.getInteractIval(self.Interactions.SCRATCH), av.getScratchPetIval(), SoundInterval(self.petSfx)), Func(self.releasePetFromHoldDown), self._getPetMovieCompleteIval(av))
                self.movieTrack.start()
            except StandardError, error:
                print str(error)

        if mode == PetConstants.PET_MOVIE_FEED:
            self.bean = loader.loadModel('phase_4/models/props/jellybean4')
            bean = self.bean.find('**/jellybean')
            bean.setColor(random.choice(BeanColors))
            self.movieTrack = Sequence(Func(self._petMovieStart, av), Func(self.holdPetDownForMovie), Parallel(Func(base.playSfx, self.swallowSfx, 0, 1, 1, 2.5, self.bean), Sequence(ActorInterval(self, 'toBeg'), ActorInterval(self, 'beg'), ActorInterval(self, 'fromBeg'), ActorInterval(self, 'eat'), ActorInterval(self, 'swallow'), Func(self.loop, 'neutral')), Sequence(Wait(0.3), ActorInterval(av, 'feedPet'), Func(av.animFSM.request, 'neutral')), Sequence(Wait(0.3), Func(self.bean.reparentTo, av.rightHand), Func(self.bean.setPos, 0.1, 0.0, 0.2), Wait(2.1), Func(av.update, 0), Func(av.update, 1), Func(av.update, 2), Func(self.bean.wrtReparentTo, render), Parallel(LerpHprInterval(self.bean, hpr=Point3(random.random() * 360.0 * 2, random.random() * 360.0 * 2, random.random() * 360.0 * 2), duration=1.2), ProjectileInterval(self.bean, endPos=self.find('**/joint_tongueBase').getPos(render), duration=1.2, gravityMult=0.45)), Func(self.bean.removeNode))), Func(self.releasePetFromHoldDown), self._getPetMovieCompleteIval(av))
            self.movieTrack.start()
        return

    def setTrickAptitudes(self, aptitudes):
        self.trickAptitudes = aptitudes
