#!/usr/bin/python

#We need command line arguments - stored in sys.argv (including script name)
import sys
moduleSourceFilename = sys.argv[1]

# we are using regular expression matching
import re

# need to do some path manipulations
import os

# encode xml escape characters
def encodeXMLText(text):
    text = text.replace("&", "&amp;")
    text = text.replace("\"", "&quot;")
    text = text.replace("'", "&apos;")
    text = text.replace("<", "&lt;")
    text = text.replace(">", "&gt;")
    return text 

moduleRootPattern = re.compile( "(([\w_]+)Module)[\.][cC][pP]*" )
moduleRootMatches = moduleRootPattern.search( moduleSourceFilename )
moduleRoot = moduleRootMatches.group(1)
moduleRootBase = moduleRootMatches.group(2)

sourceFolderPattern = re.compile( "(.*[%/])[\w_]+[\.][cC][pP]*" )
sourceFolderMatches = sourceFolderPattern.match( moduleSourceFilename )
sourceFolder = sourceFolderMatches.group(1)

destination_dir = os.getenv( 'XSD_DEST_DIR', '/mbari/LRAUV/xsd' )

outfile = open( os.path.normpath( destination_dir + '/' + moduleRootBase + '.xsd'), 'w' )
outfile.write( '<?xml version="1.0" encoding="UTF-8"?>\n' )
outfile.write( '<schema targetNamespace="Tethys/' + moduleRootBase + '"\n' )
outfile.write( '    elementFormDefault="qualified"\n' )
outfile.write( '    xmlns="http://www.w3.org/2001/XMLSchema"\n' ) 
outfile.write( '    xmlns:' + moduleRootBase + '="Tethys/' + moduleRootBase + '"\n' ) 
outfile.write( '    xmlns:Tethys="Tethys">\n' )
outfile.write( '\n' )
outfile.write( '    <import namespace="Tethys" schemaLocation="http://okeanids.mbari.org/tethys/Xml/Tethys.xsd"/>\n' )

regComponentPattern = re.compile('[\s]+registerComponent[\(][\s]*new[\s]+([\w_]+)')
regMissionCompPattern = re.compile( '[\s]*registerBehaviorCreator[\(][\s]*([\w_]+)IF::' );
importInterfacePattern = re.compile( '#include[\s]+[\"]([\w/_]+IF.h)"' )
namespacePattern = re.compile( '[\s]*namespace[\s]+([\w_]+)' );
staticConstStrPattern = re.compile( '[\s]*static[\s]+const[\s]+Str[\s]+([\w_]+)[\(][\s]*[\"]([\w_]+)[\"]' )
staticConstConfigURIPattern = re.compile( '[\s]*static[\s]+const[\s]+ConfigURI[\s]+([\w_]+)[\(][\s]*[\w_]+[\s]*,[\s]*[\"]([\w_]+)[\"](?:[\s]*,[\s]*[\"]([^\"]+)[\"][\s]*)?([,\)])?' )
staticConstRestartConfigURIPattern = re.compile( '[\s]*static[\s]+const[\s]+RestartConfigURI[\s]+([\w_]+)[\(][\s]*[\w_]+[\s]*,[\s]*[\"]([\w_]+)[\"](?:[\s]*,[\s]*[\"]([^\"]+)[\"][\s]*)?([,\)])?' )
staticConstBlobURIPattern = re.compile( '[\s]*static[\s]+const[\s]+BlobURI[\s]+([\w_]+)[\(][\s]*[\w_]+[\s]*,[\s]*[\"]([\w_]+)[\"](?:[\s]*,[\s]*[\"]([^\"]+)[\"][\s]*)?([,\)])?' )
staticConstDataURIPattern = re.compile( '[\s]*static[\s]+const[\s]+DataURI[\s]+([\w_]+)[\(][\s]*[\w_]+[\s]*,[\s]*[\"]([\w_]+)[\"](?:[\s]*,[\s]*[\"]([^\"]+)[\"][\s]*)?([,\)])?' )
staticConstNameURIPattern = re.compile( '[\s]*static[\s]+const[\s]+NameURI[\s]+([\w_]+)[\(][\s]*[\"]([\w_]+)[\"](?:[\s]*,[\s]*[\"]([^\"]+)[\"][\s]*)?([,\)])?' )
staticConstOutputURIPattern = re.compile( '[\s]*static[\s]+const[\s]+OutputURI[\s]+([\w_]+)[\(][\s]*[\"]([\w_]+)[\"](?:[\s]*,[\s]*[\"]([^\"]+)[\"][\s]*)?([,\)])?' )
staticConstSettingURIPattern = re.compile( '[\s]*static[\s]+const[\s]+SettingURI[\s]+([\w_]+)[\(][\s]*[\"]([\w_]+)[\"](?:[\s]*,[\s]*[\"]([^\"]+)[\"][\s]*)?([,\)])?' )
staticConstStringSettingURIPattern = re.compile( '[\s]*static[\s]+const[\s]+StringSettingURI[\s]+([\w_]+)[\(][\s]*[\"]([\w_]+)[\"](?:[\s]*,[\s]*[\"]([^\"]+)[\"][\s]*)?([,\)])?' )
moreDescriptionPattern = re.compile( '[\s]*[\"]([^\"]+)[\"][\s]*([,\)])?');
missonComponentPattern = re.compile( '[\s]*:[\s]*Behavior[\s]*[\(][\s]*[^,]+,[^,]+,[\s]*([^,\s]+),[\s]*([^\)\s]+)' )
altMissonComponentPattern = re.compile( '[\s]*:[\s]*([\w]+)' )
otherComponentPattern = re.compile( '[\s]*:[\s]*[\w]+[\s]*[\(]');
newSettingReaderPattern = re.compile( '[\s]*[\w_]+[\s]*=[\s]*newSettingReader[\(][\s]*([\w:_]+)' )
newSettingVariableReaderPattern = re.compile( '[\s]*[\w_]+[\s]*=[\s]*newSettingReader[\(][\s]*([\w:_]+)[\s]*,[\s]*new[\s]+StrValue' )
newInputReaderPattern = re.compile( '[\s]*[\w_]+[\s]*=[\s]*Slate::NewInputReader[\(][\s]*([\w:_]+)' )
newOutputWriterPattern = re.compile( '[\s]*[\w_]+[\s]*=[\s]*Slate::NewOutputWriter[\(][\s]*([\w:_]+)' )
findArgWriterPattern = re.compile( '[\s]*[\w_]+[\s]*=[\s]*findArgWriter[\(][\s]*([\w:_]+)' )
newUniversalWriterPattern = re.compile( '[\s]*[\w_]+[\s]*=[\s]*(?:Slate::New|new)UniversalWriter[\(][\s]*([\w:_]+)' )
universalURIPattern = re.compile( 'UniversalURI::([\w_]+)' )
enumStartPattern = re.compile( '[\s]*[\w_]+?[\s]*enum' )
enumBehaviorPattern = re.compile( '[\s]*([\w_]+)' )
enumEndPattern = re.compile( '[\s]*[\}]' )
staticConstDoublePattern = re.compile( '[\s]*static[\s]+const[\s]+double[\s]+([\w_]+)[\s]*[\(][\s]*([^\s\)]+)[\s]*[\)]' )

def appendDescription(description, descDone, ifLines):
    if description:
        while not descDone:
            ifLine = ifLines.pop(0)
            moreDescriptionMatches = moreDescriptionPattern.match(ifLine)
            if moreDescriptionMatches:
                description = description + moreDescriptionMatches.group(1)
                descDone = moreDescriptionMatches.group(2)
            else:
                descDone = True
        outfile.write( '>\n' )
        outfile.write( '        <annotation>\n' )
        outfile.write( '            <documentation>%s\n' % encodeXMLText(description))
        outfile.write( '            </documentation>\n' )
        outfile.write( '        </annotation>\n' )
        outfile.write( '    </element>\n' )
    else:
        outfile.write( '/>\n' )


moduleFile = open( os.path.normpath(moduleSourceFilename) )
for moduleLine in moduleFile.readlines():

    componentClass = None
    interfaceName = None
    parentClass = None
    isBehaviorClass = False
    regComponentMatches = regComponentPattern.match( moduleLine )
    if regComponentMatches:
        componentClass = regComponentMatches.group( 1 )
        outfile.write( '\n' )
        outfile.write( '    <element name="' + componentClass + '"')
        outfile.write( ' substitutionGroup="Tethys:Sensor" type="Tethys:SensorType"/>\n' )
    regMissionCompMatches = regMissionCompPattern.match( moduleLine )
    if regMissionCompMatches and not parentClass:
        componentClass = regMissionCompMatches.group( 1 )
        isBehaviorClass = True;
    if not componentClass:
         continue
    else:
         interfaceName = componentClass

    behaviorType = None
    dict = {};
    classSourceFile = open( os.path.normpath(sourceFolder + componentClass + ".cpp") )
    sourceLines = classSourceFile.readlines()
    # Use while loop here because sourceLines may get prepended
    encounteredAnImportInterface = False
    while len(sourceLines) > 0:
        sourceLine = sourceLines.pop(0)
        importInterfaceMatches = importInterfacePattern.match( sourceLine )
        if importInterfaceMatches:
            encounteredAnImportInterface = True
            interfacePath = importInterfaceMatches.group(1)
            if re.search( '/', interfacePath ) or interfacePath == "VehicleIF.h":
                interfacePath = "../Source/" + interfacePath
            else:
                interfacePath = sourceFolder + interfacePath
            
            namespace = "";
            interfaceFile = open( os.path.normpath(interfacePath) )
            ifLines = interfaceFile.readlines()
            # Use while loop here because ifLines may get prepended
            while len(ifLines) > 0:
                ifLine = ifLines.pop(0)

                importInterfaceMatches = importInterfacePattern.match( ifLine )
                if importInterfaceMatches:
                    interfacePath = importInterfaceMatches.group(1)
                    if re.search( '/', interfacePath ):
                        interfacePath = "../Source/" + interfacePath
                    else:
                        interfacePath = sourceFolder + interfacePath
                    if os.path.exists( interfacePath ):
                        interfaceFile = open( os.path.normpath(interfacePath) )
                        childIfLines = interfaceFile.readlines()
                        childIfLines.extend(ifLines)
                        ifLines = childIfLines
                    
                namespaceMatches = namespacePattern.match( ifLine )
                if namespaceMatches:
                    namespace = namespaceMatches.group(1);
                    #print "namespace="+namespace
                    
                staticConstStrMatches = staticConstStrPattern.match( ifLine )
                if staticConstStrMatches:
                    id = staticConstStrMatches.group(1)
                    name = staticConstStrMatches.group(2)
                    dict[namespace + "::" + id ] = name
                    #print namespace + "::" + id + " = " + name
                    if id == "NAME" and namespace.startswith(componentClass):
                        interfaceName = name

                staticConstNameURIMatches = staticConstNameURIPattern.match( ifLine )
                if staticConstNameURIMatches and ( namespace.startswith(componentClass) or (parentClass and namespace.startswith(parentClass))):
                    id = staticConstNameURIMatches.group(1)
                    name = staticConstNameURIMatches.group(2)
                    description = staticConstNameURIMatches.group(3)
                    descDone = staticConstNameURIMatches.group(4)
                    while not descDone:
                        ifLine = ifLines.pop(0)
                        moreDescriptionMatches = moreDescriptionPattern.match(ifLine)
                        if moreDescriptionMatches:
                            description = description + moreDescriptionMatches.group(1)
                            descDone = moreDescriptionMatches.group(2)
                        else:
                            descDone = True
                    dict[namespace + "::" + id ] = [name,description]
                    if id == "NAME" and namespace.startswith(componentClass):
                        interfaceName = name

                staticConstConfigURIMatches = staticConstConfigURIPattern.match( ifLine )
                if staticConstConfigURIMatches and ( namespace.startswith(componentClass) or (parentClass and namespace.startswith(parentClass))):
                    id = staticConstConfigURIMatches.group(1)
                    name = staticConstConfigURIMatches.group(2)
                    outfile.write( '    <element name="' + interfaceName + '.' + name + '"')
                    outfile.write( ' substitutionGroup="Tethys:Uri" type="Tethys:ConfigUriType"' )
                    description = staticConstConfigURIMatches.group(3)
                    descDone = staticConstConfigURIMatches.group(4)
                    appendDescription(description, descDone, ifLines )


                staticConstRestartConfigURIMatches = staticConstRestartConfigURIPattern.match( ifLine )
                if staticConstRestartConfigURIMatches and ( namespace.startswith(componentClass) or (parentClass and namespace.startswith(parentClass))):
                    id = staticConstRestartConfigURIMatches.group(1)
                    name = staticConstRestartConfigURIMatches.group(2)
                    outfile.write( '    <element name="' + interfaceName + '.' + name + '"')
                    outfile.write( ' substitutionGroup="Tethys:Uri" type="Tethys:RestartConfigUriType"' )
                    description = staticConstRestartConfigURIMatches.group(3)
                    descDone = staticConstRestartConfigURIMatches.group(4)
                    appendDescription(description, descDone, ifLines )

                staticConstBlobURIMatches = staticConstBlobURIPattern.match( ifLine )
                if staticConstBlobURIMatches and ( namespace.startswith(componentClass) or (parentClass and namespace.startswith(parentClass))):
                    id = staticConstBlobURIMatches.group(1)
                    name = staticConstBlobURIMatches.group(2)
                    outfile.write( '    <element name="' + interfaceName + '.' + name + '"')
                    outfile.write( ' substitutionGroup="Tethys:Uri" type="Tethys:BlobUriType"' )
                    description = staticConstBlobURIMatches.group(3)
                    descDone = staticConstBlobURIMatches.group(4)
                    appendDescription(description, descDone, ifLines )

                staticConstDataURIMatches = staticConstDataURIPattern.match( ifLine )
                if staticConstDataURIMatches and ( namespace.startswith(componentClass) or (parentClass and namespace.startswith(parentClass))):
                    id = staticConstDataURIMatches.group(1)
                    name = staticConstDataURIMatches.group(2)
                    outfile.write( '    <element name="' + interfaceName + '.' + name + '"')
                    outfile.write( ' substitutionGroup="Tethys:Uri" type="Tethys:DataUriType"' )
                    description = staticConstDataURIMatches.group(3)
                    descDone = staticConstDataURIMatches.group(4)
                    appendDescription(description, descDone, ifLines )

                staticConstOutputURIMatches = staticConstOutputURIPattern.match( ifLine )
                if staticConstOutputURIMatches and ( namespace.startswith(componentClass) or (parentClass and namespace.startswith(parentClass))):
                    id = staticConstOutputURIMatches.group(1)
                    name = staticConstOutputURIMatches.group(2)
                    outfile.write( '    <element name="' + interfaceName + '.' + name + '"')
                    outfile.write( ' substitutionGroup="Tethys:OutputUri" type="Tethys:OutputUriType"')
                    description = staticConstOutputURIMatches.group(3)
                    descDone = staticConstOutputURIMatches.group(4)
                    appendDescription(description, descDone, ifLines )

                staticConstSettingURIMatches = staticConstSettingURIPattern.match( ifLine )
                if staticConstSettingURIMatches and ( namespace.startswith(componentClass) or (parentClass and namespace.startswith(parentClass))):
                    id = staticConstSettingURIMatches.group(1)
                    name = staticConstSettingURIMatches.group(2)
                    outfile.write( '    <element name="' + interfaceName + '.' + name + '"')
                    outfile.write( ' substitutionGroup="Tethys:Uri" type="Tethys:SettingUriType"')
                    description = staticConstSettingURIMatches.group(3)
                    descDone = staticConstSettingURIMatches.group(4)
                    appendDescription(description, descDone, ifLines )

                staticConstStringSettingURIMatches = staticConstStringSettingURIPattern.match( ifLine )
                if staticConstStringSettingURIMatches and ( namespace.startswith(componentClass) or (parentClass and namespace.startswith(parentClass))):
                    id = staticConstStringSettingURIMatches.group(1)
                    name = staticConstStringSettingURIMatches.group(2)
                    outfile.write( '    <element name="' + interfaceName + '.' + name + '"')
                    outfile.write( ' substitutionGroup="Tethys:UriStringSetting" type="Tethys:UriStringSettingType"' )
                    description = staticConstStringSettingURIMatches.group(3)
                    descDone = staticConstStringSettingURIMatches.group(4)
                    appendDescription(description, descDone, ifLines )

            interfaceFile.close() 
        
        key = None
        variableSetting = False
        
        if isBehaviorClass and not behaviorType:
            missionComponentMatches = missonComponentPattern.match( sourceLine )
            if missionComponentMatches:
                if missionComponentMatches.group(1) == "true":
                    if missionComponentMatches.group(2) == "true":
                        behaviorType = "SequenceType"
                    else:
                        behaviorType = "ParallelType"
            else:
                altMissionComponentMatches = altMissonComponentPattern.match( sourceLine )
                if altMissionComponentMatches:
                    parentClass = altMissionComponentMatches.group(1)
                    parentClassSourceFile = open( os.path.normpath(sourceFolder + parentClass + ".cpp") )
                    parentSourceLines = parentClassSourceFile.readlines()
                    parentSourceLines.extend( sourceLines )
                    sourceLines = parentSourceLines

            if behaviorType:
                outfile.write( '\n' )
                outfile.write( '    <element name="' + componentClass + '"')
                outfile.write(' type="Tethys:BehaviorType" substitutionGroup="Tethys:Behavior"') 
                desc = dict.get(componentClass + "IF::NAME", None)
                if desc and isinstance(desc,list) and desc[1]:
                        outfile.write( '>\n' )
                        outfile.write( '        <annotation>\n' )
                        outfile.write( '            <documentation>%s\n' % encodeXMLText(desc[1]))
                        outfile.write( '            </documentation>\n' )
                        outfile.write( '        </annotation>\n' )
                        outfile.write( '    </element>\n' )
                else:
                    outfile.write('/>\n' )

        newInputReaderMatches = newInputReaderPattern.match( sourceLine )
        if newInputReaderMatches:
            key = newInputReaderMatches.group(1)

        newSettingReaderMatches = newSettingReaderPattern.match( sourceLine )
        if newSettingReaderMatches:
            key = newSettingReaderMatches.group(1)
            variableSetting = None != newSettingVariableReaderPattern.match( sourceLine )

        newOutputWriterMatches = newOutputWriterPattern.match( sourceLine )
        if newOutputWriterMatches:
            key = newOutputWriterMatches.group(1)

        findArgWriterMatches = findArgWriterPattern.match( sourceLine )
        if findArgWriterMatches:
            key = findArgWriterMatches.group(1)

        newUniversalWriterMatches = newUniversalWriterPattern.match( sourceLine )
        if newUniversalWriterMatches:
            key = newUniversalWriterMatches.group(1)

        if not key: 
            continue
            
        value = dict.get(key, None)

        if not value:
            universalURIMatches = universalURIPattern.match( key )
            if universalURIMatches:
                value = universalURIMatches.group(1).lower()
        
        if value:
            if variableSetting:
                outfile.write( '    <element name="' + componentClass + '.' + value + '"')
                outfile.write( ' substitutionGroup="Tethys:UriStringSetting" type="Tethys:UriStringSettingType"/>\n' )
            else:
                outfile.write( '    <element name="' + componentClass + '.' + value + '"')
                outfile.write( ' substitutionGroup="Tethys:Uri" type="Tethys:UriType"/>\n' )

    classSourceFile.close()
    
    if not encounteredAnImportInterface:
        print >> sys.stderr, "Error: No interface file encountered in file " + sourceFolder + componentClass + ".cpp"

    enumMode = False
    enumValue = 0;
    classInterfaceFile = open( os.path.normpath(sourceFolder + componentClass + "IF.h") )
    for ifLine in classInterfaceFile.readlines():        
        if enumMode:
            enumBehaviorMatch = enumBehaviorPattern.match( ifLine )
            if enumBehaviorMatch:
                enumBehavior = enumBehaviorMatch.group(1)
                outfile.write( '    <!--' + componentClass + '.' + enumBehavior + '=' + str(enumValue) + ' -->\n')
                enumValue = enumValue + 1
            else:
                if enumEndPattern.match( ifLine ):
                    enumMode = True
                    enumValue = 0
        elif enumStartPattern.match( ifLine ):
                enumMode = True
        else:
            staticConstDoubleMatches = staticConstDoublePattern.match( ifLine )
            if staticConstDoubleMatches:
                varName = staticConstDoubleMatches.group(1)
                value = staticConstDoubleMatches.group(2)
                outfile.write( '    <!--' + componentClass + '.' + varName + '= ' + value + ' -->\n')
        
    classInterfaceFile.close()
        
moduleFile.close()


outfile.write( '\n' )
outfile.write( '</schema>' )
outfile.close()    
