from pandac.PandaModules import *
from direct.showbase import GarbageReport, ContainerReport, MessengerLeakDetector
from direct.distributed import DistributedObject
from direct.directnotify import DirectNotifyGlobal
from direct.showbase.InputStateGlobal import inputState
from otp.otpbase.ObjectCount import ObjectCount
from direct.task import Task
from direct.task.TaskProfiler import TaskProfiler
from otp.avatar import Avatar
import string
from direct.showbase import PythonUtil
from direct.showbase.PythonUtil import Functor, DelayedCall, ScratchPad
from otp.otpbase import OTPGlobals
from direct.distributed.ClockDelta import *
from direct.showutil.TexViewer import TexViewer

class MagicWordManager(DistributedObject.DistributedObject):
    notify = DirectNotifyGlobal.directNotify.newCategory('MagicWordManager')
    neverDisable = 1
    GameAvatarClass = None

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        self.shownFontNode = None
        self.csShown = 0
        self.guiPopupShown = 0
        self.texViewer = None
        return

    def generate(self):
        DistributedObject.DistributedObject.generate(self)
        self.accept('magicWord', self.b_setMagicWord)
        self.autoMagicWordEvent = localAvatar.getArrivedOnDistrictEvent()
        if localAvatar.isGeneratedOnDistrict():
            self.doLoginMagicWords()
        else:
            self.accept(self.autoMagicWordEvent, self.doLoginMagicWords)

    def doLoginMagicWords(self):
        pass

    def disable(self):
        self.ignore(self.autoMagicWordEvent)
        del self.autoMagicWordEvent
        self.ignore('magicWord')
        self.hidefont()
        DistributedObject.DistributedObject.disable(self)

    def setMagicWord(self, word, avId, zoneId):
        try:
            self.doMagicWord(word, avId, zoneId)
        except:
            response = PythonUtil.describeException(backTrace=1)
            self.notify.warning('Ignoring error in magic word:\n%s' % response)
            self.setMagicWordResponse(response)

    def wordIs(self, word, w):
        return word == w or word[:len(w) + 1] == '%s ' % w

    def getWordIs(self, word):
        return Functor(self.wordIs, word)

    def doMagicWord(self, word, avId, zoneId):
        wordIs = self.getWordIs(word)
        print(word)
        if wordIs('~oobe'):
            base.oobe()
        elif wordIs('~oobeCull'):
            base.oobeCull()
        elif wordIs('~tex'):
            self.doTex(word)
        elif wordIs('~texmem'):
            base.toggleTexMem()
        elif wordIs('~verts'):
            base.toggleShowVertices()
        elif wordIs('~wire'):
            base.toggleWireframe()
        elif wordIs('~stereo'):
            base.toggleStereo()
        elif wordIs('~showfont'):
            self.showfont(word[9:])
        elif wordIs('~hidefont'):
            self.hidefont()
        elif wordIs('~guiPopup'):
            self.toggleGuiPopup()
        elif wordIs('~showCS') or wordIs('~showcs'):
            bitmask = self.getCSBitmask(word[7:])
            render.showCS(bitmask)
            self.csShown = 1
        elif wordIs('~hideCS') or wordIs('~hidecs'):
            bitmask = self.getCSBitmask(word[7:])
            render.hideCS(bitmask)
            self.csShown = 0
        elif wordIs('~cs'):
            bitmask = self.getCSBitmask(word[3:])
            if self.csShown:
                render.hideCS(bitmask)
                self.csShown = 0
            else:
                render.showCS(bitmask)
                self.csShown = 1
        elif wordIs('~showShadowCollisions'):
            self.showShadowCollisions()
        elif wordIs('~hideShadowCollisions'):
            self.hideShadowCollisions()
        elif wordIs('~showCollisions'):
            self.showCollisions()
        elif wordIs('~hideCollisions'):
            self.hideCollisions()
        elif wordIs('~showCameraCollisions'):
            self.showCameraCollisions()
        elif wordIs('~hideCameraCollisions'):
            self.hideCameraCollisions()
        elif wordIs('~collidespam'):
            n = Notify.ptr().getCategory(':collide')
            if hasattr(self, '_collideSpamSeverity'):
                n.setSeverity(self._collideSpamSeverity)
                del self._collideSpamSeverity
            else:
                self._collideSpamSeverity = n.getSeverity()
                n.setSeverity(NSSpam)
        elif wordIs('~notify'):
            args = word.split()
            n = Notify.ptr().getCategory(args[1])
            n.setSeverity({'error': NSError,
             'warning': NSWarning,
             'info': NSInfo,
             'debug': NSDebug,
             'spam': NSSpam}[args[2]])
        elif wordIs('~stress'):
            factor = word[7:]
            if factor:
                factor = float(factor)
                LOD.setStressFactor(factor)
                response = 'Set LOD stress factor to %s' % factor
            else:
                factor = LOD.getStressFactor()
                response = 'LOD stress factor is %s' % factor
            self.setMagicWordResponse(response)
        elif wordIs('~for'):
            self.forAnother(word, avId, zoneId)
        elif wordIs('~badname'):
            word = '~for %s ~badname' % word[9:]
            print('word is %s' % word)
            self.forAnother(word, avId, zoneId)
        elif wordIs('~avId'):
            self.setMagicWordResponse(str(localAvatar.doId))
        elif wordIs('~doId'):
            name = string.strip(word[6:])
            objs = self.identifyDistributedObjects(name)
            if len(objs) == 0:
                response = '%s is unknown.' % name
            else:
                response = ''
                for name, obj in objs:
                    response += '\n%s %d' % (name, obj.doId)

                response = response[1:]
            self.setMagicWordResponse(response)
        elif wordIs('~exec'):
            from otp.chat import ChatManager
            ChatManager.ChatManager.execChat = 1
        elif wordIs('~run'):
            self.toggleRun()
        elif wordIs('~runFaster'):
            if config.GetBool('want-running', 1):
                args = word.split()
                if len(args) > 1:
                    base.debugRunningMultiplier = float(args[1])
                else:
                    base.debugRunningMultiplier = 10
                inputState.set('debugRunning', True)
        elif wordIs('~who'):
            avIds = []
            for av in Avatar.Avatar.ActiveAvatars:
                if hasattr(av, 'getFriendsList'):
                    avIds.append(av.doId)

            self.d_setWho(avIds)
        elif wordIs('~sync'):
            tm = self.cr.timeManager
            if tm == None:
                response = 'No TimeManager.'
                self.setMagicWordResponse(response)
            else:
                tm.extraSkew = 0.0
                skew = string.strip(word[5:])
                if skew != '':
                    tm.extraSkew = float(skew)
                globalClockDelta.clear()
                tm.handleHotkey()
        elif wordIs('~period'):
            timeout = string.strip(word[7:])
            if timeout != '':
                seconds = int(timeout)
                self.cr.stopPeriodTimer()
                self.cr.resetPeriodTimer(seconds)
                self.cr.startPeriodTimer()
            if self.cr.periodTimerExpired:
                response = 'Period timer has expired.'
            elif self.cr.periodTimerStarted:
                elapsed = globalClock.getFrameTime() - self.cr.periodTimerStarted
                secondsRemaining = self.cr.periodTimerSecondsRemaining - elapsed
                response = 'Period timer expires in %s seconds.' % int(secondsRemaining)
            else:
                response = 'Period timer not set.'
            self.setMagicWordResponse(response)
        elif wordIs('~DIRECT'):
            args = word.split()
            fEnableLight = 0
            if len(args) > 1:
                if direct and args[1] == 'CAM':
                    direct.enable()
                    taskMgr.removeTasksMatching('updateSmartCamera*')
                    camera.wrtReparentTo(render)
                    direct.cameraControl.enableMouseFly()
                    self.setMagicWordResponse('Enabled DIRECT camera')
                    return
                elif args[1] == 'LIGHT':
                    fEnableLight = 1
            base.startTk()
            from direct.directtools import DirectSession
            if fEnableLight:
                direct.enableLight()
            else:
                direct.enable()
            self.setMagicWordResponse('Enabled DIRECT')
        elif wordIs('~TT'):
            if not direct:
                return
            args = word.split()
            if len(args) > 1:
                if args[1] == 'CAM':
                    direct.cameraControl.disableMouseFly()
                    camera.wrtReparentTo(base.localAvatar)
                    base.localAvatar.startUpdateSmartCamera()
                    self.setMagicWordResponse('Disabled DIRECT camera')
                    return
            direct.disable()
            camera.wrtReparentTo(base.localAvatar)
            base.localAvatar.startUpdateSmartCamera()
            self.setMagicWordResponse('Disabled DIRECT')
        elif wordIs('~net'):
            if self.cr.networkPlugPulled():
                self.cr.restoreNetworkPlug()
                self.cr.startHeartbeat()
                response = 'Network restored.'
            else:
                self.cr.pullNetworkPlug()
                self.cr.stopHeartbeat()
                response = 'Network disconnected.'
            self.setMagicWordResponse(response)
        elif wordIs('~disconnect'):
            base.cr.distributedDistrict.sendUpdate('broadcastMessage')
        elif wordIs('~model'):
            args = word.split()
            path = args[1]
            model = loader.loadModel(path)
            model.reparentTo(localAvatar)
            model.wrtReparentTo(render)
            self.setMagicWordResponse('loaded %s' % path)
        elif wordIs('~axis'):
            axis = loader.loadModel('models/misc/xyzAxis.bam')
            axis.reparentTo(render)
            axis.setPos(base.localAvatar, 0, 0, 0)
            axis.setHpr(render, 0, 0, 0)
            axis10 = loader.loadModel('models/misc/xyzAxis.bam')
            axis10.reparentTo(render)
            axis10.setPos(base.localAvatar, 0, 0, 0)
            axis10.setScale(10)
            axis10.setHpr(render, 0, 0, 0)
            axis10.setColorScale(1, 1, 1, 0.4)
            axis10.setTransparency(1)
        elif wordIs('~clearAxes') or wordIs('~clearAxis'):
            render.findAllMatches('**/xyzAxis.egg').detach()
        elif wordIs('~myAxis'):
            if hasattr(self, 'myAxis'):
                self.myAxis.detachNode()
                del self.myAxis
            else:
                self.myAxis = loader.loadModel('models/misc/xyzAxis.bam')
                self.myAxis.reparentTo(localAvatar)
        elif wordIs('~osd'):
            onScreenDebug.enabled = not onScreenDebug.enabled
        elif wordIs('~osdScale'):
            args = word.split()
            defScale = 0.05
            if len(args) > 1:
                scale = float(args[1])
            else:
                scale = 1.0
            onScreenDebug.onScreenText.setScale(defScale * scale)
        elif wordIs('~osdTaskMgr'):
            if taskMgr.osdEnabled():
                taskMgr.stopOsd()
            else:
                if not onScreenDebug.enabled:
                    onScreenDebug.enabled = True
                taskMgr.startOsd()
        elif wordIs('~fps'):
            self.doFps(word, avId, zoneId)
        elif wordIs('~sleep'):
            args = word.split()
            if len(args) > 1:
                s = float(args[1])
                base.setSleep(s)
                response = 'sleeping %s' % s
            else:
                base.setSleep(0.0)
                response = 'not sleeping'
            self.setMagicWordResponse(response)
        elif wordIs('~objects'):
            args = word.split()
            from direct.showbase import ObjectReport
            report = ObjectReport.ObjectReport('client ~objects')
            if 'all' in args:
                self.notify.info('printing full object set...')
                report.getObjectPool().printObjsByType(printReferrers='ref' in args)
            if hasattr(self, 'baselineObjReport'):
                self.notify.info('calculating diff from baseline ObjectReport...')
                self.lastDiff = self.baselineObjReport.diff(report)
                self.lastDiff.printOut(full='diff' in args or 'dif' in args)
            if 'baseline' in args or not hasattr(self, 'baselineObjReport'):
                self.notify.info('recording baseline ObjectReport...')
                if hasattr(self, 'baselineObjReport'):
                    self.baselineObjReport.destroy()
                self.baselineObjReport = report
            self.setMagicWordResponse('objects logged')
        elif wordIs('~objectcount'):

            def handleObjectCountDone(objectCount):
                self.setMagicWordResponse('object count logged')

            oc = ObjectCount('~objectcount', doneCallback=handleObjectCountDone)
        elif wordIs('~objecthg'):
            import gc
            objs = gc.get_objects()
            type2count = {}
            for obj in objs:
                tn = safeTypeName(obj)
                type2count.setdefault(tn, 0)
                type2count[tn] += 1

            count2type = invertDictLossless(type2count)
            counts = list(count2type.keys())
            counts.sort()
            counts.reverse()
            for count in counts:
                print('%s: %s' % (count, count2type[count]))

            self.setMagicWordResponse('~aiobjecthg complete')
        elif wordIs('~containers'):
            args = word.split()
            limit = 30
            if 'full' in args:
                limit = None
            ContainerReport.ContainerReport('~containers', log=True, limit=limit, threaded=True)
        elif wordIs('~garbage'):
            args = word.split()
            full = 'full' in args
            safeMode = 'safe' in args
            delOnly = 'delonly' in args
            GarbageReport.GarbageLogger('~garbage', fullReport=full, threaded=True, safeMode=safeMode, delOnly=delOnly, doneCallback=self.garbageReportDone)
        elif wordIs('~guicreates'):
            base.printGuiCreates = True
            self.setMagicWordResponse('printing gui creation stacks')
        elif wordIs('~creategarbage'):
            GarbageReport._createGarbage()
        elif wordIs('~leakTask'):

            def leakTask(task):
                return task.cont

            taskMgr.add(leakTask, uniqueName('leakedTask'))
            leakTask = None
        elif wordIs('~leakmessage'):
            MessengerLeakDetector._leakMessengerObject()
            self.down_setMagicWordResponse(senderId, 'messenger leak object created')
        elif wordIs('~pstats'):
            args = word.split()
            hostname = None
            port = None
            if len(args) > 1:
                hostname = args[1]
            if len(args) > 2:
                port = int(args[2])
            base.wantStats = 1
            Task.TaskManager.pStatsTasks = 1
            result = base.createStats(hostname, port)
            connectionName = '%s' % hostname
            if port is not None:
                connectionName += ':%s' % port
            if result:
                response = 'connected client pstats to %s' % connectionName
            else:
                response = 'could not connect pstats to %s' % connectionName
            self.setMagicWordResponse(response)
        elif wordIs('~profile'):
            args = word.split()
            if len(args) > 1:
                num = int(args[1])
            else:
                num = 5
            session = taskMgr.getProfileSession('~profile')
            session.setLogAfterProfile(True)
            taskMgr.profileFrames(num, session)
            self.setMagicWordResponse('profiling %s client frames...' % num)
        elif wordIs('~frameprofile'):
            args = word.split()
            wasOn = bool(taskMgr.getProfileFrames())
            if len(args) > 1:
                setting = bool(int(args[1]))
            else:
                setting = not wasOn
            taskMgr.setProfileFrames(setting)
            self.setMagicWordResponse('frame profiling %s%s' % (choice(setting, 'ON', 'OFF'), choice(wasOn == setting, ' already', '')))
        elif wordIs('~taskprofile'):
            args = word.split()
            wasOn = bool(taskMgr.getProfileTasks())
            if len(args) > 1:
                setting = bool(int(args[1]))
            else:
                setting = not wasOn
            taskMgr.setProfileTasks(setting)
            self.setMagicWordResponse('task profiling %s%s' % (choice(setting, 'ON', 'OFF'), choice(wasOn == setting, ' already', '')))
        elif wordIs('~taskspikethreshold'):
            args = word.split()
            if len(args) > 1:
                threshold = float(args[1])
                response = 'task spike threshold set to %ss' % threshold
            else:
                threshold = TaskProfiler.GetDefaultSpikeThreshold()
                response = 'task spike threshold reset to %ss' % threshold
            TaskProfiler.SetSpikeThreshold(threshold)
            self.setMagicWordResponse(response)
        elif wordIs('~logtaskprofiles'):
            args = word.split()
            if len(args) > 1:
                name = args[1]
            else:
                name = None
            taskMgr.logTaskProfiles(name)
            response = 'logged task profiles%s' % choice(name, ' for %s' % name, '')
            self.setMagicWordResponse(response)
        elif wordIs('~taskprofileflush'):
            args = word.split()
            if len(args) > 1:
                name = args[1]
            else:
                name = None
            taskMgr.flushTaskProfiles(name)
            response = 'flushed AI task profiles%s' % choice(name, ' for %s' % name, '')
            self.setMagicWordResponse(response)
        elif wordIs('~dobjectcount'):
            base.cr.printObjectCount()
            self.setMagicWordResponse('logging client distributed object count...')
        elif wordIs('~taskmgr'):
            print(taskMgr)
            self.setMagicWordResponse('logging client taskMgr...')
        elif wordIs('~jobmgr'):
            print(jobMgr)
            self.setMagicWordResponse('logging client jobMgr...')
        elif wordIs('~jobtime'):
            args = word.split()
            if len(args) > 1:
                time = float(args[1])
            else:
                time = None
            response = ''
            if time is None:
                time = jobMgr.getDefaultTimeslice()
                response = 'reset client jobMgr timeslice to %s ms' % time
            else:
                response = 'set client jobMgr timeslice to %s ms' % time
                time = time / 1000.0
            jobMgr.setTimeslice(time)
            self.setMagicWordResponse(response)
        elif wordIs('~detectleaks'):
            started = self.cr.startLeakDetector()
            self.setMagicWordResponse(choice(started, 'leak detector started', 'leak detector already started'))
        elif wordIs('~taskthreshold'):
            args = word.split()
            if len(args) > 1.0:
                threshold = float(args[1])
            else:
                threshold = None
            response = ''
            if threshold is None:
                threshold = taskMgr.DefTaskDurationWarningThreshold
                response = 'reset task duration warning threshold to %s' % threshold
            else:
                response = 'set task duration warning threshold to %s' % threshold
            taskMgr.setTaskDurationWarningThreshold(threshold)
            self.setMagicWordResponse(response)
        elif wordIs('~messenger'):
            print(messenger)
            self.setMagicWordResponse('logging client messenger...')
        elif wordIs('~clientcrash'):
            DelayedCall(Functor(self.notify.error, '~clientcrash: simulating a client crash'))
        elif wordIs('~badDelete'):
            doId = 0
            while doId in base.cr.doId2do:
                doId += 1

            DelayedCall(Functor(base.cr.deleteObjectLocation, ScratchPad(doId=doId), 1, 1))
            self.setMagicWordResponse('doing bad delete')
        elif wordIs('~idTags'):
            messenger.send('nameTagShowAvId', [])
            base.idTags = 1
        elif wordIs('~nameTags'):
            messenger.send('nameTagShowName', [])
            base.idTags = 0
        elif wordIs('~hideNames'):
            if NametagGlobals.getMasterNametagsVisible():
                NametagGlobals.setMasterNametagsVisible(0)
            else:
                NametagGlobals.setMasterNametagsVisible(1)
        elif wordIs('~hideGui'):
            if aspect2d.isHidden():
                aspect2d.show()
            else:
                aspect2d.hide()
        elif wordIs('~flush'):
            base.cr.doDataCache.flush()
            base.cr.cache.flush()
            self.setMagicWordResponse('client object and data caches flushed')
        elif wordIs('~prof'):
            import time
            name = 'default'
            p = Point3()
            ts = time.time()
            for i in range(1000000):
                p.set(1, 2, 3)

            tf = time.time()
            dt = tf - ts
            response = 'prof(%s): %s secs' % (name, dt)
            print(response)
            self.setMagicWordResponse(response)
        elif wordIs('~gptc'):
            args = word.split()
            if len(args) > 1.0 and hasattr(self.cr, 'leakDetector'):
                gptcJob = self.cr.leakDetector.getPathsToContainers('~gptc', args[1], Functor(self._handleGPTCfinished, args[1]))
            else:
                self.setMagicWordResponse('error')
        elif wordIs('~gptcn'):
            args = word.split()
            if len(args) > 1.0 and hasattr(self.cr, 'leakDetector'):
                gptcnJob = self.cr.leakDetector.getPathsToContainersNamed('~gptcn', args[1], Functor(self._handleGPTCNfinished, args[1]))
            else:
                self.setMagicWordResponse('error')
        else:
            return 0
        return 1

    def toggleRun(self):
        if config.GetBool('want-running', 1):
            inputState.set('debugRunning', inputState.isSet('debugRunning') != True)

    def d_setMagicWord(self, magicWord, avId, zoneId):
        self.sendUpdate('setMagicWord', [magicWord,
         avId,
         zoneId,
         base.cr.userSignature])

    def b_setMagicWord(self, magicWord, avId=None, zoneId=None):
        if self.cr.wantMagicWords:
            if avId == None:
                avId = base.localAvatar.doId

            if zoneId == None:
                try:
                    zoneId = self.cr.playGame.getPlace().getZoneId()
                except:
                    pass

                if zoneId == None:
                    zoneId = 0

            self.d_setMagicWord(magicWord, avId, zoneId)
            if magicWord.count("~crash"):
                args = magicWord.split()
                errorCode = 12
                if len(args) > 1:
                    errorCode = int(args[1])
                self.notify.info("Simulating client crash: exit error = %s" % errorCode)
                base.exitShow(errorCode)

            if magicWord.count("~exception"):
                self.notify.error("~exception: simulating a client exception...")
                s = ""
                while 1:
                    s += "INVALIDNAME"
                    eval(s)

            self.setMagicWord(magicWord, avId, zoneId)

    def setMagicWordResponse(self, response):
        self.notify.info(response)
        base.localAvatar.setChatAbsolute(response, CFSpeech | CFTimeout)
        base.talkAssistant.receiveDeveloperMessage(response)

    def d_setWho(self, avIds):
        self.sendUpdate('setWho', [avIds])

    def _handleGPTCfinished(self, ct, gptcJob):
        self.setMagicWordResponse('gptc(%s) finished' % ct)

    def _handleGPTCNfinished(self, cn, gptcnJob):
        self.setMagicWordResponse('gptcn(%s) finished' % cn)

    def forAnother(self, word, avId, zoneId):
        b = 5
        while word[b:b + 2] != ' ~':
            b += 1
            if b >= len(word):
                self.setMagicWordResponse('No next magic word!')
                return

        nextWord = word[b + 1:]
        name = string.strip(word[5:b])
        id = self.identifyAvatar(name)
        if id == None:
            self.setMagicWordResponse("Don't know who %s is." % name)
            return
        self.d_setMagicWord(nextWord, id, zoneId)
        return

    def identifyAvatar(self, name):
        self.notify.error('Pure virtual - please override me.')

    def identifyDistributedObjects(self, name):
        result = []
        lowerName = string.lower(name)
        for obj in list(self.cr.doId2do.values()):
            className = obj.__class__.__name__
            try:
                name = obj.getName()
            except:
                name = className

            if string.lower(name) == lowerName or string.lower(className) == lowerName or string.lower(className) == 'distributed' + lowerName:
                result.append((name, obj))

        return result

    def doTex(self, word):
        args = word.split()
        if len(args) <= 1:
            if self.texViewer:
                self.texViewer.cleanup()
                self.texViewer = None
                return
            base.toggleTexture()
            return
        if self.texViewer:
            self.texViewer.cleanup()
            self.texViewer = None
        tex = TexturePool.findTexture(args[1])
        if not tex:
            tex = TexturePool.findTexture('*%s*' % args[1])
        if not tex:
            self.setMagicWordResponse('Unknown texture: %s' % args[1])
            return
        self.texViewer = TexViewer(tex)
        return

    def getCSBitmask(self, str):
        words = string.lower(str).split()
        if len(words) == 0:
            return None
        invalid = ''
        bitmask = BitMask32.allOff()
        for w in words:
            if w == 'wall':
                bitmask |= OTPGlobals.WallBitmask
            elif w == 'floor':
                bitmask |= OTPGlobals.FloorBitmask
            elif w == 'cam':
                bitmask |= OTPGlobals.CameraBitmask
            elif w == 'catch':
                bitmask |= OTPGlobals.CatchBitmask
            elif w == 'ghost':
                bitmask |= OTPGlobals.GhostBitmask
            elif w == 'pet':
                bitmask |= OTPGlobals.PetBitmask
            elif w == 'furniture':
                bitmask |= OTPGlobals.FurnitureSideBitmask | OTPGlobals.FurnitureTopBitmask | OTPGlobals.FurnitureDragBitmask
            elif w == 'furnitureside':
                bitmask |= OTPGlobals.FurnitureSideBitmask
            elif w == 'furnituretop':
                bitmask |= OTPGlobals.FurnitureTopBitmask
            elif w == 'furnituredrag':
                bitmask |= OTPGlobals.FurnitureDragBitmask
            elif w == 'pie':
                bitmask |= OTPGlobals.PieBitmask
            else:
                try:
                    bitmask |= BitMask32.bit(int(w))
                    print(bitmask)
                except ValueError:
                    invalid += ' ' + w

        if invalid:
            self.setMagicWordResponse('Unknown CS keyword(s): %s' % invalid)
        return bitmask

    def getFontByName(self, fontname):
        if fontname == 'default':
            return TextNode.getDefaultFont()
        elif fontname == 'interface':
            return OTPGlobals.getInterfaceFont()
        elif fontname == 'sign':
            return OTPGlobals.getSignFont()
        else:
            return None
        return None

    def showfont(self, fontname):
        fontname = string.strip(string.lower(fontname))
        font = self.getFontByName(fontname)
        if font == None:
            self.setMagicWordResponse('Unknown font: %s' % fontname)
            return
        if not isinstance(font, DynamicTextFont):
            self.setMagicWordResponse('Font %s is not dynamic.' % fontname)
            return
        self.hidefont()
        self.shownFontNode = aspect2d.attachNewNode('shownFont')
        tn = TextNode('square')
        tn.setCardActual(0.0, 1.0, -1.0, 0.0)
        tn.setFrameActual(0.0, 1.0, -1.0, 0.0)
        tn.setCardColor(1, 1, 1, 0.5)
        tn.setFrameColor(1, 1, 1, 1)
        tn.setFont(font)
        tn.setText(' ')
        numXPages = 2
        numYPages = 2
        pageScale = 0.8
        pageMargin = 0.1
        numPages = font.getNumPages()
        x = 0
        y = 0
        for pi in range(numPages):
            page = font.getPage(pi)
            tn.setCardTexture(page)
            np = self.shownFontNode.attachNewNode(tn.generate())
            np.setScale(pageScale)
            (np.setPos(float(x) / numXPages * 2 - 1 + pageMargin, 0, 1 - float(y) / numYPages * 2 - pageMargin),)
            x += 1
            if x >= numXPages:
                y += 1
                x = 0

        return

    def hidefont(self):
        if self.shownFontNode != None:
            self.shownFontNode.removeNode()
            self.shownFontNode = None
        return

    def showShadowCollisions(self):
        try:
            base.shadowTrav.showCollisions(render)
        except:
            self.setMagicWordResponse('CollisionVisualizer is not compiled in.')

    def hideShadowCollisions(self):
        base.shadowTrav.hideCollisions()

    def showCollisions(self):
        try:
            base.cTrav.showCollisions(render)
        except:
            self.setMagicWordResponse('CollisionVisualizer is not compiled in.')

    def hideCollisions(self):
        base.cTrav.hideCollisions()

    def showCameraCollisions(self):
        try:
            localAvatar.ccTrav.showCollisions(render)
        except:
            self.setMagicWordResponse('CollisionVisualizer is not compiled in.')

    def hideCameraCollisions(self):
        localAvatar.ccTrav.hideCollisions()

    def doFps(self, word, avId, zoneId):
        args = word.split()
        response = None
        if len(args) == 1 or args[1] == 'normal':
            if globalClock.getMode() != ClockObject.MNormal:
                globalClock.setMode(ClockObject.MNormal)
                response = 'Normal frame rate set.'
            else:
                base.setFrameRateMeter(not base.frameRateMeter)
        elif args[1] == 'forced':
            fps = float(args[2])
            globalClock.setMode(ClockObject.MForced)
            globalClock.setDt(1.0 / fps)
            response = 'Frame rate forced to %s fps.' % fps
            base.setFrameRateMeter(1)
        elif args[1] == 'degrade':
            factor = float(args[2])
            globalClock.setMode(ClockObject.MDegrade)
            globalClock.setDegradeFactor(factor)
            response = 'Frame rate degraded by factor of %s.' % factor
            base.setFrameRateMeter(1)
        elif args[1][-1] == '%':
            percent = float(args[1][:-1])
            if percent == 100:
                globalClock.setMode(ClockObject.MNormal)
                response = 'Normal frame rate set.'
            else:
                globalClock.setMode(ClockObject.MDegrade)
                globalClock.setDegradeFactor(100.0 / percent)
                response = 'Frame rate degraded to %s percent.' % percent
            base.setFrameRateMeter(1)
        else:
            try:
                fps = float(args[1])
            except:
                fps = None

            if fps != None:
                globalClock.setMode(ClockObject.MForced)
                globalClock.setDt(1.0 / fps)
                response = 'Frame rate forced to %s fps.' % fps
                base.setFrameRateMeter(1)
            else:
                response = 'Unknown fps command: ~s' % args[1]
        if base.frameRateMeter:
            globalClock.setAverageFrameRateInterval(ConfigVariableDouble('average-frame-rate-interval').getValue())
        if response != None:
            self.setMagicWordResponse(response)
        return

    def identifyAvatar(self, name):
        for av in Avatar.Avatar.ActiveAvatars:
            if isinstance(av, self.GameAvatarClass) and av.getName() == name:
                return av.doId

        lowerName = string.lower(name)
        for av in Avatar.Avatar.ActiveAvatars:
            if isinstance(av, self.GameAvatarClass) and string.strip(string.lower(av.getName())) == lowerName:
                return av.doId

        try:
            avId = int(name)
            return avId
        except:
            pass

        return None

    def toggleGuiPopup(self):
        if self.guiPopupShown:
            base.mouseWatcherNode.hideRegions()
            self.guiPopupShown = 0
        else:
            base.mouseWatcherNode.showRegions(render2d, 'gui-popup', 0)
            self.guiPopupShown = 1

    def garbageReportDone(self, garbageReport):
        self.setMagicWordResponse('%s garbage cycles' % garbageReport.getNumCycles())


def magicWord(mw):
    messenger.send('magicWord', [mw])


import builtins
builtins.magicWord = magicWord