# Copyright (c) 2000-2003 The Regents of the University of California.
# Copyright (c) 2005 Stanford University. 
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# - Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the
#   distribution.
# - Neither the name of the copyright holders nor the names of
#   its contributors may be used to endorse or promote products derived
#   from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
#
# @author Kamin Whitehouse 
# @author Philip Levis

from tinyos.tossim.TossimNescDecls import *

class NescVariables(object) :
  def __init__( self, applicationName="Unknown App", xmlFilename=None ) :
    self.applicationName = applicationName
    self._varNames = []
    self._vars = []

    dom = minidom.parse(xmlFilename)
    variableList = [node for node in dom.getElementsByTagName("variables")]
    while len(variableList) > 0:
      variables = variableList.pop(0).getElementsByTagName("variable")
      while len(variables) > 0:
        cVariable = 0
        isArray = 0

        variable = variables.pop(0)
        name = variable.getAttribute("name")
        component = variable.getElementsByTagName("component-ref")

        if (len(component) > 0):
          name = component[0].getAttribute("qname") + "." + name
        else:  # It's in a C file
          cVariable = 1
          fileName = variable.getAttribute("loc")
          index = fileName.rfind("/") # First check for a UNIX path
          if (index == -1):
            index = fileName.rfind("\\") # Then a windows path
            if (index == -1):
              index = fileName.rfind(":") # Then if it's in the local dir

          if (index != -1):
            fileName = fileName[index+1:]
            index = fileName.rfind(".")
            if (index != -1):
              fileName = fileName[0:index]
              name = fileName + "." + name

        varType = "unknown"
        varTypes = variable.getElementsByTagName("type-float")
        if (len(varTypes) == 0):
          varTypes = variable.getElementsByTagName("type-int")

        if (len(variable.getElementsByTagName("type-array")) > 0):
          isArray = 1
          
        if (len(varTypes) > 0):
          varTypeEntry = varTypes[0]
          varType = varTypeEntry.getAttribute("cname")

        if (cVariable == 0):
          self._varNames.append(str(name))
          self._vars.append(str(name))
          if (isArray):
            self._vars.append("array")
          else:
            self._vars.append("simple")
          self._vars.append(str(varType))
             
  def __str__(self) :
    """ Print all available variables."""
    string = "\n"
    name = 1
    for val in self._varNames :
      if (name):
        string += "\t" + val
        name = 0
      else:
        string += ": " + val + "\n"
        name = 1
        
    return string
     
  def variables(self):
    return self._vars
  

class NescTypes( object ) :
  """A class that holds all types defined in a specific nesc application.

  usage:
  myTypes = NescTypes('/path/to/nescDecls.xml')
  print myTypes
  var = myTypes.typeName
  """
  def __init__( self, applicationName="Unknown App", xmlFilename = None) :
    self.applicationName = applicationName
    self._typeNames = []
    self._types = {}
    #figure out the sizes of all the basic types for this platform (by scanning the xml file)
    platformTypes = {}
    typeRE = re.compile('cname=\"([\w\s]+?)\" size=\"I:(\d+?)\"')
    infile = open(xmlFilename, 'r')
    for line in infile :
      match = typeRE.search(line)
      if match != None:
        platformTypes[match.groups()[0]] = int(match.groups()[1])      
    #define all the basic types
    self.addType(
      nescType("uint8_t", "unsigned char", "int", "type-int", "B",1,0))
    self.addType(
      nescType("int8_t", "signed char", "int", "type-int", "b", 1, 0))
    if (platformTypes.has_key("int") and platformTypes["int"] == 4) or \
       (platformTypes.has_key("unsigned int") and platformTypes["unsigned int"] == 4) :
      self.addType(
        nescType("uint16_t", "unsigned short", "int", "type-int", "H", 2, 0))
      self.addType(
        nescType("int16_t", "short", "int", "type-int", "h", 2, 0))
      self.addType(
        nescType("uint32_t", "unsigned int", "int", "type-int", "L",4,0))
      self.addType(
        nescType("int32_t", "int", "int", "type-int", "L", 4, 0))
      self.addType(
        nescType("unsigned long", "unsigned long", "int", "type-int", "L",4,0))
      self.addType(
        nescType("long", "long", "int", "type-int", "l", 4, 0))
    else : #int is 2 bytes long (the default)
      self.addType(
        nescType("unsigned short", "unsigned short", "int", "type-int", "H", 2, 0))
      self.addType(
        nescType("short", "short", "int", "type-int", "h", 2, 0))
      self.addType(
        nescType("uint16_t", "unsigned int", "int", "type-int", "H", 2, 0))
      self.addType(
        nescType("int16_t", "int", "int", "type-int", "h", 2, 0))
      self.addType(
        nescType("uint32_t", "unsigned long", "int", "type-int", "L",4,0))
      self.addType(
        nescType("int32_t", "long", "int", "type-int", "l", 4, 0))
    self.addType(
      nescType("int64_t", "long long", "long", "type-int", "q", 8, 0))
    self.addType(
      nescType("uint64_t", "unsigned long long", "long", "type-int", "Q", 8, 0))
    self.addType(
      nescType("float", "float", "float", "type-float", "f", 4, 0))
    if platformTypes.has_key("double") and platformTypes["double"] == 8 :
      self.addType(
        nescType("double", "double", "float", "type-float", "d", 8, 0))
    else : #double is 4 bytes (the default)
      self.addType(
        nescType("double", "double", "float", "type-float", "f", 4, 0))
    self.addType(
      nescType("char", "char", "str", "type-int", "c", 1, '\x00'))
    self.addType(
      nescType("void", "void", "", "type-void", "", 0, ''))

    #some arrays for error reporting:
    self.unknownStructs = []
    self.anonymousStructs = []
    self.anonymousRefStructs = []
    self.undefinedTypes = []
    self.createTypesFromXml(xmlFilename)
    self._typeNames.sort()
    #self.printSkippedTypes()
  
  def addType(self, value) :
    if not value.nescType in self._typeNames :
      self._typeNames.append(value.nescType)
    self._types[value.nescType] = value #XXX: why does this have to be unconditional??
    if not self._types.has_key(value.cType):
      self._types[value.cType] = value
      self._typeNames.append(value.cType)
    
  def __getattr__(self, name) :
    if name in self._typeNames :
      return deepcopy(self._types[name])
    else:
      raise AttributeError("No type \"%s\" defined" % name)
  
  def __getitem__(self, key) :
    if key in self._typeNames :
      return deepcopy(self._types[key])
    else:
      raise AttributeError("No type \"%s\" defined" % key)

  def __repr__(self) :
    return "%s object at %s:\n\n\t%s" % (self.__class__, hex(id(self)), str(self))
    
  def __str__(self) :
    """ Print all available types."""
    string = "\n"
    for t in self._typeNames :
      string += "\t%s\n" % t
    return string
    
  def createTypesFromXml(self, xmlFilename) :
    """Go through the struct and typedef elements in the nescDecls.xml file"""
    
    dom = minidom.parse(xmlFilename)
    typeDefs = [node for node in dom.getElementsByTagName("struct")]
    for node in dom.getElementsByTagName("typedef") :
      typeDefs.append(node)
    
    numSkipped = 0

    #keep going through the queue until it is empty
    while len(typeDefs) > 0:
      typeDef = typeDefs.pop(0)

      #if this is a typedef, see if the value is there
      if typeDef.tagName == "typedef" :
        value = typeDef.getAttribute("value")
        name = typeDef.getAttribute("name")
        #if the real value exists and typedef doesn't already exist, copy and rename original
        if self._types.has_key(value) :
          newType = deepcopy(self._types[value])
          newType.nescType = name
          self.addType(newType)
          numSkipped=0
        else :
          #try again later
          typeDefs.append(typeDef)
          numSkipped += 1
          
      else :
        #if all types within the struct are already defined, it can be defined
        try :
          self.addType(nescStruct(self, typeDef ) )
          numSkipped=0

        except Exception, e:
          if len(e.args) > 0 and e.args[0] == "Undefined struct":
            #otherwise, put it back in the queue and move on to the next one
            typeDefs.append(typeDef)
            numSkipped += 1
          elif len(e.args) > 0 and e.args[0] == "Anonymous struct" :
            self.anonymousStructs.append(typeDef)
          elif len(e.args) > 0 and e.args[0] == "Anonymous struct reference" :
            self.anonymousRefStructs.append( (typeDef, e.args[1]) )
          elif len(e.args) > 0 and e.args[0] == "Unknown type" :
            self.unknownStructs.append( (typeDef, e.args[1]) )
          else :
            #if it's an unknown exception, reraise it
            raise
      
      #make sure we are not cycling endlessly
      if numSkipped >= len(typeDefs) > 0:
        self.undefinedTypes = typeDefs
        break

  def printSkippedTypes(self):
    err = ""
    if len(self.anonymousStructs) >0 :
      err += "\nWarning: %d structs were anonymous." % len(self.anonymousStructs)
#        for struc in anonymousStructs :
#            err += "\t%s\n" % struc.getAttribute("ref")
    if len(self.anonymousRefStructs) >0 :
      err += "\nWarning: The following structs referenced anonymous structs:\n"
      for pair in self.anonymousRefStructs :
        err += "\t%s\n" % pair[0].getAttribute("name")
    if len(self.undefinedTypes) >0 :
      err += "\nWarning: The following types are ill-defined or had circular dependencies:\n"
      for struc in self.undefinedTypes :
        err += "\t%s\n" % struc.getAttribute("name")
    if len(self.unknownStructs) >0 :
      err += "\nWarning: The following structs had unknown xml types:\n"
      for pair in self.unknownStructs :
        err += "\t%s (%s)\n" % (pair[0].getAttribute("name"),
                                pair[1].tagName )
    if len(err) > 0 : print err
    
  def getTypeFromXML(self, xmlDefinition) :
    """Find the type name value given an xml definition.
    If it is an array or pointer, define the new type here."""

    #first, see if the tag is type or if child is type
    if xmlDefinition.tagName.find("type-") < 0 or \
           xmlDefinition.tagName.find("type-qualified") >= 0 :
      foundType = 0
      childNodes = [node for node in xmlDefinition.childNodes
                    if node.nodeType == 1]
      for tag in childNodes :
        if tag.tagName.find("type-") >= 0 :
          foundType += 1
          typeTag = tag
      if foundType < 1 :
        raise Exception("No type tag found")
      if foundType > 1 :
        raise Exception("Too many type tags found")
      else :
        return self.getTypeFromXML(typeTag)

    #now check all the existing types to see if it is one of them
    for val in self._typeNames :
      typeObj = self._types[val]
      if typeObj.isType(xmlDefinition) :
        return deepcopy(typeObj)

    #if the type doesn't already exist, try creating a new one
    try :
      return nescArray(self, xmlDefinition)
    except Exception, e:
        if len(e.args) <= 0 or e.args[0] != "Not array definition":
          raise
    try :
      return nescPointer(self, xmlDefinition)
    except Exception, e:
        if len(e.args) <= 0 or e.args[0] != "Not pointer definition":
          raise
      
    #it is not a simple type, array, or pointer,
    #so it must be a yet undefined struct
    child = getUniqueChild(xmlDefinition)
    if ( xmlDefinition.tagName == "type-tag" and child != None and
         child.tagName == "struct-ref" ):
         if child.hasAttribute("name"):
             raise Exception("Undefined struct")
         else :
             raise Exception("Anonymous struct reference", child)
    else:
      #otherwise, raise an exception
      #(but first make sure the right kind of unknown type is displayed)
      if  xmlDefinition.tagName == "type-tag":
          xmlDefinition = child
      raise Exception("Unknown type", xmlDefinition)

class NescEnums( object ) :
  """A class that holds all enums defined in a specific nesc application.

  usage:
  myEnums = NescEnums('/path/to/nescDecls.xml')
  print myEnums
  var = myEnums.enumName
  """

  def __init__( self, applicationName="Unknown App", xmlFilename = None ) :
    self.applicationName = applicationName
    self._enums = []
    if type(xmlFilename) == str:
      xmlFilename = minidom.parse(xmlFilename)

    self.createEnumsFromXml(xmlFilename)

  def __getitem__(self, key) :
    if key in self._enums :
      return self.__dict__[key]
    else:
      raise AttributeError("No such enum defined")
      
  def createEnumsFromXml(self, dom) :

    #now define all the struct types
    enumDefs = [node for node in dom.getElementsByTagName("enum")]
    integer = re.compile('^I:(\d+)$')
    hexidecimal = re.compile('^(0x[\dabcdefABCDEF]+)$')
    
    for enumDef in enumDefs :
      name = enumDef.getAttribute("name")
      if name in self._enums :
        continue
      value = enumDef.getAttribute("value")
      match = integer.match(value)
      if match != None :
        self.__dict__[name] = int(match.groups()[0])
      else :
        match = hexidecimal.match(value)
        if match != None :
          self.__dict__[name] = int(match.groups()[0], 16)
        else :
          self.__dict__[name] = value
      self._enums.append(name)
      
    namedEnums = [node for node in dom.getElementsByTagName("namedEnum")]
    for namedEnum in namedEnums :
      name = namedEnum.getAttribute("name")
      self.__dict__[name] = NescEnums(namedEnum,name)
      self._enums.append(name)
    
  def __repr__(self) :
    return "%s object at %s:\n\n\t%s" % (self.__class__, hex(id(self)), str(self))
  
  def __str__(self) :
    """ Print all available enums."""
    string = "\n"
    for key in self._enums :
      string += "\t%s = %s\n" % (key, str(self[key]))
    return string
    

class NescMsgs( object ) :
  """A class that holds all msgs defined in a specific nesc application.
  It assumes a struct is a message if AM_STRUCTNAME is defined.

  usage:
  myMsgs = NescMsgs(myTypes, myEnums[, applicationName])
  print myMsgs
  var = myMsgs.msgName
  """
  def __init__( self, types, enums, applicationName="Unknown App" ) :
    self.applicationName = applicationName
    msgTypes = [enum for enum in enums._enums if enum.find("AM_") ==0]
    name = re.compile("^AM_(\w+)$")
    self._msgNames = []
    self._msgs = {}
    for msgType in msgTypes :
      if type(enums[msgType]) == int:
        msgName = name.match(msgType)
        if msgName != None :
          msgName = msgName.groups()[0]
        for key in types._typeNames :
          if key.lower() == msgName.lower() :
            msg = TosMsg(enums[msgType], types[key])
            self._msgs[key] = msg
            self._msgNames.append(key)
            break

  def __getattr__(self, name) :
    if name in self._msgNames :
      return deepcopy(self._msgs[name])
    else:
      raise AttributeError("No such message defined")
  
  def __getitem__(self, key) :
    if key in self._msgNames :
      return deepcopy(self._msgs[key])
    else:
      raise AttributeError("No such message defined")
      
  def __repr__(self) :
    return "%s object at %s:\n\n\t%s" % (self.__class__, hex(id(self)), str(self))
  
  def __str__(self) :
    """ Print all available msgs."""
    string = "\n"
    for key in self._msgNames :
      string += "\t%5d : %s\n" % (self._msgs[key].amType, key)
    return string
    

class NescApp( object ) :
  """A class that holds all types, enums, msgs, rpc commands and ram
  symbol definitions as defined for a specific nesc application.

  usage:
  myApp = nescApp('/path/to/nescDecls.xml')
  print myApp
  var = myApp.enums.enumName
  var = myApp.types.typeName
  """
  def __init__( self, applicationName="Unknown App", xmlFile="app.xml" ) :
    """This function creates the NescEnums, NescTypes, and NescMsgs
    objects for a particular application."""
    
    #first, import all enums, types, msgs, rpc functions, and ram symbols
    self.applicationName = applicationName
    self.xmlFile = xmlFile
    
    # Check for the nescDecls.xml file
    if not os.path.isfile(xmlFile):
      raise Exception("""\nERROR: cannot find file \"%s\".

Your nesC app cannot be imported.  Be sure that you compiled with the \"nescDecls\" option.\n\n""" % xmlFile)

    # Import enums, types, and msgs
    self.enums = NescEnums(applicationName, xmlFile)
    self.types = NescTypes(applicationName, xmlFile)
    self.variables = NescVariables(applicationName, xmlFile)
    self.messages = NescMsgs(self.types, self.enums, applicationName)

  def __repr__(self) :
    return "%s object at %s:\n\n%s" % (self.__class__, hex(id(self)), str(self))
  
  def __str__(self) :
    """ Print all application declarations."""
    string = "%20s : %d\n" % ("Enums", len(self.enums._enums))
    string += "%20s : %d\n" % ("Types", len(self.types._types))
    string += "%20s : %d\n" % ("Messages", len(self.messages._msgNames))
    string += "%20s : %d\n" % ("Variables", len(self.variables._varNames))
    return string

  def configureTossim(self):
    for var in variables:
      Mote.var
