"""
Parse/Generate python classes to match objectmodel classes

still highly experimental!

todo:
    keep namespace info, in the class, I guess
    (the URI of the class, and the URIs of each property,
    unless we're assuming they have the same namespace)

       rdf -> python -> schema-specic xml -> python etc ...

        ISSUE - does this support multiple-namespace ontologies?
              --- or need the URI only go at the module level?
       
    add triplestore interface, with namespace handling
          (where SHOULD the data be stored???)

    demo forward/backward rules/code




    getNamespace could be inherited from a baseclass
    which looks it up in the module.    Eh, whatever.
    
"""

from sys import stderr
from cStringIO import StringIO
import re

import opendata.loader
from objectmodel import ObjectModel, Class, Property
from namespace import ns
import qname

class Error(RuntimeError):
   pass

class Generator:
    """
 
    >>> from objectmodel import createTestModel1
    >>> t1 = createTestModel1()
    
    >>> import python
    >>> g = python.Generator()

    Write test model 1 to a file...
    
    >>> out = file('generated_TestModel1.py', 'w')
    >>> g.writeModel(out, t1)    # breaks doctest
    >>> out.close()

    then import it and use it!!
    
    >>> import generated_TestModel1
    >>> x = generated_TestModel1.Sale()
    >>> x.amount = 3
    >>> x.amount
    3

    """

    def __init__(self):
        self.ns = { }          
        for (short, long) in ns.__dict__.iteritems():
           self.ns[long.uri] = short
        self.nscount = 1
        
    def qname(self, resource):
        import namespace 
        split = namespace.split(str(resource))
        if split:
            (long, local) = split
            #print "long: '%s', local: '%s'" % (long, local) 
            try:
                short = self.ns[long]
            except KeyError:
                short = "ns" + str(self.nscount)
                self.nscount = self.nscount + 1
                self.ns[long] = short
                # print "Adding %s for '%s'" % (short, long)

            if short == "ns1":
                return local
            else: 
                return short + ":" + local
        else:
            return "Resource('"+str(resource)+"')"

    def writeModel(self, stream, model):
       import sys
       old = sys.stdout
       sys.stdout = stream
       self.printModel(model)
       sys.stdout = old
       
    def printModel(self, model):
       self.model = model
       for c in model.classes:
           self.printClass(c)
           print

    def printClass(self, c):
        print "class "+self.qname(c) + " (",
        if c.superclasses:
           # @@@ model check!
           print ", ".join(self.qname(x.name) for x in c.superclasses) ,
        else:
           print "object",
        print ") :"

        #print '    """docstring goes here"""'
        print

        properties = [ x for x in self.model.propertiesForClass(c) ]

        #    to do slots, we need to store the data somewhere else,
        #    in a way that works with inheritance -- we need to inherit
        #    from  ExternalDataObject, which links to where the data is
        #    stored....
        
        #if properties:
        #    print ('    __slots__ = [ "%s" ]' %
        #           '", "'.join([self.qname(x.name) for x in properties]))
        #else:
        #    print  '    __slots__ = [ ]'
        print '    def __init__(self, **kwargs):'
        if properties:
           for prop in properties:
              (ns, local) = qname.uri_split(str(prop.name))
              (ns, tostr) = qname.uri_split(str(prop.to))
              if prop.list:
                 typestr = "[ ]"
                 comment = "list of "+tostr
              elif prop.multi:
                 typestr = "set"
                 comment = "set of "+tostr
              else:
                 typestr = "None"
                 comment = "a "+tostr
              print '        self.%s = %s  # %s' % (local, typestr, comment)
        else:
           print '        self.__dict__.update(kwargs)'   # loop setattr?
        print
        #print '    def getProperties(self):'
        #print '        return ['
        #for prop in self.model.propertiesForClass(c):
        #    print '          '+repr(prop.name)+','
        #print '        ]'

        (ns, dummy) = qname.uri_split(str(c.name))
        print '    def getNamespace(self): '
        print '       return '+repr(ns)
        print 
        print '    def __repr__(self): '
        print '        return (self.__class__.__name__+"("+'
        for prop in self.model.propertiesForClassWithInheritance(c):
           (ns, local) = qname.uri_split(str(prop.name))
           print '            "%s="+repr(self.%s)+", "+' % (local,local)
        print '            ")") '
        # inheritance based on superclasses, in/out of schema

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

    def printProperty(self, prop):
        """At some point this support things like
               - type checking
               - forward chaining rules on "set"
               - backward chaining rules on "get"
               - list values
               - multi values
        """
        name = self.qname(prop.name)
        print '    def set_%s(self, new_value):' % name
        print '        # minimal implementation for now'
        print '        self.__dict__["(tmp) %s"] = new_value' % name
        print '    def get_%s(self):' % name
        print '        # minimal implementation for now'
        print '        return self.__dict__["(tmp) %s"]' % name
        print ('    %s = property(get_%s, set_%s, None, \n        """%s""")' %
               (name, name, name, "documentation for "+name))

        ###   try to do these in a way which is subclassable
        ###   so storage can be subclassed....?
        
        # (inverse -- rules for maintain these?)
        # (transitive -- rules for maintaining these?)
        # if prop.to: / type?
        # if prop.optional:
        # if prop.list:
        # if prop.multi:

        print

def import_interface(source, module_name):
   """Import a python module created to match a datamodel

    >>> import opendata.python
    >>> (m,i) = opendata.python.import_interface('test-data/animal-movies.asn', 'animal_movies')
    
    >>> a = i.Animal()
    >>> a.getNamespace()
    'http://s2.example.com/'
    >>> a          # should have module name, no trailing comma....
    Animal(mother=None, father=None, )

   """
   model = opendata.loader.load(source)
   
   out = StringIO()
   generator = Generator()
   generator.writeModel(out, model)

   copy = open(module_name+".py", "w")
   copy.write(out.getvalue())
   copy.close()

   return (model, __import__(module_name))
   
   #code = compile(out.getvalue(), "generated from "+source, "exec")
   #return eval(code)

   #out = open("tmp.py", "w")
   #generator = Generator()
   #generator.writeModel(out, model)
   
   #code = compile(out.getvalue(), "generated from "+source, "exec")
   #exec code

class _Base ( object ):
   """ musing...   not used. """
   def __init__(self):
      pass

   def getURI(self):
      return self.__dict__['_uri']     # do we have one?

   def getClass(self):
      return self.getNamespace()+str(self.__class__)

   def getProperties(self):
      return self.getClass().properties

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