"""   QuArK  -  Quake Army Knife

Python macros available for direct call by QuArK
"""

#
#$Header: /cvsroot/quark/runtime/plugins/entdef2qrk.py,v 1.6 2003/12/17 13:58:59 peter-b Exp $
#

import time, sys

class Key:
    def __init__(self):
        self.m_keyname = None
        self.m_desc = ""
        self.m_defaultvalue = None

    def SetKeyname(self, keyname):
        self.m_keyname = keyname

    def SetDesc(self, desc):
        self.m_desc = desc

    def SetDefaultValue(self, defvalue):
        self.m_defaultvalue = defvalue

    def GetDefaultValue(self):
        return self.m_defaultvalue

    def GenerateFolder(self, indent):
        if (self.m_defaultvalue is None) or (self.m_defaultvalue == ""):
            return None
        indent[self.m_keyname] = str(self.m_defaultvalue)
        return None

    def GenerateForm(self, indent):
        return "This is pure virtual"

    def AddKeyFlag(self, value, desc, selected):
        return "This is pure virtual"

    def AddKeyChoice(self, value, desc):
        return "This is pure virtual"

class KeyString(Key):
    def __init__(self):
        Key.__init__(self)

    def GenerateForm(self, indent):
        s = quarkx.newobj(self.m_keyname + ":")
        # s["txt"] = "&"
        s["hint"] = self.m_desc
        indent.appenditem(s)
        return s

class KeyNumeric(Key):
    def __init__(self):
        Key.__init__(self)

    def GenerateForm(self, indent):
        s = quarkx.newobj(self.m_keyname + ":")
        # s["txt"] = "&"
        s["hint"] = self.m_desc
        indent.appenditem(s)
        return None

class KeyFlags(Key):
    def __init__(self):
        Key.__init__(self)
        self.m_flags = []

    def AddKeyFlag(self, value, desc, selected):
        self.m_flags = self.m_flags + [(value, desc)]
        if (int(selected) > 0):
            try:
                oldvalue = int(self.GetDefaultValue())
            except:
                oldvalue = 0
            self.SetDefaultValue(oldvalue + int(value))

    def GenerateForm(self, indent):
        s = ""
        nl = "" # no first newline
        for value, desc in self.m_flags:
          s = quarkx.newobj(self.m_keyname + ":")
          # s["txt"] = "&"
          # s["hint"] = ""
          s["typ"] = "X"+value
          s["cap"] = desc
          indent.appenditem(s)
        return None

class KeyChoices(Key):
    def __init__(self):
        Key.__init__(self)
        self.m_choices = []

    def AddKeyChoice(self, value, desc):
        self.m_choices = self.m_choices + [(value, desc)]

    def GenerateForm(self, indent):
        s = quarkx.newobj(self.m_keyname + ":")
        # s["txt"] = "&"
        s["hint"] = self.m_desc
        s["typ"] = "C"
        indent.appenditem(s)
        it = ""
        vl = ""
        c = 0
        for value, desc in self.m_choices:
          it = it + desc
          vl = vl + value
          c = c + 1
          if (c <> len(self.m_choices)):
            it = it + "\r"
            vl = vl + "\r"
        s["items"] = it
        s["values"] = vl
        return None

## --------

INHERITPREFIX = "t_"

class Entity:
    def __init__(self):
        self.m_classname = None
        self.m_desc = ""
        self.m_keys = []
        self.m_inherit = []
        self.m_size = None
        self.m_color = None
        self.m_help = None

    def Type(self):
        raise "This is pure virtual"

    def SetClassname(self, classname):
        self.m_classname = classname

    def SetDesc(self, desc):
        self.m_desc = desc

    def SetSize(self, sizeargs):
        if (len(sizeargs) == 6):
            self.m_size = (float(sizeargs[0]), float(sizeargs[1]), float(sizeargs[2]),
                           float(sizeargs[3]), float(sizeargs[4]), float(sizeargs[5]))

    def SetHelp(self, helpstring):
        self.m_help = helpstring

    def InheritsFrom(self, inherit):
        self.m_inherit = self.m_inherit + [INHERITPREFIX + inherit]

    def AddKey(self, key):
        self.m_keys = self.m_keys + [key]

    def TypeForm(self):
        return ":form"

    def GetFolderStuff(self, s):
        return ""

    def GenerateFolder(self, indent):
        def SortedAppendItem(obj, subitem):
            i = 0
            for s in obj.subitems:
                if (s.shortname.lower() > subitem.shortname.lower()):
                    break
                i = i + 1
            obj.insertitem(i, subitem)

        s = quarkx.newobj(self.m_classname + self.Type())
        folder = indent
        p = s.name.find("_")
        if (p == -1):
            folder = indent.findname("other entities.qtxfolder")
            if (folder is None):
                folder = quarkx.newobj("other entities.qtxfolder")
                SortedAppendItem(indent, folder)
        else:
            folder = indent.findname(s.name[:p+1]+"* entities.qtxfolder")
            if (folder is None):
                folder = quarkx.newobj(s.name[:p+1]+"* entities.qtxfolder")
                SortedAppendItem(indent, folder)
        SortedAppendItem(folder, s)
        self.GetFolderStuff(s)
        s[";desc"] = self.m_desc
        founddefaults = 0
        for key in self.m_keys:
            k = key.GenerateFolder(s)

    def GenerateForm(self, indent):
        s = quarkx.newobj(self.m_classname + self.TypeForm())
        if (self.m_help is not None):
            s["help"] = self.m_help
        if (self.m_size is not None):
            s["bbox"] = self.m_size
        for key in self.m_keys:
            key.GenerateForm(s)
        # Place "<keyword>=!"-statements at the _end_ of ":form" definitions, because of a problem which Decker found but can't solve.
        for inh in self.m_inherit:
            s.specificadd(inh+"=!")
        indent.appenditem(s)

class BrushEntity(Entity):
    def __init__(self):
        Entity.__init__(self)

    def Type(self):
        return ":b"

    def GetFolderStuff(self, s):
        if (self.m_classname.lower() == "worldspawn"):
            return
        s["angle"] = "360"
        s[";incl"] = "defpoly"

class PointEntity(Entity):
    def __init__(self):
        Entity.__init__(self)

    def Type(self):
        return ":e"

    def GetFolderStuff(self, s):
        s["angle"] = "360"
        s["origin"] = "0 0 0"

class InheritEntity(Entity):
    def __init__(self):
        Entity.__init__(self)

    def SetClassname(self, classname):
        self.m_classname = INHERITPREFIX + classname

    def GenerateFolder(self, indent):
        return

    def Type(self):
        return

    def TypeForm(self):
        return ":incl"

## --------

theEntities = []
theEntity = None
theKey = None
currentclassname = None
currentsize = None
currentspawnflagbit = None
currentcomment = None

def CheckQUAKED(token):
    if not (token == "QUAKED"):
        raise "Expected 'QUAKED' token, but found: "+token

def CreateClass(token):
    global currentclassname, currentspawnflagbit, currentsize
    CloseClass("--CloseByCreateClass--")
    # Create entity
    currentclassname = token
    currentspawnflagbit = None
    currentsize = None
    currentcomment = None

def CloseClass(token):
    global theEntity, theEntities
    # Add to large list of entities
    if (theEntity is not None):
        theEntities = theEntities + [theEntity]

def MakePointEntity(token):
    global theEntity, currentclassname
    theEntity = PointEntity()
    theEntity.SetClassname(currentclassname)

def MakeBrushEntity(token):
    global theEntity, currentclassname
    theEntity = BrushEntity()
    theEntity.SetClassname(currentclassname)

def AppendSize(token):
    global currentsize
    if (currentsize is None):
        currentsize = [token]
    else:
        currentsize = currentsize + [token]

def SetSize(token):
    global currentsize, theEntity
    theEntity.SetSize(currentsize)
    currentsize = None

def AddSpawnflag(token):
    global currentspawnflagbit, theKey
    if (currentspawnflagbit == None):
        currentspawnflagbit = 1
        CloseSpawnflag("--CloseByAddSpawnflag--")
        theKey = KeyFlags()
        theKey.SetKeyname("spawnflags")
    # Only append spawnflag-tokens that are not "x" nor "-"
    if not (token == "x" or token == "-" or token == "?"):
        theKey.AddKeyFlag(str(currentspawnflagbit), token, 0)
    # Always double the spawnflagbit-value
    currentspawnflagbit = currentspawnflagbit * 2

def CloseSpawnflag(token):
    global theEntity, theKey
    if (theKey is not None):
        theEntity.AddKey(theKey)
        theKey = None

def AddComment(token):
    global currentcomment
    token = token.replace("\t", "    ")
    if (currentcomment is None):
        currentcomment = token
    else:
        currentcomment = currentcomment + "\r" + token

def DoneComment(token):
    global currentcomment, theEntity
    theEntity.SetHelp(currentcomment)
    currentcomment = None


## ------------

def readentirefile(file):
    f = open(file, "r")
    filecontents = ""
    while 1:
        line = f.readline()
        if not line:
            break
        line = line.strip()
        if line:
            filecontents = filecontents + line + "\n"
    f.close()
    return filecontents

TYPE_UNKNOWN    = 0
TYPE_NUMERIC    = 1
TYPE_STRING     = 2
TYPE_SYMBOL     = 3
TYPE_ANY        = 4
TYPE_ALFABETIC  = 5
TYPE_EOC        = 6 # End-Of-Comment == "*/"
TYPE_SPLITTER_AT        = 10 # @
TYPE_SPLITTER_COLON     = 11 # :
TYPE_SPLITTER_EQUAL     = 12 # =
TYPE_SPLITTER_SQUARE_B  = 13 # [
TYPE_SPLITTER_SQUARE_E  = 14 # ]
TYPE_SPLITTER_PRNTSHS_B = 15 # (
TYPE_SPLITTER_PRNTSHS_E = 16 # )
TYPE_SPLITTER_COMMA     = 17 # ,
TYPE_QUESTION_MARK      = 18 # ?
TYPE_ASTERISK           = 19 # *
TYPE_FORWARD_SLASH      = 20 # /
TYPE_NEWLINE            = 21 # \n

CHARS_NUMERIC           = "0123456789"
CHARS_NUMERIC_SYMBOLS   = "-."
CHARS_ALFABETIC         = "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

def getnexttoken(srcstring):
    # Remove heading spaces/tabs
    i = 0
    while (srcstring[i] in " \t"):
        i = i + 1

    if (srcstring[i] in "{"):
        while (not srcstring[i] in "}"):
            i = i + 1
        i = i + 1

    token_is = TYPE_UNKNOWN
    token = ""

    # Determine token-type
    if (srcstring[i] in CHARS_NUMERIC and not srcstring[i+1] in CHARS_ALFABETIC) \
    or (srcstring[i] in CHARS_NUMERIC_SYMBOLS and srcstring[i+1] in CHARS_NUMERIC):
        # Numeric
        token_is = TYPE_NUMERIC
        while (srcstring[i] in (CHARS_NUMERIC_SYMBOLS + CHARS_NUMERIC)):
            token = token + srcstring[i]
            i = i + 1
    elif (srcstring[i] in CHARS_ALFABETIC):
        # Alfabetic
        token_is = TYPE_ALFABETIC
        while (srcstring[i] in (CHARS_ALFABETIC + CHARS_NUMERIC + "*")):
            token = token + srcstring[i]
            i = i + 1
    else:
        # Character-symbol
        token = srcstring[i]
        i = i + 1
        if (token == "?"):
            token_is = TYPE_QUESTION_MARK
        elif (token == "*"):
            token_is = TYPE_ASTERISK
        elif (token == "/"):
            token_is = TYPE_FORWARD_SLASH
        elif (token == "("):
            token_is = TYPE_SPLITTER_PRNTSHS_B
        elif (token == ")"):
            token_is = TYPE_SPLITTER_PRNTSHS_E
        elif (token == "\n"):
            token_is = TYPE_NEWLINE
            if (srcstring[i] == "\r"):
                i = i + 1
        elif (token == "\r"):
            token_is = TYPE_NEWLINE
            if (srcstring[i] == "\n"):
                i = i + 1

    return token, token_is, srcstring[i:]

def getwholeline(srcstring):
    token = ""
    i = 0
    srclen = len(srcstring)
    while (i < srclen) and (not (srcstring[i] in "\n\r")):
        token = token + srcstring[i]
        i = i + 1
    i = i + 1
    if (token[-2:] == "*/"):
        token_is = TYPE_EOC
    else:
        token_is = TYPE_STRING
    return token, token_is, srcstring[i:]


statediagram =                                                                                  \
{                                                                                               \
# Current state            Token-type to go to ->    Next state             Function to call with token \
 'STATE_UNKNOWN'        :[(TYPE_FORWARD_SLASH      ,'STATE_COMMENTBEGIN'   ,None)               \
                         ,(TYPE_NEWLINE            ,'STATE_UNKNOWN'        ,None)             ] \
                                                                                                \
,'STATE_COMMENTBEGIN'   :[(TYPE_ASTERISK           ,'STATE_QUAKEEDBEGIN'   ,None)               \
                         ,(TYPE_FORWARD_SLASH      ,'STATE_EOL_COMMENT'    ,None)             ] \
                                                                                                \
,'STATE_EOL_COMMENT'    :[(TYPE_NEWLINE            ,'STATE_UNKNOWN'        ,None)               \
                         ,(TYPE_ANY                ,'STATE_EOL_COMMENT'    ,None)             ] \
                                                                                                \
,'STATE_QUAKEEDBEGIN'   :[(TYPE_ALFABETIC          ,'STATE_CLASSNAME'      ,CheckQUAKED)      ] \
                                                                                                \
,'STATE_CLASSNAME'      :[(TYPE_ALFABETIC          ,'STATE_CLASSCOLOR'     ,CreateClass)      ] \
                                                                                                \
,'STATE_CLASSCOLOR'     :[(TYPE_SPLITTER_PRNTSHS_B ,'STATE_CLASSCOLOR2'    ,None)             ] \
,'STATE_CLASSCOLOR2'    :[(TYPE_NUMERIC            ,'STATE_CLASSCOLOR3'    ,None)             ] \
,'STATE_CLASSCOLOR3'    :[(TYPE_NUMERIC            ,'STATE_CLASSCOLOR4'    ,None)             ] \
,'STATE_CLASSCOLOR4'    :[(TYPE_NUMERIC            ,'STATE_CLASSCOLOR5'    ,None)             ] \
,'STATE_CLASSCOLOR5'    :[(TYPE_SPLITTER_PRNTSHS_E ,'STATE_CLASSSIZE'      ,None)             ] \
                                                                                                \
,'STATE_CLASSSIZE'      :[(TYPE_SPLITTER_PRNTSHS_B ,'STATE_CLASSSIZE2'     ,MakePointEntity)    \
                         ,(TYPE_QUESTION_MARK      ,'STATE_SPAWNFLAGS'     ,MakeBrushEntity)  ] \
                                                                                                \
,'STATE_CLASSSIZE2'     :[(TYPE_NUMERIC            ,'STATE_CLASSSIZE3'     ,AppendSize)       ] \
,'STATE_CLASSSIZE3'     :[(TYPE_NUMERIC            ,'STATE_CLASSSIZE4'     ,AppendSize)       ] \
,'STATE_CLASSSIZE4'     :[(TYPE_NUMERIC            ,'STATE_CLASSSIZE5'     ,AppendSize)       ] \
,'STATE_CLASSSIZE5'     :[(TYPE_SPLITTER_PRNTSHS_E ,'STATE_CLASSSIZE6'     ,None)             ] \
,'STATE_CLASSSIZE6'     :[(TYPE_SPLITTER_PRNTSHS_B ,'STATE_CLASSSIZE7'     ,None)             ] \
,'STATE_CLASSSIZE7'     :[(TYPE_NUMERIC            ,'STATE_CLASSSIZE8'     ,AppendSize)       ] \
,'STATE_CLASSSIZE8'     :[(TYPE_NUMERIC            ,'STATE_CLASSSIZE9'     ,AppendSize)       ] \
,'STATE_CLASSSIZE9'     :[(TYPE_NUMERIC            ,'STATE_CLASSSIZE10'    ,AppendSize)       ] \
,'STATE_CLASSSIZE10'    :[(TYPE_SPLITTER_PRNTSHS_E ,'STATE_SPAWNFLAGS'     ,SetSize)          ] \
                                                                                                \
,'STATE_SPAWNFLAGS'     :[(TYPE_ALFABETIC          ,'STATE_SPAWNFLAGS'     ,AddSpawnflag)       \
                         ,(TYPE_UNKNOWN            ,'STATE_SPAWNFLAGS'     ,AddSpawnflag)       \
                         ,(TYPE_NEWLINE            ,'STATE_COMMENT'        ,CloseSpawnflag)   ] \
                                                                                                \
,'STATE_COMMENT'        :[(TYPE_EOC                ,'STATE_UNKNOWN'        ,DoneComment)        \
                         ,(TYPE_STRING             ,'STATE_COMMENT'        ,AddComment)       ] \
}

## --------

import quarkpy.qutils
import quarkx

def makeqrk(root, filename, gamename):
    quarkx.msgbox("Please note, this is not always 100% accurate and will duplicate\nexisting entities and possibly miss some out.\n\nYou may need to handedit the .qrk file. For help with this,\nfeel free to ask questions at the QuArK forum:\n\nhttp://groups.yahoo.com/group/quark/messages\n", quarkpy.qutils.MT_INFORMATION, quarkpy.qutils.MB_OK)
    global currentclassname
    srcstring = readentirefile(filename)
    state = 'STATE_UNKNOWN'
    while (len(srcstring) > 1):
        if (state == 'STATE_COMMENT'):
            # Special case, to keep the comment-lines intact, and not tokenized
            token, token_is, srcstring = getwholeline(srcstring)
        else:
            token, token_is, srcstring = getnexttoken(srcstring)
        # Figure out, if the token_is type is expected or not
        expectedtypes = []
        newstate = None
        typestates = statediagram[state]
        for type, nextstate, func in typestates:
            if (type == token_is or type == TYPE_ANY or (token_is == TYPE_QUESTION_MARK and func == AddSpawnflag)):
                # We found the correct token type, now remember what new state we're going into
                newstate = nextstate
                break
            expectedtypes = expectedtypes + [type]
        if newstate is None:
            print "Parse error: Got type", token_is, "but expected type(s);", expectedtypes
            print "Debug: Last classname was =", currentclassname
            print "Debug:", srcstring[:64]
            print "Debug - Associated function: ", func
            raise "Parse error!"
        if (func is not None):
            # This state have a function attached to it. Call it giving it the found token.
            func(token)
        # Change to new state
        state = newstate
    CloseClass("--EndByEOF--")
    indent = 2
    r_tbx = quarkx.newobj("Toolbox Folders.qtx")
    r_tbx["Toolbox"] = "New map items..."
    r_tbx.flags = r_tbx.flags | quarkpy.qutils.OF_TVSUBITEM
    root.appenditem(r_tbx)

    e_tbx = quarkx.newobj("Entities for "+gamename.split("\\")[-1]+".qtxfolder")
    e_tbx[";desc"] = "Created from "+filename.split("\\")[-1]
    r_tbx.appenditem(e_tbx)

    r_tbx["Root"] = e_tbx.name

    for ent in theEntities:
        ent.GenerateFolder(e_tbx)

    f_tbx = quarkx.newobj("Entity Forms.fctx")
    f_tbx.flags = f_tbx.flags | quarkpy.qutils.OF_TVSUBITEM
    root.appenditem(f_tbx)

    for ent in theEntities:
        ent.GenerateForm(f_tbx)
    root.refreshtv()

    quarkx.msgbox("The .DEF file have now almost been converted to QuArK format.\n\nWhat remains is to save it as a 'Structured text for hand-editing (*.qrk)' file.\n\nIf you encounter any problems using this 'Convert from QeRadiant .DEF file' utility, please post a mail in the QuArK-forum.", quarkpy.qutils.MT_INFORMATION, quarkpy.qutils.MB_OK)

import quarkpy.qentbase
quarkpy.qentbase.RegisterEntityConverter("QERadiant .def file", "QERadiant .def file", "*.def", makeqrk)

#
#$Log: entdef2qrk.py,v $
#Revision 1.6  2003/12/17 13:58:59  peter-b
#- Rewrote defines for setting Python version
#- Removed back-compatibility with Python 1.5
#- Removed reliance on external string library from Python scripts
#
#Revision 1.5  2003/06/21 14:46:22  nerdiii
#I modified the .def importer to work around three bugs:
#1. {...} entries are ignored now, caused parse errors before
#2. tokens starting with a digit such as '1st_left' are no longer treated as
#a number
#3. some entities have unused flags, that are named '?' in QERadiant. The
#importer used to interpret them as special characters.
#
#Revision 1.4  2002/04/17 12:32:20  decker_dk
#Minor problem, which caused it to not convert the MOHAA .DEF file correctly.
#
#Revision 1.3  2002/02/05 18:32:58  decker_dk
#Corrected a problem with debug() calls
#
#Revision 1.2  2001/12/02 09:57:45  decker_dk
#Removing 'os' from the import list, and some other minor fixes.
#
#Revision 1.1  2001/10/05 17:56:42  decker_dk
#Created QERadiant .DEF file to .QRK file converter.
#
