"""

TODO:

   parse namespace directives (qname expansion)
   generate namespace directives
   unify stringIO / stream / file access
   clarify access to ns -- we cheat!   (see __dict__)
          [ namespace should have commonShortnames = ( ('foaf', 'http....'), ... )  ]
   better error messages on syntax errors


"""

from objectmodel import ObjectModel, Class, Property
import objectmodel
from namespace import ns
import re
from sys import stderr
import qname

class Error(RuntimeError):
   pass

class Generator:
    """
 
    >>> from objectmodel import createTestModel1
    >>> t1 = createTestModel1()
    
    >>> import asn0701
    >>> g = asn0701.Generator()
    
    >>> g.printModel(t1, prefix=".    ")
    .    default namespace "http://example.com/books/"
    .    namespace xsd   = "http://www.w3.org/2001/XMLSchema#"
    .    namespace books = "http://example.com/books/"
    .
    .    class Chainstore
    .
    .        subclass ChainBookstore
    .
    .    class Store
    .
    .        subclass Bookstore
    .            property stockedTitles : set of Title
    .
    .            subclass ChainBookstore    # (repeat, see above)
    .
    .            subclass SmallBookstore
    .
    .                subclass PrivateBookSeller
    .
    .    class Title
    .
    .    class Sale
    .        property titles : list of Title
    .        property amount : xsd:int
    .        property date : xsd:string
    .

    """

    def __init__(self):
        self.map = qname.Map()
        self.map.defaults = [ qname.common ]
        
    def qname(self, resource):
       """Should be passed a URI, but accepts some other forms for
       historical reasons.  Such use is deprecated.  
       """
       uri = objectmodel.uri(resource)
       return self.map.qname(uri)

    def writeModel(self, stream, model):
       import sys
       sys.stdout = stream
       self.printModel(model)
       sys.stdout = sys.__stdout__

       
    def printModel(self, model, prefix=""):
       self.model = model
       self.doneClass = []

       counts = model.namespaces_with_count(self.map)
       self.default_short = counts[-1][1]
       self.map.bind('', self.map.getLong(self.default_short))

       print prefix+'default namespace "%s"' % self.map.getLong('')

       pad = -1 * max([len(short) for count, short in counts])
       for (count, short) in counts:
          print (prefix+'namespace %*s = "%s"' %
                 (pad, short, self.map.getLong(short)))

       print prefix.rstrip()
          
       for c in model.classes:
          if c in self.doneClass:
             pass
          else: 
             self.printClass(c, prefix=prefix, intro="class")

    def printClass(self, c, prefix="", intro=""):
       print prefix+intro, self.qname(c),
       if c in self.doneClass:
          print "   # (repeat, see above)"
       else:
          self.doneClass.append(c)
          print
       
       prefix += "    "

       for prop in self.model.propertiesForClass(c):
          self.printProperty(prop, prefix)

       print prefix.rstrip()

       for sub in self.model.subclassesForClass(c):
          self.printClass(sub, prefix, intro="subclass")


    def printProperty(self, prop, prefix):
       print prefix+"property", self.qname(prop.name),

       if prop.to:
          type = self.qname(prop.to)
          print ":", 

          if prop.optional:
             print "optional",

          if prop.list:
             print "list of", type,
          elif prop.multi:
             print "set of", type,
          else:
             print type,

       print

nsPattern = re.compile(r"""^\s*(?P<default>default)?\s*namespace\s+(?P<short>\w*)\s*=\s*"(?P<long>[^"]*)"\s*""")
commentPattern = re.compile(r"^([^#]*)\s*#")
indentPattern = re.compile(r"^( *)(.*)$")
classLinePattern = re.compile(r"^(sub)?class\s+(?P<name>(\w|:)+)\s*$")
propertyLinePattern = re.compile(r"^property\s+(?P<name>(\w|:)+)\s*:\s*((?P<set>set\s+of\s*)|(?P<list>list\s+of\s*)|)(?P<type>(\w|:)+)\s*(,\s*(?P<optional>optional))?\s*$")

class Parser:
   """
   >>> import asn0701
   >>> p = asn0701.Parser()

   >>> stream = open("test-data/poscond.asn", "r")
   >>> m = p.readModel(stream)

   >>> g = asn0701.Generator()

   x>>> g.printModel(m, prefix=".  ")
   
   """

   def __init__(self):
      self.map = qname.Map()
      self.map.defaults = [ qname.common ]

   def readModel(self, stream):
      self.model = ObjectModel()
      self.indents = [ (-1, None) ]
      for line in stream:
         self.parseLine(line)
      return self.model

   def parseLine(self, line):

      #print >>stderr, ""
      #print >>stderr, "line:", line

      m = nsPattern.match(line)
      if m:
         d = m.groupdict()
         if d["default"]:
            self.map.bind('', d["long"])
         else:
            if not d["short"]:
               raise Error, "Namespace declaration needs short name, if not default"

         self.map.bind(d["short"], d["long"])
         return

      m = commentPattern.match(line)    # doesn't know about quotes
      if m:
         line = m.groups()[0]

      if not line.strip():
         return

      if line.find("\t") != -1:
         raise Error, "no tab characters allowed (M-x untabify, man expand, or something)"

      m = indentPattern.match(line)
      (indentText, line) = m.groups()
      indent = len(indentText)

      (baseIndent, container) = self.indents[-1]
      #print >>stderr, "[start] indent: %i, baseIndent: %i, container: %s" % (indent, baseIndent, container)
      while indent <= baseIndent:
         self.indents.pop()
         (baseIndent, container) = self.indents[-1]
         #print >>stderr, "[pop'd] indent: %i, baseIndent: %i, container: %s" % (indent, baseIndent, container)
               
      m = classLinePattern.match(line)
      if m:
         d = m.groupdict()
         item = self.model.get_or_create_class(self.map.uri((d["name"])))
         #print >>stderr, ("Got class, URI: %s" % self.map.uri((d["name"])))
         if container:
            #print >> stderr, "container: %s" % container
            item.addSuperclass(container)
      else:
         m = propertyLinePattern.match(line)
         if m:
            d = m.groupdict()
            item = Property(self.map.uri(d["name"]),
                            domain=container,
                            to=self.map.uri(d["type"]),
                            list=(d["list"] is not None),
                            optional=(d["optional"] is not None),
                            multi=(d["set"] is not None)
                            )
            # check for duplicate?
            self.model.add(item)
         else:
            raise Error, "syntax error: "+line
      self.indents.append( (indent, item) )

      # print m.groupdict()
      
if __name__ == "__main__":
    import doctest, sys
    doctest.testmod(sys.modules[__name__])
