from direct.directnotify import DirectNotifyGlobal
from direct.interval.IntervalGlobal import *
from direct.showbase import AppRunnerGlobal
from direct.showbase import DirectObject
from direct.showbase import PythonUtil
from pandac.PandaModules import *
from otp.speedchat import SpeedChatGlobals
from toontown.ai import DistributedBlackCatMgr
from toontown.chat.ChatGlobals import *
from toontown.suit import Suit
from toontown.suit import SuitDNA
from toontown.toon import ToonHeadFrame
from toontown.toonbase import TTLocalizer
from toontown.toonbase import ToontownBattleGlobals
from toontown.quest import QuestScripts
import copy, re, tokenize, BlinkingArrows, StringIO

notify = DirectNotifyGlobal.directNotify.newCategory('QuestParser')
lineDict = {}
globalVarDict = {}
curId = None
FLOAT = re.compile(r'[+-]?\d+[.]\d*([e][+-]\d+)?')

def init():
    globalVarDict.update({'render': render,
     'camera': camera,
     'hidden': hidden,
     'aspect2d': aspect2d,
     'localToon': base.localAvatar,
     'laffMeter': base.localAvatar.laffMeter,
     'inventory': base.localAvatar.inventory,
     'bFriendsList': base.localAvatar.bFriendsList,
     'book': base.localAvatar.book,
     'bookPrevArrow': base.localAvatar.book.prevArrow,
     'bookNextArrow': base.localAvatar.book.nextArrow,
     'bookOpenButton': base.localAvatar.book.bookOpenButton,
     'bookCloseButton': base.localAvatar.book.bookCloseButton,
     'chatNormalButton': base.localAvatar.chatMgr.normalButton,
     'chatScButton': base.localAvatar.chatMgr.scButton,
     'arrows': BlinkingArrows.BlinkingArrows()})

def clear():
    globalVarDict.clear()

def readFile():
    global curId
    script = StringIO.StringIO(QuestScripts.script)
    
    def readLine():
        return script.readline().replace('\r', '')
    
    gen = tokenize.generate_tokens(readLine)
    line = getLineOfTokens(gen)
    
    while line is not None:
        if line == []:
            line = getLineOfTokens(gen)
            continue
        if line[0] == 'ID':
            parseId(line)
        elif curId is None:
            notify.error('A script must begin with an ID')
        else:
            lineDict[curId].append(line)
        line = getLineOfTokens(gen)
    
    script.close()

def getLineOfTokens(gen):
    tokens = []
    nextNeg = 0
    try:
        token = gen.next()
    except StopIteration:
        return None
    if token[0] == tokenize.ENDMARKER:
        return None
    while token[0] != tokenize.NEWLINE and token[0] != tokenize.NL:
        if token[0] == tokenize.COMMENT:
            pass
        elif token[0] == tokenize.OP and token[1] == '-':
            nextNeg = 1
        elif token[0] == tokenize.NUMBER:
            if re.match(FLOAT, token[1]):
                number = float(token[1])
            else:
                number = int(token[1])
            if nextNeg:
                tokens.append(-number)
                nextNeg = 0
            else:
                tokens.append(number)
        elif token[0] == tokenize.STRING:
            tokens.append(eval(token[1]))
        elif token[0] == tokenize.NAME:
            tokens.append(token[1])
        else:
            notify.warning('Ignored token type: %s on line: %s' % (tokenize.tok_name[token[0]], token[2][0]))

        try:
            token = gen.next()
        except StopIteration:
            break

    return tokens

def parseId(line):
    global curId
    curId = line[1]
    notify.debug('Setting current scriptId to: %s' % curId)
    if questDefined(curId):
        notify.error('Already defined scriptId: %s' % curId)
    else:
        lineDict[curId] = []


def questDefined(scriptId):
    return scriptId in lineDict


class NPCMoviePlayer(DirectObject.DirectObject):

    def __init__(self, scriptId, toon, npc):
        DirectObject.DirectObject.__init__(self)
        self.scriptId = scriptId
        self.toon = toon
        self.isLocalToon = self.toon == base.localAvatar
        self.npc = npc
        self.privateVarDict = {}
        self.toonHeads = {}
        self.uniqueId = 'scriptMovie_' + str(self.scriptId) + '_' + str(toon.getDoId()) + '_' + str(npc.getDoId())
        self.setVar('toon', self.toon)
        self.setVar('npc', self.npc)
        self.chapterDict = {}
        self.timeoutTrack = None
        self.currentTrack = None
        return

    def getVar(self, varName):
        if varName in self.privateVarDict:
            return self.privateVarDict[varName]
        elif varName in globalVarDict:
            return globalVarDict[varName]
        elif varName.find('tomDialogue') > -1 or varName.find('harryDialogue') > -1:
            notify.warning('%s getting referenced. Tutorial Ack: %d                                  Place: %s' % (varName, base.localAvatar.tutorialAck, base.cr.playGame.hood))
            return None
        else:
            notify.error('Variable not defined: %s' % varName)
        return None

    def delVar(self, varName):
        if varName in self.privateVarDict:
            del self.privateVarDict[varName]
        elif varName in globalVarDict:
            del globalVarDict[varName]
        else:
            notify.warning('Variable not defined: %s' % varName)

    def setVar(self, varName, var):
        self.privateVarDict[varName] = var

    def cleanup(self):
        if self.currentTrack:
            self.currentTrack.pause()
            self.currentTrack = None
        self.ignoreAll()
        taskMgr.remove(self.uniqueId)
        for toonHeadFrame in self.toonHeads.values():
            toonHeadFrame.destroy()

        del self.toonHeads
        del self.privateVarDict
        del self.chapterDict
        del self.toon
        del self.npc
        del self.timeoutTrack
        return

    def timeout(self, fFinish = 0):
        if self.timeoutTrack:
            if fFinish:
                self.timeoutTrack.finish()
            else:
                self.timeoutTrack.start()

    def finishMovie(self):
        self.npc.finishMovie(self.toon, self.isLocalToon, 0.0)

    def playNextChapter(self, eventName, timeStamp = 0.0):
        trackList = self.chapterDict[eventName]
        if trackList:
            self.currentTrack = trackList.pop(0)
            self.currentTrack.start()
        else:
            notify.debug('Movie ended waiting for an event (%s)' % eventName)

    def play(self):
        lineNum = 0
        self.currentEvent = 'start'
        lines = lineDict.get(self.scriptId)
        if lines is None:
            notify.error('No movie defined for scriptId: %s' % self.scriptId)
        chapterList = []
        timeoutList = []
        for line in lines:
            lineNum += 1
            command = line[0]
            if command == 'UPON_TIMEOUT':
                uponTimeout = 1
                iList = timeoutList
                line = line[1:]
                command = line[0]
            else:
                uponTimeout = 0
                iList = chapterList
            if command == 'CALL':
                if uponTimeout:
                    self.notify.error('CALL not allowed in an UPON_TIMEOUT')
                iList.append(self.parseCall(line))
                continue
            elif command == 'DEBUG':
                iList.append(self.parseDebug(line))
                continue
            elif command == 'WAIT':
                if uponTimeout:
                    self.notify.error('WAIT not allowed in an UPON_TIMEOUT')
                iList.append(self.parseWait(line))
                continue
            elif command == 'CHAT':
                iList.append(self.parseChat(line))
                continue
            elif command == 'CLEAR_CHAT':
                iList.append(self.parseClearChat(line))
                continue
            elif command == 'FINISH_QUEST_MOVIE':
                chapterList.append(Func(self.finishMovie))
                continue
            elif command == 'CHAT_CONFIRM':
                if uponTimeout:
                    self.notify.error('CHAT_CONFIRM not allowed in an UPON_TIMEOUT')
                avatarName = line[1]
                avatar = self.getVar(avatarName)
                nextEvent = avatar.uniqueName('doneChatPage')
                iList.append(Func(self.acceptOnce, nextEvent, self.playNextChapter, [nextEvent]))
                iList.append(self.parseChatConfirm(line))
                self.closePreviousChapter(iList)
                chapterList = []
                self.currentEvent = nextEvent
                continue
            elif command == 'LOCAL_CHAT_CONFIRM':
                if uponTimeout:
                    self.notify.error('LOCAL_CHAT_CONFIRM not allowed in an UPON_TIMEOUT')
                avatarName = line[1]
                avatar = self.getVar(avatarName)
                nextEvent = avatar.uniqueName('doneChatPage')
                iList.append(Func(self.acceptOnce, nextEvent, self.playNextChapter, [nextEvent]))
                iList.append(self.parseLocalChatConfirm(line))
                self.closePreviousChapter(iList)
                chapterList = []
                self.currentEvent = nextEvent
                continue
            elif command == 'LOCAL_CHAT_PERSIST':
                iList.append(self.parseLocalChatPersist(line))
                continue
            elif command == 'LOCAL_CHAT_TO_CONFIRM':
                if uponTimeout:
                    self.notify.error('LOCAL_CHAT_TO_CONFIRM not allowed in an UPON_TIMEOUT')
                avatarName = line[1]
                avatar = self.getVar(avatarName)
                nextEvent = avatar.uniqueName('doneChatPage')
                iList.append(Func(self.acceptOnce, nextEvent, self.playNextChapter, [nextEvent]))
                iList.append(self.parseLocalChatToConfirm(line))
                self.closePreviousChapter(iList)
                chapterList = []
                self.currentEvent = nextEvent
                continue
            if self.isLocalToon:
                if command == 'LOAD':
                    self.parseLoad(line)
                elif command == 'LOAD_SFX':
                    self.parseLoadSfx(line)
                elif command == 'LOAD_SUIT':
                    self.parseLoadSuit(line)
                elif command == 'SET':
                    self.parseSet(line)
                elif command == 'LOCK_LOCALTOON':
                    iList.append(self.parseLockLocalToon(line))
                elif command == 'FREE_LOCALTOON':
                    iList.append(self.parseFreeLocalToon(line))
                elif command == 'REPARENTTO':
                    iList.append(self.parseReparent(line))
                elif command == 'WRTREPARENTTO':
                    iList.append(self.parseWrtReparent(line))
                elif command == 'SHOW':
                    iList.append(self.parseShow(line))
                elif command == 'HIDE':
                    iList.append(self.parseHide(line))
                elif command == 'POS':
                    iList.append(self.parsePos(line))
                elif command == 'HPR':
                    iList.append(self.parseHpr(line))
                elif command == 'SCALE':
                    iList.append(self.parseScale(line))
                elif command == 'POSHPRSCALE':
                    iList.append(self.parsePosHprScale(line))
                elif command == 'COLOR':
                    iList.append(self.parseColor(line))
                elif command == 'COLOR_SCALE':
                    iList.append(self.parseColorScale(line))
                elif command == 'ADD_LAFFMETER':
                    iList.append(self.parseAddLaffMeter(line))
                elif command == 'LAFFMETER':
                    iList.append(self.parseLaffMeter(line))
                elif command == 'OBSCURE_LAFFMETER':
                    iList.append(self.parseObscureLaffMeter(line))
                elif command == 'ARROWS_ON':
                    iList.append(self.parseArrowsOn(line))
                elif command == 'ARROWS_OFF':
                    iList.append(self.parseArrowsOff(line))
                elif command == 'START_THROB':
                    iList.append(self.parseStartThrob(line))
                elif command == 'STOP_THROB':
                    iList.append(self.parseStopThrob(line))
                elif command == 'SHOW_FRIENDS_LIST':
                    iList.append(self.parseShowFriendsList(line))
                elif command == 'HIDE_FRIENDS_LIST':
                    iList.append(self.parseHideFriendsList(line))
                elif command == 'SHOW_BOOK':
                    iList.append(self.parseShowBook(line))
                elif command == 'HIDE_BOOK':
                    iList.append(self.parseHideBook(line))
                elif command == 'ENABLE_CLOSE_BOOK':
                    iList.append(self.parseEnableCloseBook(line))
                elif command == 'OBSCURE_BOOK':
                    iList.append(self.parseObscureBook(line))
                elif command == 'OBSCURE_CHAT':
                    iList.append(self.parseObscureChat(line))
                elif command == 'ADD_INVENTORY':
                    iList.append(self.parseAddInventory(line))
                elif command == 'SET_INVENTORY':
                    iList.append(self.parseSetInventory(line))
                elif command == 'SET_INVENTORY_YPOS':
                    iList.append(self.parseSetInventoryYPos(line))
                elif command == 'SET_INVENTORY_DETAIL':
                    iList.append(self.parseSetInventoryDetail(line))
                elif command == 'PLAY_SFX':
                    iList.append(self.parsePlaySfx(line))
                elif command == 'STOP_SFX':
                    iList.append(self.parseStopSfx(line))
                elif command == 'PLAY_ANIM':
                    iList.append(self.parsePlayAnim(line))
                elif command == 'LOOP_ANIM':
                    iList.append(self.parseLoopAnim(line))
                elif command == 'LERP_POS':
                    iList.append(self.parseLerpPos(line))
                elif command == 'LERP_HPR':
                    iList.append(self.parseLerpHpr(line))
                elif command == 'LERP_SCALE':
                    iList.append(self.parseLerpScale(line))
                elif command == 'LERP_POSHPRSCALE':
                    iList.append(self.parseLerpPosHprScale(line))
                elif command == 'LERP_COLOR':
                    iList.append(self.parseLerpColor(line))
                elif command == 'LERP_COLOR_SCALE':
                    iList.append(self.parseLerpColorScale(line))
                elif command == 'DEPTH_WRITE_ON':
                    iList.append(self.parseDepthWriteOn(line))
                elif command == 'DEPTH_WRITE_OFF':
                    iList.append(self.parseDepthWriteOff(line))
                elif command == 'DEPTH_TEST_ON':
                    iList.append(self.parseDepthTestOn(line))
                elif command == 'DEPTH_TEST_OFF':
                    iList.append(self.parseDepthTestOff(line))
                elif command == 'SET_BIN':
                    iList.append(self.parseSetBin(line))
                elif command == 'CLEAR_BIN':
                    iList.append(self.parseClearBin(line))
                elif command == 'TOON_HEAD':
                    iList.append(self.parseToonHead(line))
                elif command == 'SEND_EVENT':
                    iList.append(self.parseSendEvent(line))
                elif command == 'FUNCTION':
                    iList.append(self.parseFunction(line))
                elif command == 'BLACK_CAT_LISTEN':
                    iList.append(self.parseBlackCatListen(line))
                elif command == 'SHOW_PREVIEW':
                    if uponTimeout:
                        self.notify.error('SHOW_PREVIEW not allowed in an UPON_TIMEOUT')
                    nextEvent = 'donePreview'
                    iList.append(Func(self.acceptOnce, nextEvent, self.playNextChapter, [nextEvent]))
                    iList.append(self.parsePreview(line))
                    self.closePreviousChapter(iList)
                    chapterList = []
                    self.currentEvent = nextEvent
                elif command == 'WAIT_EVENT':
                    if uponTimeout:
                        self.notify.error('WAIT_EVENT not allowed in an UPON_TIMEOUT')
                    nextEvent = self.parseWaitEvent(line)

                    def proceed(self = self, nextEvent = nextEvent):
                        self.playNextChapter(nextEvent)

                    def handleEvent(*args):
                        proceed = args[0]
                        proceed()

                    iList.append(Func(self.acceptOnce, nextEvent, handleEvent, [proceed]))
                    self.closePreviousChapter(iList)
                    chapterList = []
                    self.currentEvent = nextEvent
                else:
                    notify.warning('Unknown command token: %s for scriptId: %s on line: %s' % (command, self.scriptId, lineNum))

        self.closePreviousChapter(chapterList)
        if timeoutList:
            self.timeoutTrack = Sequence(*timeoutList)
        self.playNextChapter('start')
        return

    def closePreviousChapter(self, iList):
        trackList = self.chapterDict.setdefault(self.currentEvent, [])
        trackList.append(Sequence(*iList))

    def parseLoad(self, line):
        if len(line) == 3:
            token, varName, modelPath = line
            node = loader.loadModel(modelPath.replace('"', ''))
        elif len(line) == 4:
            token, varName, modelPath, subNodeName = line
            node = loader.loadModel(modelPath.replace('"', '')).find('**/' + subNodeName)
        else:
            notify.error('invalid parseLoad command')
        self.setVar(varName, node)

    def parseLoadSfx(self, line):
        token, varName, fileName = line
        sfx = base.loadSfx(fileName)
        self.setVar(varName, sfx)

    def parseLoadSuit(self, line):
        token, name, suitType = line
        suit = Suit.Suit()
        dna = SuitDNA.SuitDNA()
        dna.newSuit(suitType)
        suit.setDNA(dna)
        self.setVar(name, suit)

    def parseSet(self, line):
        token, varName, value = line
        self.setVar(varName, value)

    def parseCall(self, line):
        token, scriptId = line
        nmp = NPCMoviePlayer(scriptId, self.toon, self.npc)
        return Func(nmp.play)

    def parseLockLocalToon(self, line):
        return Sequence(Func(self.toon.detachCamera), Func(self.toon.collisionsOff), Func(self.toon.disableAvatarControls), Func(self.toon.stopTrackAnimToSpeed), Func(self.toon.stopUpdateSmartCamera))

    def parseFreeLocalToon(self, line):
        return Sequence(Func(self.toon.attachCamera), Func(self.toon.startTrackAnimToSpeed), Func(self.toon.collisionsOn), Func(self.toon.enableAvatarControls), Func(self.toon.startUpdateSmartCamera))

    def parseDebug(self, line):
        token, str = line
        return Func(notify.debug, str)

    def parseReparent(self, line):
        if len(line) == 3:
            token, childNodeName, parentNodeName = line
            subNodeName = None
        elif len(line) == 4:
            token, childNodeName, parentNodeName, subNodeName = line
        childNode = self.getVar(childNodeName)
        if subNodeName:
            parentNode = self.getVar(parentNodeName).find(subNodeName)
        else:
            parentNode = self.getVar(parentNodeName)
        return ParentInterval(childNode, parentNode)

    def parseWrtReparent(self, line):
        if len(line) == 3:
            token, childNodeName, parentNodeName = line
            subNodeName = None
        elif len(line) == 4:
            token, childNodeName, parentNodeName, subNodeName = line
        childNode = self.getVar(childNodeName)
        if subNodeName:
            parentNode = self.getVar(parentNodeName).find(subNodeName)
        else:
            parentNode = self.getVar(parentNodeName)
        return WrtParentInterval(childNode, parentNode)

    def parseShow(self, line):
        token, nodeName = line
        node = self.getVar(nodeName)
        return Func(node.show)

    def parseHide(self, line):
        token, nodeName = line
        node = self.getVar(nodeName)
        return Func(node.hide)

    def parsePos(self, line):
        token, nodeName, x, y, z = line
        node = self.getVar(nodeName)
        return Func(node.setPos, x, y, z)

    def parseHpr(self, line):
        token, nodeName, h, p, r = line
        node = self.getVar(nodeName)
        return Func(node.setHpr, h, p, r)

    def parseScale(self, line):
        token, nodeName, x, y, z = line
        node = self.getVar(nodeName)
        return Func(node.setScale, x, y, z)

    def parsePosHprScale(self, line):
        token, nodeName, x, y, z, h, p, r, sx, sy, sz = line
        node = self.getVar(nodeName)
        return Func(node.setPosHprScale, x, y, z, h, p, r, sx, sy, sz)

    def parseColor(self, line):
        token, nodeName, r, g, b, a = line
        node = self.getVar(nodeName)
        return Func(node.setColor, r, g, b, a)

    def parseColorScale(self, line):
        token, nodeName, r, g, b, a = line
        node = self.getVar(nodeName)
        return Func(node.setColorScale, r, g, b, a)

    def parseWait(self, line):
        token, waitTime = line
        return Wait(waitTime)

    def parseChat(self, line):
        toonId = self.toon.getDoId()
        avatarName = line[1]
        avatar = self.getVar(avatarName)
        chatString = eval('TTLocalizer.' + line[2])
        chatFlags = CFSpeech | CFTimeout
        quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[3:])
        if extraChatFlags:
            chatFlags |= extraChatFlags
        if len(dialogueList) > 0:
            dialogue = dialogueList[0]
        else:
            dialogue = None
        return Func(avatar.setChatAbsolute, chatString, chatFlags, dialogue)

    def parseClearChat(self, line):
        toonId = self.toon.getDoId()
        avatarName = line[1]
        avatar = self.getVar(avatarName)
        chatFlags = CFSpeech | CFTimeout
        return Func(avatar.setChatAbsolute, '', chatFlags)

    def parseExtraChatArgs(self, args):
        quitButton = 0
        extraChatFlags = None
        dialogueList = []
        for arg in args:
            if type(arg) == type(0):
                quitButton = arg
            elif type(arg) == type(''):
                if len(arg) > 2 and arg[:2] == 'CF':
                    extraChatFlags = eval(arg)
                else:
                    dialogueList.append(self.getVar(arg))
            else:
                pass
                #notify.error('invalid argument type')

        return (quitButton, extraChatFlags, dialogueList)

    def parseChatConfirm(self, line):
        lineLength = len(line)
        toonId = self.toon.getDoId()
        avatarName = line[1]
        avatar = self.getVar(avatarName)
        chatString = eval('TTLocalizer.' + line[2])
        quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[3:])
        return Func(avatar.setPageChat, toonId, 0, chatString, quitButton, extraChatFlags, dialogueList)

    def parseLocalChatConfirm(self, line):
        lineLength = len(line)
        avatarName = line[1]
        avatar = self.getVar(avatarName)
        chatString = eval('TTLocalizer.' + line[2])
        quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[3:])
        return Func(avatar.setLocalPageChat, chatString, quitButton, extraChatFlags, dialogueList)

    def parseLocalChatPersist(self, line):
        lineLength = len(line)
        avatarName = line[1]
        avatar = self.getVar(avatarName)
        chatString = eval('TTLocalizer.' + line[2])
        quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[3:])
        if len(dialogueList) > 0:
            dialogue = dialogueList[0]
        else:
            dialogue = None
        return Func(avatar.setChatAbsolute, chatString, CFSpeech, dialogue)

    def parseLocalChatToConfirm(self, line):
        lineLength = len(line)
        avatarKey = line[1]
        avatar = self.getVar(avatarKey)
        toAvatarKey = line[2]
        toAvatar = self.getVar(toAvatarKey)
        localizerAvatarName = toAvatar.getName().capitalize()
        toAvatarName = eval('TTLocalizer.' + localizerAvatarName)
        chatString = eval('TTLocalizer.' + line[3])
        chatString = chatString.replace('%s', toAvatarName)
        quitButton, extraChatFlags, dialogueList = self.parseExtraChatArgs(line[4:])
        return Func(avatar.setLocalPageChat, chatString, quitButton, extraChatFlags, dialogueList)

    def parsePlaySfx(self, line):
        if len(line) == 2:
            token, sfxName = line
            looping = 0
        elif len(line) == 3:
            token, sfxName, looping = line
        else:
            notify.error('invalid number of arguments')
        sfx = self.getVar(sfxName)
        return Func(base.playSfx, sfx, looping)

    def parseStopSfx(self, line):
        token, sfxName = line
        sfx = self.getVar(sfxName)
        return Func(sfx.stop)

    def parsePlayAnim(self, line):
        if len(line) == 3:
            token, actorName, animName = line
            playRate = 1.0
        elif len(line) == 4:
            token, actorName, animName, playRate = line
        else:
            notify.error('invalid number of arguments')
        actor = self.getVar(actorName)
        return Sequence(Func(actor.setPlayRate, playRate, animName), Func(actor.play, animName))

    def parseLoopAnim(self, line):
        if len(line) == 3:
            token, actorName, animName = line
            playRate = 1.0
        elif len(line) == 4:
            token, actorName, animName, playRate = line
        else:
            notify.error('invalid number of arguments')
        actor = self.getVar(actorName)
        return Sequence(Func(actor.setPlayRate, playRate, animName), Func(actor.loop, animName))

    def parseLerpPos(self, line):
        token, nodeName, x, y, z, t = line
        node = self.getVar(nodeName)
        return Sequence(LerpPosInterval(node, t, Point3(x, y, z), blendType='easeInOut'), duration=0.0)

    def parseLerpHpr(self, line):
        token, nodeName, h, p, r, t = line
        node = self.getVar(nodeName)
        return Sequence(LerpHprInterval(node, t, VBase3(h, p, r), blendType='easeInOut'), duration=0.0)

    def parseLerpScale(self, line):
        token, nodeName, x, y, z, t = line
        node = self.getVar(nodeName)
        return Sequence(LerpScaleInterval(node, t, VBase3(x, y, z), blendType='easeInOut'), duration=0.0)

    def parseLerpPosHprScale(self, line):
        token, nodeName, x, y, z, h, p, r, sx, sy, sz, t = line
        node = self.getVar(nodeName)
        return Sequence(LerpPosHprScaleInterval(node, t, VBase3(x, y, z), VBase3(h, p, r), VBase3(sx, sy, sz), blendType='easeInOut'), duration=0.0)

    def parseLerpColor(self, line):
        token, nodeName, sr, sg, sb, sa, er, eg, eb, ea, t = line
        node = self.getVar(nodeName)
        return Sequence(LerpColorInterval(node, t, VBase4(er, eg, eb, ea), startColorScale=VBase4(sr, sg, sb, sa), blendType='easeInOut'), duration=0.0)

    def parseLerpColorScale(self, line):
        token, nodeName, sr, sg, sb, sa, er, eg, eb, ea, t = line
        node = self.getVar(nodeName)
        return Sequence(LerpColorScaleInterval(node, t, VBase4(er, eg, eb, ea), startColorScale=VBase4(sr, sg, sb, sa), blendType='easeInOut'), duration=0.0)

    def parseDepthWriteOn(self, line):
        token, nodeName, depthWrite = line
        node = self.getVar(nodeName)
        return Sequence(Func(node.setDepthWrite, depthWrite))

    def parseDepthWriteOff(self, line):
        token, nodeName = line
        node = self.getVar(nodeName)
        return Sequence(Func(node.clearDepthWrite))

    def parseDepthTestOn(self, line):
        token, nodeName, depthTest = line
        node = self.getVar(nodeName)
        return Sequence(Func(node.setDepthTest, depthTest))

    def parseDepthTestOff(self, line):
        token, nodeName = line
        node = self.getVar(nodeName)
        return Sequence(Func(node.clearDepthTest))

    def parseSetBin(self, line):
        if len(line) == 3:
            token, nodeName, binName = line
            sortOrder = 0
        else:
            token, nodeName, binName, sortOrder = line
        node = self.getVar(nodeName)
        return Sequence(Func(node.setBin, binName, sortOrder))

    def parseClearBin(self, line):
        token, nodeName = line
        node = self.getVar(nodeName)
        return Sequence(Func(node.clearBin))

    def parseWaitEvent(self, line):
        token, eventName = line
        return eventName

    def parseSendEvent(self, line):
        token, eventName = line
        return Func(messenger.send, eventName)

    def parseFunction(self, line):
        token, objectName, functionName = line
        object = self.getVar(objectName)
        cfunc = compile('object' + '.' + functionName, '<string>', 'eval')
        return Func(eval(cfunc))

    def parseAddLaffMeter(self, line):
        token, maxHpDelta = line
        newMaxHp = maxHpDelta + self.toon.getMaxHp()
        newHp = newMaxHp
        laffMeter = self.getVar('laffMeter')
        return Func(laffMeter.adjustFace, newHp, newMaxHp)

    def parseLaffMeter(self, line):
        token, newHp, newMaxHp = line
        laffMeter = self.getVar('laffMeter')
        return Func(laffMeter.adjustFace, newHp, newMaxHp)

    def parseObscureLaffMeter(self, line):
        token, val = line
        return Func(self.toon.laffMeter.obscure, val)

    def parseAddInventory(self, line):
        token, track, level, number = line
        inventory = self.getVar('inventory')
        countSound = base.loadSfx('phase_3.5/audio/sfx/tick_counter.ogg')
        return Sequence(Func(base.playSfx, countSound), Func(inventory.buttonBoing, track, level), Func(inventory.addItems, track, level, number), Func(inventory.updateGUI, track, level))

    def parseSetInventory(self, line):
        token, track, level, number = line
        inventory = self.getVar('inventory')
        return Sequence(Func(inventory.setItem, track, level, number), Func(inventory.updateGUI, track, level))

    def parseSetInventoryYPos(self, line):
        token, track, level, yPos = line
        inventory = self.getVar('inventory')
        button = inventory.buttons[track][level].stateNodePath[0]
        text = button.find('**/+TextNode')
        return Sequence(Func(text.setY, yPos))

    def parseSetInventoryDetail(self, line):
        if len(line) == 2:
            token, val = line
        elif len(line) == 4:
            token, val, track, level = line
        else:
            notify.error('invalid line for parseSetInventoryDetail: %s' % line)
        inventory = self.getVar('inventory')
        if val == -1:
            return Func(inventory.noDetail)
        elif val == 0:
            return Func(inventory.hideDetail)
        elif val == 1:
            return Func(inventory.showDetail, track, level)
        else:
            notify.error('invalid inventory detail level: %s' % val)

    def parseShowFriendsList(self, line):
        from toontown.friends import FriendsListPanel
        return Func(FriendsListPanel.showFriendsListTutorial)

    def parseHideFriendsList(self, line):
        from toontown.friends import FriendsListPanel
        return Func(FriendsListPanel.hideFriendsListTutorial)

    def parseShowBook(self, line):
        return Sequence(Func(self.toon.book.setPage, self.toon.mapPage), Func(self.toon.book.enter), Func(self.toon.book.disableBookCloseButton))

    def parseEnableCloseBook(self, line):
        return Sequence(Func(self.toon.book.enableBookCloseButton))

    def parseHideBook(self, line):
        return Func(self.toon.book.exit)

    def parseObscureBook(self, line):
        token, val = line
        return Func(self.toon.book.obscureButton, val)

    def parseObscureChat(self, line):
        token, val0, val1 = line
        return Func(self.toon.chatMgr.obscure, val0, val1)

    def parseArrowsOn(self, line):
        arrows = self.getVar('arrows')
        token, x1, y1, h1, x2, y2, h2 = line
        return Func(arrows.arrowsOn, x1, y1, h1, x2, y2, h2)

    def parseArrowsOff(self, line):
        arrows = self.getVar('arrows')
        return Func(arrows.arrowsOff)

    def parseStartThrob(self, line):
        token, nodeName, r, g, b, a, r2, g2, b2, a2, t = line
        node = self.getVar(nodeName)
        startCScale = Point4(r, g, b, a)
        destCScale = Point4(r2, g2, b2, a2)
        self.throbIval = Sequence(LerpColorScaleInterval(node, t / 2.0, destCScale, startColorScale=startCScale, blendType='easeInOut'), LerpColorScaleInterval(node, t / 2.0, startCScale, startColorScale=destCScale, blendType='easeInOut'))
        return Func(self.throbIval.loop)

    def parseStopThrob(self, line):
        return Func(self.throbIval.finish)

    def parseToonHead(self, line):
        if len(line) == 5:
            token, toonName, x, z, toggle = line
            scale = 1.0
        else:
            token, toonName, x, z, toggle, scale = line
        toon = self.getVar(toonName)
        toonId = toon.getDoId()
        toonHeadFrame = self.toonHeads.get(toonId)
        if not toonHeadFrame:
            toonHeadFrame = ToonHeadFrame.ToonHeadFrame(toon)
            toonHeadFrame.tag1Node
            toonHeadFrame.hide()
            self.toonHeads[toonId] = toonHeadFrame
            self.setVar('%sToonHead' % toonName, toonHeadFrame)
        if toggle:
            return Sequence(Func(toonHeadFrame.setPos, x, 0, z), Func(toonHeadFrame.setScale, scale), Func(toonHeadFrame.show))
        else:
            return Func(toonHeadFrame.hide)

    def parseToonHeadScale(self, line):
        token, toonName, scale = line
        toon = self.getVar(toonName)
        toonId = toon.getDoId()
        toonHeadFrame = self.toonHeads.get(toonId)
        return Func(toonHeadFrame.setScale, scale)

    def parseBlackCatListen(self, line):
        token, enable = line
        if enable:

            def phraseSaid(phraseId):
                toontastic = 315
                if phraseId == toontastic:
                    base.cr.blackCatMgr.requestBlackCatTransformation()

            def enableBlackCatListen():
                self.acceptOnce(SpeedChatGlobals.SCStaticTextMsgEvent, phraseSaid)

            return Func(enableBlackCatListen)
        else:

            def disableBlackCatListen():
                self.ignore(SpeedChatGlobals.SCStaticTextMsgEvent)

            return Func(disableBlackCatListen)

    def parsePreview(self, line):
        self.oldTrackAccess = None

        def grabCurTrackAccess():
            self.oldTrackAccess = copy.deepcopy(base.localAvatar.getTrackAccess())

        def restoreTrackAccess():
            base.localAvatar.setTrackAccess(self.oldTrackAccess)

        minGagLevel = ToontownBattleGlobals.MIN_LEVEL_INDEX + 1
        maxGagLevel = ToontownBattleGlobals.MAX_LEVEL_INDEX + 1
        curGagLevel = minGagLevel

        def updateGagLevel(t, curGagLevel = curGagLevel):
            newGagLevel = int(round(t))
            if newGagLevel == curGagLevel:
                return
            curGagLevel = newGagLevel
            access = [0, 0, 0, 0, 0, 0, 0]
            
            for i in xrange(len(self.oldTrackAccess)):
                access[i] = curGagLevel if self.oldTrackAccess[i] > 0 else 0
            
            base.localAvatar.setTrackAccess(access)

        return Sequence(Func(grabCurTrackAccess), LerpFunctionInterval(updateGagLevel, fromData=1, toData=7, duration=0.3), WaitInterval(3.5), LerpFunctionInterval(updateGagLevel, fromData=7, toData=1, duration=0.3), Func(restoreTrackAccess), Func(messenger.send, 'donePreview'))

readFile()