from panda3d.core import *
from direct.interval.IntervalGlobal import *
from direct.showbase.PythonUtil import lineInfo, Functor
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObject
from otp.level import Level
from otp.level import LevelConstants
from otp.level import Entity
from otp.level import EditMgr
from .SpecImports import *
from .InGameEditorElements import *
from toontown.cogdominium import CogdoEntityCreator
import string

class InGameEditorEntityBase(InGameEditorElement):

    def __init__(self):
        InGameEditorElement.__init__(self)

    def attribChanged(self, attrib, value):
        Entity.Entity.attribChanged(self, attrib, value)
        print('attribChange: %s %s, %s = %s' % (self.level.getEntityType(self.entId),
         self.entId,
         attrib,
         repr(value)))

    def getTypeName(self):
        return self.level.getEntityType(self.entId)

    def privGetNamePrefix(self):
        return '[%s-%s] ' % (self.getTypeName(), self.entId)

    def privGetEntityName(self):
        return self.level.levelSpec.getEntitySpec(self.entId)['name']

    def getName(self):
        return '%s%s' % (self.privGetNamePrefix(), self.privGetEntityName())

    def setNewName(self, newName):
        prefix = self.privGetNamePrefix()
        if newName[:len(prefix)] == prefix:
            newName = newName[len(prefix):]
        oldName = self.privGetEntityName()
        if oldName != newName:
            self.level.setAttribEdit(self.entId, 'name', newName)

    def setParentEntId(self, parentEntId):
        self.parentEntId = parentEntId
        self.level.buildEntityTree()

    def setName(self, name):
        self.name = name
        self.level.buildEntityTree()


class InGameEditorEntity(Entity.Entity, InGameEditorEntityBase):

    def __init__(self, level, entId):
        Entity.Entity.__init__(self, level, entId)
        InGameEditorEntityBase.__init__(self)

    def id(self):
        return self.entId

    def destroy(self):
        Entity.Entity.destroy(self)


class InGameEditorEditMgr(EditMgr.EditMgr, InGameEditorEntityBase):

    def __init__(self, level, entId):
        EditMgr.EditMgr.__init__(self, level, entId)
        InGameEditorEntityBase.__init__(self)

    def destroy(self):
        EditMgr.EditMgr.destroy(self)


class AttribModifier(Entity.Entity, InGameEditorEntityBase):
    notify = DirectNotifyGlobal.directNotify.newCategory('AttribModifier')

    def __init__(self, level, entId):
        Entity.Entity.__init__(self, level, entId)
        InGameEditorEntityBase.__init__(self)

    def destroy(self):
        Entity.Entity.destroy(self)

    def setValue(self, value):
        if len(self.typeName) == 0:
            AttribModifier.notify.warning('no typeName set')
            return
        entTypeReg = self.level.entTypeReg
        if self.typeName not in entTypeReg.getAllTypeNames():
            AttribModifier.notify.warning('invalid typeName: %s' % self.typeName)
            return
        typeDesc = entTypeReg.getTypeDesc(self.typeName)
        if len(self.attribName) == 0:
            AttribModifier.notify.warning('no attribName set')
            return
        if self.attribName not in typeDesc.getAttribNames():
            AttribModifier.notify.warning('invalid attribName: %s' % self.attribName)
            return
        if len(value) == 0:
            AttribModifier.notify.warning('no value set')

        def setAttrib(entId, typeName = self.typeName, attribName = self.attribName, value = eval(value), recursive = self.recursive):
            if typeName == self.level.getEntityType(entId):
                self.level.setAttribEdit(entId, attribName, value)
            if recursive:
                entity = self.level.getEntity(entId)
                for child in entity.getChildren():
                    setAttrib(child.entId)

        setAttrib(self.parentEntId)


def getInGameEditorEntityCreatorClass(level):
    entCreator = level.createEntityCreator()
    EntCreatorClass = entCreator.__class__

    class InGameEditorEntityCreator(EntCreatorClass):

        def __init__(self, editor):
            EntCreatorClass.__init__(self, editor)
            entTypes = list(self.entType2Ctor.keys())
            for type in entTypes:
                self.entType2Ctor[type] = InGameEditorEntity

            self.entType2Ctor['editMgr'] = InGameEditorEditMgr
            self.entType2Ctor['attribModifier'] = AttribModifier

    return InGameEditorEntityCreator


class DistributedInGameEditor(DistributedObject.DistributedObject, Level.Level, InGameEditorElement):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedInGameEditor')

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        Level.Level.__init__(self)
        InGameEditorElement.__init__(self)
        self.editorInitialized = 0
        self.specModified = 0
        self.undoStack = []
        self.redoStack = []
        self.entCreateHandlerQ = []
        self.entitiesWeCreated = []
        self.nodePathId2EntId = {}

    def generate(self):
        self.notify.debug('generate')
        DistributedObject.DistributedObject.generate(self)
        base.inGameEditor = self

    def setEditorAvId(self, editorAvId):
        self.editorAvId = editorAvId

    def setEditUsername(self, editUsername):
        self.editUsername = editUsername

    def getEditUsername(self):
        return self.editUsername

    def setLevelDoId(self, levelDoId):
        self.levelDoId = levelDoId
        self.level = base.cr.doId2do[self.levelDoId]

    def getLevelDoId(self):
        return self.levelDoId

    def announceGenerate(self):
        self.notify.debug('announceGenerate')
        DistributedObject.DistributedObject.announceGenerate(self)
        if self.editorIsLocalToon():
            from otp.level import EditorGlobals
            EditorGlobals.assertReadyToEdit()
            self.notify.debug('requesting an up-to-date copy of the level spec')
            self.sendUpdate('requestCurrentLevelSpec')

    def setSpecSenderDoId(self, doId):
        DistributedInGameEditor.notify.debug('setSpecSenderDoId: %s' % doId)
        blobSender = base.cr.doId2do[doId]

        def setSpecBlob(specBlob, blobSender = blobSender, self = self):
            blobSender.sendAck()
            from otp.level.LevelSpec import LevelSpec
            curSpec = eval(specBlob)
            self.gotCurrentSpec(curSpec)

        if blobSender.isComplete():
            setSpecBlob(blobSender.getBlob())
        else:
            evtName = self.uniqueName('specDone')
            blobSender.setDoneEvent(evtName)
            self.acceptOnce(evtName, setSpecBlob)

    def gotCurrentSpec(self, curSpec):
        self.entTypeReg = self.level.getEntityTypeReg()
        curSpec.setEntityTypeReg(self.entTypeReg)
        self.axis = loader.loadModel('models/misc/xyzAxis.bam')
        self.axis.setColorOff()
        self.axis.setColorScale(1, 1, 1, 1, 1)
        self.initializeLevel(self.doId, curSpec, curSpec.getScenario())
        entCreator = self.level.createEntityCreator()
        self.entTypes = entCreator.getEntityTypes()
        self.selectedEntity = None
        base.startTk()
        from . import InGameEditor
        doneEvent = self.uniqueName('editorDone')
        saveAsEvent = self.uniqueName('saveSpec')
        requestSaveEvent = self.uniqueName('requestSpecSave')
        undoEvent = self.uniqueName('undoEvent')
        redoEvent = self.uniqueName('redoEvent')
        wireframeEvent = self.uniqueName('wireframeEvent')
        oobeEvent = self.uniqueName('oobeEvent')
        csEvent = self.uniqueName('csEvent')
        runEvent = self.uniqueName('runEvent')
        texEvent = self.uniqueName('texEvent')
        self.editor = InGameEditor.InGameEditor(level=self, doneEvent=doneEvent, requestSaveEvent=requestSaveEvent, saveAsEvent=saveAsEvent, undoEvent=undoEvent, redoEvent=redoEvent, wireframeEvent=wireframeEvent, oobeEvent=oobeEvent, csEvent=csEvent, runEvent=runEvent, texEvent=texEvent)
        self.acceptOnce(doneEvent, self.doneEditing)
        self.accept(saveAsEvent, self.saveSpec)
        self.accept(requestSaveEvent, self.requestSpecSave)
        self.accept(undoEvent, self.doUndo)
        self.accept(redoEvent, self.doRedo)
        self.accept(wireframeEvent, self.doWireframe)
        self.accept(oobeEvent, self.doOobe)
        self.accept(csEvent, self.doCs)
        self.accept(runEvent, self.doRun)
        self.accept(texEvent, self.doTex)
        self.accept(self.editor.getEventMsgName('Select'), self.handleEntitySelect)
        self.accept(self.editor.getEventMsgName('Flash'), self.handleEntitySelect)
        self.editorInitialized = 1
        self.buildEntityTree()
        return

    def editorIsLocalToon(self):
        return self.editorAvId == base.localAvatar.doId

    def createEntityCreator(self):
        return getInGameEditorEntityCreatorClass(self.level)(self)

    def doneEditing(self):
        self.notify.debug('doneEditing')
        if self.specModified:
            if self.editor.askYesNo('Save the spec on the AI?'):
                self.requestSpecSave()
        self.sendUpdate('setFinished')

    def disable(self):
        self.notify.debug('disable')
        if self.editorInitialized and self.editorIsLocalToon():
            self.axis.removeNode()
            del self.axis
            if hasattr(self, 'entTypeReg'):
                del self.entTypeReg
            self.editorInitialized = 0
            Level.Level.destroyLevel(self)
            if hasattr(self, 'editor'):
                self.editor.quit()
                del self.editor
        DistributedObject.DistributedObject.disable(self)
        self.ignoreAll()

    def getEntInstance(self, entId):
        return self.level.getEntity(entId)

    def getEntInstanceNP(self, entId):
        entity = self.getEntInstance(entId)
        if entity is None:
            return
        if isinstance(entity, NodePath):
            return entity
        if hasattr(entity, 'getNodePath'):
            return entity.getNodePath()
        return

    def getEntInstanceNPCopy(self, entId):
        np = self.getEntInstanceNP(entId)
        if np is None:
            return np
        stashNodeGroups = []
        searches = ('**/+ActorNode', '**/+Character')
        for search in searches:
            stashNodeGroups.append(np.findAllMatches(search))

        for group in stashNodeGroups:
            if not group.isEmpty():
                group.stash()

        par = np.getParent()
        copy = np.copyTo(par)
        for group in stashNodeGroups:
            if not group.isEmpty():
                group.unstash()

        return copy

    def saveSpec(self, filename):
        return self.levelSpec.saveToDisk(filename)

    def setEntityParent(self, entity, parent):
        parent.addChild(entity)
        entity._parentEntity = parent

    def insertEntityIntoTree(self, entId):
        ent = self.getEntity(entId)
        if entId == LevelConstants.UberZoneEntId:
            self.setEntityParent(ent, self)
            return
        parentEnt = self.getEntity(ent.parentEntId)
        if parentEnt is not None:
            self.setEntityParent(ent, parentEnt)
            return
        self.setEntityParent(ent, self.uberZoneEntity)
        return

    def buildEntityTree(self):
        self.setChildren([])
        entIds = list(self.entities.keys())
        entIds.sort()
        for entId in entIds:
            ent = self.getEntity(entId)
            ent.setChildren([])

        for entId in entIds:
            self.insertEntityIntoTree(entId)

        self.editor.refreshExplorer()

    def onEntityCreate(self, entId):
        DistributedInGameEditor.notify.debug('onEntityCreate %s' % entId)
        Level.Level.onEntityCreate(self, entId)
        entityNP = self.getEntInstanceNP(entId)
        if entityNP:
            self.nodePathId2EntId[entityNP.id()] = entId
        if not self.editorInitialized:
            return
        self.insertEntityIntoTree(entId)
        self.editor.refreshExplorer()
        if entId == self.entitiesWeCreated[0]:
            self.entitiesWeCreated = self.entitiesWeCreated[1:]
            self.editor.selectEntity(entId)

    def onEntityDestroy(self, entId):
        DistributedInGameEditor.notify.debug('onEntityDestroy %s' % entId)
        ent = self.getEntity(entId)
        if self.editorInitialized:
            entityNP = self.getEntInstanceNP(entId)
            if entityNP in self.nodePathId2EntId:
                del self.nodePathId2EntId[entityNP.id()]
            if ent is self.selectedEntity:
                self.editor.clearAttribEditPane()
                self.selectedEntity = None
            ent._parentEntity.removeChild(ent)
            del ent._parentEntity
            self.editor.refreshExplorer()
        Level.Level.onEntityDestroy(self, entId)
        return

    def handleEntitySelect(self, entity):
        self.selectedEntity = entity
        if hasattr(self, 'identifyIval'):
            self.identifyIval.finish()
        if entity is self:
            self.editor.clearAttribEditPane()
        else:
            entityNP = self.getEntInstanceNP(entity.entId)
            if entityNP is not None:
                dur = float(0.5)
                oColor = entityNP.getColorScale()
                flashIval = Sequence(Func(Functor(entityNP.setColorScale, 1, 0, 0, 1)), WaitInterval(dur / 3), Func(Functor(entityNP.setColorScale, 0, 1, 0, 1)), WaitInterval(dur / 3), Func(Functor(entityNP.setColorScale, 0, 0, 1, 1)), WaitInterval(dur / 3), Func(Functor(entityNP.setColorScale, oColor[0], oColor[1], oColor[2], oColor[3])))
                boundIval = Sequence(Func(entityNP.showBounds), WaitInterval(dur * 0.5), Func(entityNP.hideBounds))
                entCp = self.getEntInstanceNPCopy(entity.entId)
                entCp.setRenderModeWireframe()
                entCp.setTextureOff(1)
                wireIval = Sequence(Func(Functor(entCp.setColor, 1, 0, 0, 1, 1)), WaitInterval(dur / 3), Func(Functor(entCp.setColor, 0, 1, 0, 1, 1)), WaitInterval(dur / 3), Func(Functor(entCp.setColor, 0, 0, 1, 1, 1)), WaitInterval(dur / 3), Func(entCp.removeNode))
                self.identifyIval = Parallel(flashIval, boundIval, wireIval)

                def putAxis(self = self, entityNP = entityNP):
                    self.axis.reparentTo(entityNP)
                    self.axis.setPos(0, 0, 0)
                    self.axis.setHpr(0, 0, 0)

                def takeAxis(self = self):
                    self.axis.reparentTo(hidden)

                self.identifyIval = Sequence(Func(putAxis), Parallel(self.identifyIval, WaitInterval(1000.5)), Func(takeAxis))
                self.identifyIval.start()
            self.editor.updateAttribEditPane(entity.entId, self.levelSpec, self.entTypeReg)
            entType = self.getEntityType(entity.entId)
            menu = self.editor.menuBar.component('Entity-menu')
            index = menu.index('Remove Selected Entity')
            if entType in self.entTypeReg.getPermanentTypeNames():
                menu.entryconfigure(index, state='disabled')
            else:
                menu.entryconfigure(index, state='normal')
        return

    def privSendAttribEdit(self, entId, attrib, value):
        self.specModified = 1
        valueStr = repr(value)
        self.notify.debug("sending edit: %s: '%s' = %s" % (entId, attrib, valueStr))
        self.sendUpdate('setEdit', [entId,
         attrib,
         valueStr,
         self.editUsername])

    def privExecActionList(self, actions):
        for action in actions:
            if callable(action):
                action()
            else:
                entId, attrib, value = action
                self.privSendAttribEdit(entId, attrib, value)

    def setUndoableAttribEdit(self, old2new, new2old):
        self.redoStack = []
        self.undoStack.append((new2old, old2new))
        self.privExecActionList(old2new)

    def setAttribEdit(self, entId, attrib, value, canUndo = 1):
        oldValue = eval(repr(self.levelSpec.getEntitySpec(entId)[attrib]))
        new2old = [(entId, attrib, oldValue)]
        old2new = [(entId, attrib, value)]
        self.setUndoableAttribEdit(old2new, new2old)
        if not canUndo:
            self.undoStack = []

    def doUndo(self):
        if len(self.undoStack) == 0:
            self.editor.showWarning('Nothing left to undo')
            return
        undo = self.undoStack.pop()
        self.redoStack.append(undo)
        new2old, old2new = undo
        self.privExecActionList(new2old)

    def doRedo(self):
        if len(self.redoStack) == 0:
            self.editor.showWarning('Nothing to redo')
            return
        redo = self.redoStack.pop()
        self.undoStack.append(redo)
        new2old, old2new = redo
        self.privExecActionList(old2new)

    def doWireframe(self):
        messenger.send('magicWord', ['~wire'])

    def doOobe(self):
        messenger.send('magicWord', ['~oobe'])

    def doCs(self):
        messenger.send('magicWord', ['~cs'])

    def doRun(self):
        messenger.send('magicWord', ['~run'])

    def doTex(self):
        messenger.send('magicWord', ['~tex'])

    def insertEntity(self, entType, parentEntId = None, callback = None):
        if parentEntId is None:
            try:
                parentEntId = self.selectedEntity.entId
            except AttributeError:
                self.editor.showWarning('Please select a valid parent entity first.', 'error')
                return

        removeAction = (self.editMgrEntity.entId, 'removeEntity', {'entId': 'REPLACEME'})
        new2old = [removeAction]

        def setNewEntityId(entId, self = self, action = removeAction, callback = callback):
            action[2]['entId'] = entId
            if callback:
                callback(entId)

        def setEntCreateHandler(self = self, handler = setNewEntityId):
            self.entCreateHandlerQ.append(handler)

        old2new = [setEntCreateHandler, (self.editMgrEntity.entId, 'requestNewEntity', {'entType': entType,
           'parentEntId': parentEntId,
           'username': self.editUsername})]
        self.setUndoableAttribEdit(old2new, new2old)
        return

    def setEntityCreatorUsername(self, entId, editUsername):
        Level.Level.setEntityCreatorUsername(self, entId, editUsername)
        if editUsername == self.getEditUsername():
            print('entity %s about to be created; we requested it' % entId)
            callback = self.entCreateHandlerQ[0]
            del self.entCreateHandlerQ[:1]
            callback(entId)
            self.entitiesWeCreated.append(entId)

    def removeSelectedEntity(self):
        try:
            selectedEntId = self.selectedEntity.entId
        except AttributeError:
            self.editor.showWarning('Please select a valid entity to be removed first.', 'error')
            return -1

        if self.getEntity(selectedEntId).getNumChildren() > 0:
            self.editor.showWarning('Remove children first.')
            return -1
        self.doRemoveEntity(selectedEntId)

    def removeSelectedEntityTree(self):
        try:
            selectedEntId = self.selectedEntity.entId
        except AttributeError:
            self.editor.showWarning('Please select a valid entity to be removed first.', 'error')
            return -1

        def removeEntity(entId):
            entity = self.getEntity(entId)
            for child in entity.getChildren():
                removeEntity(child.entId)

            self.doRemoveEntity(entId)

        removeEntity(selectedEntId)

    def doRemoveEntity(self, entId):
        parentEntId = self.getEntity(entId)._parentEntity.entId
        entType = self.getEntityType(entId)
        if entType in self.entTypeReg.getPermanentTypeNames():
            self.editor.showWarning("Cannot remove entities of type '%s'" % entType)
            return
        removeAction = (self.editMgrEntity.entId, 'removeEntity', {'entId': entId})
        old2new = [removeAction]
        oldAttribs = []
        spec = self.levelSpec.getEntitySpecCopy(entId)
        del spec['type']
        for attrib, value in list(spec.items()):
            oldAttribs.append((attrib, value))

        def setNewEntityId(entId, self = self, removeAction = removeAction, oldAttribs = oldAttribs):
            removeAction[2]['entId'] = entId
            for attrib, value in list(spec.items()):
                self.privSendAttribEdit(entId, attrib, value)

        def setEntCreateHandler(self = self, handler = setNewEntityId):
            self.entCreateHandlerQ.append(handler)

        new2old = [setEntCreateHandler, (self.editMgrEntity.entId, 'requestNewEntity', {'entType': self.getEntityType(entId),
           'parentEntId': parentEntId,
           'username': self.editUsername})]
        self.setUndoableAttribEdit(old2new, new2old)

    def makeCopyOfEntName(self, name):
        prefix = 'copy of '
        suffix = ' (%s)'
        oldName = name
        if len(oldName) <= len(prefix):
            newName = prefix + oldName
        elif oldName[:len(prefix)] != prefix:
            newName = prefix + oldName
        else:
            hasSuffix = True
            copyNum = 2
            if oldName[-1] != ')':
                hasSuffix = False
            if hasSuffix and oldName[-2] in string.digits:
                i = len(oldName) - 2
                numString = ''
                while oldName[i] in string.digits:
                    numString = oldName[i] + numString
                    i -= 1

                if oldName[i] != '(':
                    hasSuffix = False
                else:
                    i -= 1
                    if oldName[i] != ' ':
                        hasSuffix = False
                    else:
                        print('numString: %s' % numString)
                        copyNum = int(numString) + 1
            if hasSuffix:
                newName = oldName[:i] + suffix % copyNum
            else:
                newName = oldName + suffix % copyNum
        return newName

    def duplicateSelectedEntity(self):
        try:
            selectedEntId = self.selectedEntity.entId
            parentEntId = self.selectedEntity._parentEntity.entId
        except AttributeError:
            self.editor.showWarning('Please select a valid entity to be removed first.', 'error')
            return

        if self.selectedEntity.getNumChildren() > 0:
            self.editor.showTodo('Cannot duplicate entity with children.')
            return
        removeAction = (self.editMgrEntity.entId, 'removeEntity', {'entId': selectedEntId})
        new2old = [removeAction]
        copyAttribs = self.levelSpec.getEntitySpecCopy(selectedEntId)
        copyAttribs['comment'] = ''
        copyAttribs['name'] = self.makeCopyOfEntName(copyAttribs['name'])
        typeDesc = self.entTypeReg.getTypeDesc(copyAttribs['type'])
        attribDescs = typeDesc.getAttribDescDict()
        for attribName, attribDesc in list(attribDescs.items()):
            if attribDesc.getDatatype() == 'const':
                del copyAttribs[attribName]

        def setNewEntityId(entId, self = self, removeAction = removeAction, copyAttribs = copyAttribs):
            removeAction[2]['entId'] = entId
            for attribName, value in list(copyAttribs.items()):
                self.privSendAttribEdit(entId, attribName, value)

        def setEntCreateHandler(self = self, handler = setNewEntityId):
            self.entCreateHandlerQ.append(handler)

        old2new = [setEntCreateHandler, (self.editMgrEntity.entId, 'requestNewEntity', {'entType': self.getEntityType(selectedEntId),
           'parentEntId': parentEntId,
           'username': self.editUsername})]
        self.setUndoableAttribEdit(old2new, new2old)

    def specPrePickle(self, spec):
        for attribName, value in list(spec.items()):
            spec[attribName] = repr(value)

        return spec

    def specPostUnpickle(self, spec):
        for attribName, value in list(spec.items()):
            spec[attribName] = eval(value)

        return spec

    def handleImportEntities(self):
        try:
            selectedEntId = self.selectedEntity.entId
        except AttributeError:
            self.editor.showWarning('Please select a valid entity first.', 'error')
            return

        import tkinter.filedialog
        filename = tkinter.filedialog.askopenfilename(parent=self.editor.parent, defaultextension='.egroup', filetypes=[('Entity Group', '.egroup'), ('All Files', '*')])
        if len(filename) == 0:
            return
        try:
            import pickle
            f = open(filename, 'r')
            eTree = pickle.load(f)
            eGroup = pickle.load(f)
            for entId, spec in list(eGroup.items()):
                eGroup[entId] = self.specPostUnpickle(spec)

        except:
            self.editor.showWarning("Error importing entity group from '%s'." % filename, 'error')
            return

        oldEntId2new = {}

        def addEntities(treeEntry, parentEntId, eGroup = eGroup):
            for entId, children in list(treeEntry.items()):
                spec = eGroup[entId]
                entType = spec['type']
                del spec['type']
                del spec['parentEntId']
                typeDesc = self.entTypeReg.getTypeDesc(entType)
                for attribName, attribDesc in list(typeDesc.getAttribDescDict().items()):
                    if attribDesc.getDatatype() == 'const':
                        if attribName in spec:
                            del spec[attribName]

                def handleEntityInsertComplete(newEntId, oldEntId = entId, oldEntId2new = oldEntId2new, spec = spec, treeEntry = treeEntry, addEntities = addEntities):
                    oldEntId2new[oldEntId] = newEntId

                    def assignAttribs(entId = newEntId, oldEntId = oldEntId, spec = spec, treeEntry = treeEntry):
                        for attribName in spec:
                            self.setAttribEdit(entId, attribName, spec[attribName])

                        addEntities(treeEntry[oldEntId], newEntId)

                    self.acceptOnce(self.getEntityCreateEvent(newEntId), assignAttribs)

                self.insertEntity(entType, parentEntId=parentEntId, callback=handleEntityInsertComplete)

        addEntities(eTree, selectedEntId)

    def handleExportEntity(self):
        try:
            selectedEntId = self.selectedEntity.entId
        except AttributeError:
            self.editor.showWarning('Please select a valid entity first.', 'error')
            return

        import tkinter.filedialog
        filename = tkinter.filedialog.asksaveasfilename(parent=self.editor.parent, defaultextension='.egroup', filetypes=[('Entity Group', '.egroup'), ('All Files', '*')])
        if len(filename) == 0:
            return
        eTree = {selectedEntId: {}}
        eGroup = {}
        eGroup[selectedEntId] = self.levelSpec.getEntitySpecCopy(selectedEntId)
        for entId, spec in list(eGroup.items()):
            eGroup[entId] = self.specPrePickle(spec)

        try:
            import pickle
            f = open(filename, 'w')
            pickle.dump(eTree, f)
            pickle.dump(eGroup, f)
        except:
            self.editor.showWarning("Error exporting entity group to '%s'." % filename, 'error')
            return

    def handleExportEntityTree(self):
        try:
            selectedEntId = self.selectedEntity.entId
        except AttributeError:
            self.editor.showWarning('Please select a valid entity first.', 'error')
            return

        import tkinter.filedialog
        filename = tkinter.filedialog.asksaveasfilename(parent=self.editor.parent, defaultextension='.egroup', filetypes=[('Entity Group', '.egroup'), ('All Files', '*')])
        if len(filename) == 0:
            return
        eTree = {}
        eGroup = {}

        def addEntity(entId, treeEntry):
            treeEntry[entId] = {}
            eGroup[entId] = self.levelSpec.getEntitySpecCopy(entId)
            entity = self.getEntity(entId)
            for child in entity.getChildren():
                addEntity(child.entId, treeEntry[entId])

        addEntity(selectedEntId, eTree)
        for entId, spec in list(eGroup.items()):
            eGroup[entId] = self.specPrePickle(spec)

        try:
            import pickle
            f = open(filename, 'w')
            pickle.dump(eTree, f)
            pickle.dump(eGroup, f)
        except:
            self.editor.showWarning("Error exporting entity group to '%s'." % filename, 'error')
            return

    def moveAvToSelected(self):
        try:
            selectedEntId = self.selectedEntity.entId
        except AttributeError:
            self.editor.showWarning('Please select a valid entity first.', 'error')
            return

        entNp = self.getEntInstanceNP(selectedEntId)
        if entNp is None:
            zoneEntId = self.levelSpec.getEntityZoneEntId(selectedEntId)
            entNp = self.getEntInstanceNP(zoneEntId)
        base.localAvatar.setPos(entNp, 0, 0, 0)
        base.localAvatar.setHpr(entNp, 0, 0, 0)
        zoneNum = self.getEntityZoneEntId(selectedEntId)
        self.level.enterZone(zoneNum)
        return

    def requestSpecSave(self):
        self.privSendAttribEdit(LevelConstants.EditMgrEntId, 'requestSave', None)
        self.specModified = 0
        return

    def setAttribChange(self, entId, attrib, valueStr, username):
        if username == self.editUsername:
            print('we got our own edit back!')
        value = eval(valueStr)
        self.levelSpec.setAttribChange(entId, attrib, value, username)

    def getTypeName(self):
        return 'Level'