
"""
*** RefCheck

Flow
  Read files
    On @defManual manual refManual eg @defManual LM rlearning
      Associate manual name 'manual' with reference @'refManual'{ ... }
      Add 'refManual' to list of keywords
    On @setManual manual set current manual name to 'manual'
    On @include open and process new file
    On @node add node to Nodes list with current manual name and current file name
    On @ref add contents to References list with refManual, current manual name and current file name
    On one of refManual keywords add contents to References list with ditto
  Match refs and nodes
    Process References list:
      Check reference is contained in Nodes list with refManual in Nodes = refManual in References
    Print results
    
Known issues
  Node names containing commas are only checked up to the comma
  Spurious warnings "Xref should be internal" for files in /included

"""


##################################################
class CrossRefs:
  """ Holds References and Nodes """
  def __init__(self):
    self.Manuals = {}
    self.Nodes = {}
    self.nodeNames = {}  # used to check for duplicates only
    self.Refs = []
    self.nodeCount = 0

  def addManual(self, manualName, refManual):
    self.Manuals[refManual] = manualName

  def getRefManuals(self):
    return self.Manuals.keys()

  def getManualName(self, refManual):
    return self.Manuals[refManual]

  def addNode(self, nodeName, manualName, fileName):
    global returnCode
#    print "Node: ", nodeName, " in ", manualName, " found in ", fileName
    if "\\" in nodeName:
      returnCode = 1
      print "nodeName: ", nodeName, " in ", fileName, " contains backslash"
    if manualName+"/"+nodeName in self.Nodes.keys():
      print "Error: Duplicate nodename ",nodeName, " in ", fileName, " and ", self.Nodes[manualName+"/"+nodeName][1]
      returnCode=1
    self.Nodes[manualName + "/" + nodeName] = [manualName, fileName]
    self.nodeNames[nodeName] = fileName

  def addRef(self, toManualName, toHeading, inFileName):
    global returnCode
    if "\\" in toHeading:
      returnCode = 1
      print "ref to: ", toHeading, " in ", inFileName, " contains backslash"
#    if inFileName == "notation/vocal.itely":
#      print "Ref to ", toManualName, "/",toHeading, " found in ", inFileName
    self.Refs.append([toManualName + "/" + toHeading, inFileName])

  def check(self):
    noErrors = True
    for [refHeading, refFileName] in self.Refs:
      try:
        targetFileName = self.Nodes[refHeading]
#        if refFileName == "notation/vocal.itely":
#          print "ref to: ", refHeading, " in ", refFileName, " found in ", targetFileName
      except KeyError:
        noErrors = False
        print "ref to: ", refHeading, " in ", refFileName, " not found"
    if noErrors:
      print " All references satisfied"
    else:
      returnCode=1

##################################################
class File:
  """ Process an included file """

  # Class variables
  CurrentManualName = ""
  DefaultPath = ""
  Excludes = []
  Paths = {}

  # Methods
  def __init__(self, fileName):
    self.fileName = fileName
    try:
      self.fullFileName = File.Paths[fileName] + fileName
    except KeyError:
      self.fullFileName = File.DefaultPath + fileName

  def read(self, crossRefs):
    """ Process File """

    skip = False
    try:
      myfile = open(self.fullFileName, 'r')
    except IOError:
      print "File ", self.fullFileName, " referenced in ", File.CurrentManualName, " but not found"
      return
    remainderLine = ""
    lineNo = 0
    for line in myfile:
      lineNo += 1
      words = line.split()
      if len(words) > 0:
        if words[0] == "@ignore" or words[0] == "@macro":
          skip = True
        if skip and len(words) > 1:
          if words[0] == "@end" and (words[1].find("ignore") >= 0 or words[1].find("macro") >= 0):
            skip = False

        if not skip and words[0] != "@c":
          if words[0].find("@defManual") >= 0:
            # Manual definition found - extract manual name and refManual string
            manualName = words[1]
            refManual = words[2]
            crossRefs.addManual(manualName, refManual)
#            print manualName, refManual

          elif words[0].find("@defaultPath") >=0:
            File.DefaultPath = words[1]

          elif words[0].find("@path") >=0:
            File.Paths[words[1]] = words[2]

          elif words[0].find("@setManual") >= 0:
            File.CurrentManualName = words[1]
#            print " Checking ", File.CurrentManualName

          elif words[0].find("@exclude") >=0:
            File.Excludes.append(words[1])

          elif words[0].find("@include") >= 0:
            if words[1] not in File.Excludes:
              currentFileName = words[1]
#              print "  File: ", currentFileName
              currentFile = File(currentFileName)
              currentFile.read(crossRefs)

          elif words[0] == "@node":
            nodeName = line[6:-1]
            crossRefs.addNode(nodeName, File.CurrentManualName, self.fileName)

          # Find references

          twoLines = remainderLine + ' ' + line.strip()
          manualRefStrings = crossRefs.getRefManuals()
          refFound = False
          for manualRefString in manualRefStrings:
            toManualName = crossRefs.getManualName(manualRefString)
            actualToManualName = toManualName
            if toManualName == "this":
              toManualName = File.CurrentManualName
            refString = "@" + manualRefString + "{"
            refStart = twoLines.find(refString)
            if refStart >= 0:
              refFound = True
              if actualToManualName == File.CurrentManualName:
                print "Warning: should xref be internal around line ", lineNo, " in ", self.fileName, "?"
              twoLines = twoLines[refStart:]
              refNodeStart = twoLines.find("{") + 1
              # TODO Need to check here for nested {}
              refNodeEnd = twoLines.find("}")
              refNodeEndComma = twoLines.find(",")
              if refNodeEndComma > 0:
                refNodeEnd = min(refNodeEnd, refNodeEndComma)
              if refNodeEnd >= 0:
                crossRefs.addRef(toManualName, twoLines[refNodeStart:refNodeEnd], self.fileName)
                remainderLine=twoLines[refNodeEnd+1:]
            if refFound:
              refFound = False
              break
          if not refFound:
              remainderLine = ""

    myfile.close()
    return

topFile = File("scripts/auxiliar/ref_check.tely") # TODO get from input params
print "RefCheck ver 0.1"
returnCode=0
crossRefs = CrossRefs()
topFile.read(crossRefs)
crossRefs.check()
if returnCode > 0:
  print "Errors found: status code: ",returnCode
