"""   QuArK  -  Quake Army Knife

Python macros available for direct call by QuArK
"""

#
#$Header: /cvsroot/quark/runtime/plugins/entfgd2qrk.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

    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 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):
        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")
                indent.appenditem(folder)
        else:
            folder = indent.findname(s.name[:p+1]+"* entities.qtxfolder")
            if (folder is None):
                folder = quarkx.newobj(s.name[:p+1]+"* entities.qtxfolder")
                indent.appenditem(folder)
        folder.appenditem(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_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
currentkeyname = None
currentinherit = None
currentinheritargs = None
currentkeyflag = None
currentkeychoice = None

def CreateClass(token):
    global theEntity, theEntities
    CloseClass("--CloseByCreateClass--")
    # Create entity-type
    if (token.lower() == "solidclass"):
        theEntity = BrushEntity()
    elif (token.lower() == "pointclass"):
        theEntity = PointEntity()
    elif (token.lower() == "baseclass"):
        theEntity = InheritEntity()
    else:
        raise "Unknown @-token:", token

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

def BeginInherit(token):
    global currentinherit, currentinheritargs
    EndInherit("--EndByBeginInherit--")
    currentinherit = token.lower()
    currentinheritargs = []

def AddInherit(token):
    global currentinherit, currentinheritargs
    currentinheritargs = currentinheritargs + [token]

def EndInherit(token):
    global currentinherit, currentinheritargs, theEntity
    if (currentinherit is None):
        return
    if (currentinherit == "base"):
        for arg in currentinheritargs:
            theEntity.InheritsFrom(arg)
    elif (currentinherit == "size"):
        theEntity.SetSize(currentinheritargs)
    else:
        pass
    currentinherit = None

def BeginClassname(token):
    global currentclassname, theEntity
    EndClassname("--EndByBeginClassname--")
    currentclassname = token
    theEntity.SetClassname(token)

def AddClassnameDesc(token):
    global theEntity
    theEntity.SetDesc(token)

def EndClassname(token):
    global currentclassname
    if (currentclassname is None):
        return
    EndKey("--EndByEndClassname--")
    currentclassname = None

def BeginKey(token):
    global currentkeyname
    EndKey("--EndByBeginKey--")
    currentkeyname = token

def AddKeyType(token):
    global currentkeyname, theKey
    # Determine what type this key is, so the correct object can be created
    token = token.lower()
    if (token == "integer"):
        theKey = KeyNumeric()
    elif (token == "string" \
       or token == "target_source" \
       or token == "target_destination" \
       or token == "color1" \
       or token == "color255" \
       or token == "studio" \
       or token == "sound" \
       or token == "sprite" \
       or token == "decal"):
        theKey = KeyString()
    elif (token == "flags"):
        theKey = KeyFlags()
    elif (token == "choices"):
        theKey = KeyChoices()
    else:
        raise "Unknown KeyType-token:", token
    theKey.SetKeyname(currentkeyname)

def AddKeyDesc(token):
    global theKey
    theKey.SetDesc(token)

def AddKeyDefa(token):
    global theKey
    theKey.SetDefaultValue(token)

def AddKeyFlagNum(token):
    global currentkeyflag
    EndKeyFlag("--EndByAddKeyFlagNum--")
    currentkeyflag = token

def AddKeyFlagDesc(token):
    global currentkeyflag
    value = currentkeyflag
    currentkeyflag = (value, token)

def AddKeyFlagDefa(token):
    global currentkeyflag
    value, desc = currentkeyflag
    currentkeyflag = (value, desc, token)

def EndKeyFlag(token):
    global currentkeyflag, theKey
    if (currentkeyflag is None):
        return
    value, desc, selected = currentkeyflag
    theKey.AddKeyFlag(value, desc, selected)
    currentkeyflag = None

def AddKeyChoiceNum(token):
    global currentkeychoice
    EndKeyChoice("--EndByAddKeyChoiceNum--")
    currentkeychoice = token

def AddKeyChoiceDesc(token):
    global currentkeychoice
    value = currentkeychoice
    currentkeychoice = (value, token)

def EndKeyChoice(token):
    global currentkeychoice, theKey
    if (currentkeychoice is None):
        return
    value, desc = currentkeychoice
    theKey.AddKeyChoice(value, desc)
    currentkeychoice = None

def EndKey(token):
    global currentkeyname, theEntity, theKey
    if (currentkeyname is None):
        return
    if (theKey is None or theEntity is None):
        raise "Failure in EndKey()"
    theEntity.AddKey(theKey)
    currentkeyname = None

def EndKeyFlags(token):
    EndKeyFlag("--EndByEndKeyFlags--")
    EndKey(token)

def EndKeyChoices(token):
    EndKeyChoice("--EndByEndKeyChoices--")
    EndKey(token)

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

def readentirefile(file):
    f = open(file, "r")
    filecontents = ""
    while 1:
        line = f.readline()
        if not line:
            break
        line = line.strip()
        line = line.split("//")[0] # Remove end-of-line comments
        if line:
            filecontents = filecontents + line + "\n"
    f.close()
    return filecontents

TYPE_UNKNOWN    = 0
TYPE_NUMERIC    = 1
TYPE_STRING     = 2
TYPE_SYMBOL     = 3
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    # ','

CHARS_NUMERIC  = "-0123456789."
CHARS_STRING   = "\""
CHARS_SPLITTER = "@:=[](),"

def getnexttoken(srcstring):
    def nextnonwhitespace(srcstring):
        i = 0
        while (srcstring[i] in " \t\n\r"):
            i = i + 1
        return srcstring[i:]

    def gettoken(srcstring, delims=None):
        token = ""
        i = 0
        if delims is None:
            delims = " \t\n\r" + CHARS_SPLITTER
        if not (srcstring[i] in delims):
            token = token + srcstring[i]
            i = i + 1
        while not (srcstring[i] in delims):
            token = token + srcstring[i]
            i = i + 1
        return token, srcstring[i:]

    token_is = TYPE_UNKNOWN
    srcstring = nextnonwhitespace(srcstring)
    if (not srcstring):
        return None, TYPE_UNKNOWN, srcstring

    if (srcstring[0] in CHARS_NUMERIC):
        token_is = TYPE_NUMERIC
        token, srcstring = gettoken(srcstring)
    elif (srcstring[0] in CHARS_STRING):
        token_is = TYPE_STRING
        token, srcstring = gettoken(srcstring[1:], CHARS_STRING)
        srcstring = srcstring[1:] # Jump over the last " character
    elif (srcstring[0] in CHARS_SPLITTER):
        token = srcstring[0]
        srcstring = srcstring[1:] # Jump over the splitter character
        if (token == "@"):
            token_is = TYPE_SPLITTER_AT
        elif (token == ":"):
            token_is = TYPE_SPLITTER_COLON
        elif (token == "="):
            token_is = TYPE_SPLITTER_EQUAL
        elif (token == "["):
            token_is = TYPE_SPLITTER_SQUARE_B
        elif (token == "]"):
            token_is = TYPE_SPLITTER_SQUARE_E
        elif (token == "("):
            token_is = TYPE_SPLITTER_PRNTSHS_B
        elif (token == ")"):
            token_is = TYPE_SPLITTER_PRNTSHS_E
        elif (token == ","):
            token_is = TYPE_SPLITTER_COMMA
    else:
        token_is = TYPE_SYMBOL
        token, srcstring = gettoken(srcstring)
    return token, token_is, srcstring


statediagram =                                                                                  \
{                                                                                               \
# Current state            Token-type to go to ->    Next state             Function to call with token \
 'STATE_UNKNOWN'        :[(TYPE_SPLITTER_AT        ,'STATE_CLASSBEGIN'     ,None)             ] \
                                                                                                \
,'STATE_CLASSBEGIN'     :[(TYPE_SYMBOL             ,'STATE_CLASSINHERIT'   ,CreateClass)      ] \
,'STATE_CLASSINHERIT'   :[(TYPE_SYMBOL             ,'STATE_INHERITBEGIN'   ,BeginInherit)       \
                         ,(TYPE_SPLITTER_EQUAL     ,'STATE_CLASSNAME'      ,None)             ] \
                                                                                                \
,'STATE_INHERITBEGIN'   :[(TYPE_SPLITTER_PRNTSHS_B ,'STATE_INHERITMEDIUM'  ,None)             ] \
,'STATE_INHERITMEDIUM'  :[(TYPE_SYMBOL             ,'STATE_INHERITMEDIUM'  ,AddInherit)         \
                         ,(TYPE_NUMERIC            ,'STATE_INHERITMEDIUM'  ,AddInherit)         \
                         ,(TYPE_STRING             ,'STATE_INHERITMEDIUM'  ,AddInherit)         \
                         ,(TYPE_SPLITTER_COMMA     ,'STATE_INHERITMEDIUM'  ,None)               \
                         ,(TYPE_SPLITTER_PRNTSHS_E ,'STATE_CLASSINHERIT'   ,EndInherit)       ] \
                                                                                                \
,'STATE_CLASSNAME'      :[(TYPE_SYMBOL             ,'STATE_CLASSNAME2'     ,BeginClassname)   ] \
,'STATE_CLASSNAME2'     :[(TYPE_SPLITTER_COLON     ,'STATE_CLASSNAME3'     ,None)               \
                         ,(TYPE_SPLITTER_SQUARE_B  ,'STATE_KEYSBEGIN'      ,None)             ] \
,'STATE_CLASSNAME3'     :[(TYPE_STRING             ,'STATE_CLASSNAME4'     ,AddClassnameDesc) ] \
,'STATE_CLASSNAME4'     :[(TYPE_SPLITTER_SQUARE_B  ,'STATE_KEYSBEGIN'      ,None)             ] \
                                                                                                \
,'STATE_KEYSBEGIN'      :[(TYPE_SPLITTER_SQUARE_E  ,'STATE_UNKNOWN'        ,EndClassname)       \
                         ,(TYPE_SYMBOL             ,'STATE_KEYBEGIN'       ,BeginKey)         ] \
                                                                                                \
,'STATE_KEYBEGIN'       :[(TYPE_SPLITTER_PRNTSHS_B ,'STATE_KEYTYPE'        ,None)             ] \
,'STATE_KEYTYPE'        :[(TYPE_SYMBOL             ,'STATE_KEYTYPE2'       ,AddKeyType)       ] \
,'STATE_KEYTYPE2'       :[(TYPE_SPLITTER_PRNTSHS_E ,'STATE_KEYTYPE3'       ,None)             ] \
,'STATE_KEYTYPE3'       :[(TYPE_SPLITTER_EQUAL     ,'STATE_VALUEFLAGS'     ,None)               \
                         ,(TYPE_SPLITTER_COLON     ,'STATE_VALUE'          ,None)               \
                         ,(TYPE_SYMBOL             ,'STATE_KEYBEGIN'       ,BeginKey)           \
                         ,(TYPE_SPLITTER_SQUARE_E  ,'STATE_UNKNOWN'        ,EndClassname)     ] \
                                                                                                \
,'STATE_VALUEFLAGS'     :[(TYPE_SPLITTER_SQUARE_B  ,'STATE_VALUEFLAGS2'    ,None)             ] \
,'STATE_VALUEFLAGS2'    :[(TYPE_SPLITTER_SQUARE_E  ,'STATE_KEYSBEGIN'      ,EndKeyFlags)        \
                         ,(TYPE_NUMERIC            ,'STATE_VALUEFLAG'      ,AddKeyFlagNum)    ] \
,'STATE_VALUEFLAG'      :[(TYPE_SPLITTER_COLON     ,'STATE_VALUEFLAG2'     ,None)             ] \
,'STATE_VALUEFLAG2'     :[(TYPE_STRING             ,'STATE_VALUEFLAG3'     ,AddKeyFlagDesc)   ] \
,'STATE_VALUEFLAG3'     :[(TYPE_SPLITTER_COLON     ,'STATE_VALUEFLAG4'     ,None)             ] \
,'STATE_VALUEFLAG4'     :[(TYPE_NUMERIC            ,'STATE_VALUEFLAGS2'    ,AddKeyFlagDefa)   ] \
                                                                                                \
,'STATE_VALUE'          :[(TYPE_STRING             ,'STATE_VALUE2'         ,AddKeyDesc)       ] \
,'STATE_VALUE2'         :[(TYPE_SYMBOL             ,'STATE_KEYBEGIN'       ,BeginKey)           \
                         ,(TYPE_SPLITTER_SQUARE_E  ,'STATE_UNKNOWN'        ,EndClassname)       \
                         ,(TYPE_SPLITTER_COLON     ,'STATE_VALUE3'         ,None)               \
                         ,(TYPE_SPLITTER_EQUAL     ,'STATE_CHOICES'        ,None)             ] \
,'STATE_VALUE3'         :[(TYPE_NUMERIC            ,'STATE_VALUE4'         ,AddKeyDefa)         \
                         ,(TYPE_STRING             ,'STATE_VALUE4'         ,AddKeyDefa)       ] \
,'STATE_VALUE4'         :[(TYPE_SYMBOL             ,'STATE_KEYBEGIN'       ,BeginKey)           \
                         ,(TYPE_SPLITTER_SQUARE_E  ,'STATE_UNKNOWN'        ,EndClassname)       \
                         ,(TYPE_SPLITTER_EQUAL     ,'STATE_CHOICES'        ,None)             ] \
                                                                                                \
,'STATE_CHOICES'        :[(TYPE_SPLITTER_SQUARE_B  ,'STATE_CHOICES2'       ,None)             ] \
,'STATE_CHOICES2'       :[(TYPE_SPLITTER_SQUARE_E  ,'STATE_KEYSBEGIN'      ,EndKeyChoices)      \
                         ,(TYPE_NUMERIC            ,'STATE_CHOICES3'       ,AddKeyChoiceNum)  ] \
,'STATE_CHOICES3'       :[(TYPE_SPLITTER_COLON     ,'STATE_CHOICES4'       ,None)             ] \
,'STATE_CHOICES4'       :[(TYPE_STRING             ,'STATE_CHOICES2'       ,AddKeyChoiceDesc) ] \
}

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):
        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):
                # 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]
            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+".qtxfolder")
    e_tbx[";desc"] = "Created from "+filename
    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 .FGD 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, then using a text-editor do a Search-Replace of   \"!\"   with   !\nE.g. replacing a double-quoted exclamation mark, with just a exclamation mark.\n\nIf you encounter any problems using this 'Convert from Worldcraft .FGD file' utility, please post a mail in the QuArK-forum.", quarkpy.qutils.MT_INFORMATION, quarkpy.qutils.MB_OK)

import quarkpy.qentbase
quarkpy.qentbase.RegisterEntityConverter("Worldcraft .fgd file", "Worldcraft .fgd file", "*.fgd", makeqrk)

#
#$Log: entfgd2qrk.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  2002/02/05 18:32:58  decker_dk
#Corrected a problem with debug() calls
#
#Revision 1.4  2001/12/02 09:57:45  decker_dk
#Removing 'os' from the import list, and some other minor fixes.
#
#Revision 1.3  2001/10/21 08:34:42  decker_dk
#Print out debug-information, in case of parse-error.
#
#Revision 1.2  2001/08/13 17:45:30  decker_dk
#Problem with "<keyword>=!" placements in ":form" definitions. Hard to solve correctly, as its deep within QuArK's Delphi-code!
#
#Revision 1.1  2001/06/13 23:02:50  aiv
#Moved 'Convert From' stuff to python code (plugin type)
#
#Revision 1.4  2001/06/11 17:42:38  decker_dk
#Fixed the BBOX problem, where it would think the value were a string (double-quotes), and not 6 numbers (single-quotes).
#Also added a messagebox which states what should be manually done afterwards.
#
#Revision 1.3  2001/04/14 19:30:58  decker_dk
#Handle 'color1' FGD-types too.
#
#
