# This file is part of the Coral project.
# Please read the copyright notice in the LICENSE file.

from coral.lgen.codeartifact import CodeArtifact
from coral.cgen.general.CodeGenerator import CodeGenerator
from coral.modeler.unicode import *
import re
import time
import os
import coral

class _ImportCodeArtifact(CodeArtifact):
    def __init__(self):
        CodeArtifact.__init__(self)
        self.imports = {}

    def beforeDump(self):
        for i in self.imports.iterkeys():
            self.add("import " + i)
        self.add("")


class PyCodeGenerator(CodeGenerator):
    def __init__(self, topleveldir):
        CodeGenerator.__init__(self, topleveldir)
        self.python_version = "2.3" # merrily ignored.

    def generateCode(self, root_set):
        for i in root_set:
            if i.oclIsKindOf(i.__type__().__language__().getElement("Package")):
                self._writePackage(i)

    def _writePackage(self, pkg):
        list_of_pkgs = coral.Element__getParentsAndMyself__(pkg)
        d = ""
        for p in list_of_pkgs:
            d += p.name
            d += os.sep
        # Determines if we create a __init__.py or just a leaf "mypackage.py" file
        found = 0
        for e in pkg.ownedElement:
            if e.__type__().getName() == "Package":
                found = 1
                break
        if found:
            f = self.getFile(d + "_init_.py")
        else:
            f = self.getFile(d[:-1] + ".py")

            
        prelude = f.getStream("prelude", "notice_generate")
        if prelude:
            prelude.add("# -*- coding: UTF-8 -*-")
            # No, file contents will change for no good reason...
            #rfc822time = time.strftime("%a, %d %b %Y %H:%M:%S %z")
            #prelude.add("# Generated at " + rfc822time)
            #prelude.add("# by the Coral modelling framework. Do not edit!")
            prelude.add("# Generated by the Coral modelling framework. Do not edit!")
            prelude.add("#")
            prelude.add("")
        stream = f.getStream("definitions")
        importstream = _ImportCodeArtifact()
        f.setStream("imports", importstream)
        for a in pkg.ownedElement:
            if a.oclIsKindOf(a.__type__().__language__().getElement("Class")):
                self._writeClass(stream, importstream, a)
            elif a.oclIsKindOf(a.__type__().__language__().getElement("Package")):
                self._writePackage(a)
        postlude = f.getStream("postlude")
        postlude.add("")
        postlude.add("")

    def _writeMethod(self, stream, importstream, method):
        assert method.oclIsKindOf(method.__type__().__language__().getElement("Method"))
        assert method.specification
        if method.body:
            if method.body.language != "python":
                return False
        # Method definition
        res = "def " + unicode(method.specification.name) + "("
        # Parameters
        params = []
        # Static / Instance method
        if method.specification.ownerScope == "instance":
            params.append("self")
        elif method.specification.ownerScope == "classifier":
            params.append("cls")
        else:
            assert 0

        # Loop through method formal parameters
        for param in method.specification.parameter:
            if param.kind == "return":
                continue
            params.append(unicode(param.name))
            
        # BUG loop through keywords args
        # BUG remember to set default values.
        res += ", ".join(params)
        res += "):"
        stream.add(res)
        # Method body
        stream.indent(1)
        b = ""
        if method.body:
            b = unicode(method.body.body).strip()
        if b == "":
            stream.add("pass")
        else:
            b = b.split("\n")
            for i in b:
                stream.add(i)
        stream.indent(-1)
        stream.add("")
        if method.specification.ownerScope == "classifier":
            stream.add(method.specification.name + " = classmethod("
                       + method.specification.name + ")")
            stream.add("")
        return True

    def _import(self, curnamespace, stream, pkghash):
        for p in pkghash:
            if p == curnamespace:
                continue
            list_of_pkgs = coral.Element__getParentsAndMyself__(p)
            imp = ".".join([self.normStrFile(i.name) for i in list_of_pkgs])
            stream.imports[imp] = True
            
            
    def _writeClass(self, stream, importstream, aclass):
        # Superclasses
        supers = aclass.generalization.parent
        s = ""
        import_these = {}
        if supers:
            for i in supers:
                p = i.__parent__()
                if p:
                    import_these[p] = True

            _s = []
            for i in supers:
                if i.namespace == aclass.namespace:
                    _s.append(self.normStrFile(i.name))
                    continue
                list_of_pkgs = coral.Element__getParentsAndMyself__(i)
                imp = ".".join( [ self.normStrFile(i.name) for i in list_of_pkgs ] )
                _s.append(imp)
            supers = [ i for i in _s ]
            s = "(" + ", ".join(supers) + ")"
        # Class definition
        stream.add("class " + self.normStrVariable(aclass.name) + s + ":")
        # BUG __init__
        # BUG __del__
        # BUG other special methods
        stream.indent(1)
        n_methods = 0
        for method in aclass.feature:
            if not method.oclIsKindOf(method.__type__().__language__().getElement("Method")):
                continue
            if self._writeMethod(stream, importstream, method):
                n_methods += 1
        if n_methods == 0:
            stream.add("pass")
        stream.indent(-1)
        stream.add("")

        self._import(aclass.namespace, importstream, import_these)



    def __n(self, s, regexp):
        s = PyStringToFileName(s)
        s = re.sub("_", "__", s)
        res = ""
        for i in s:
            if re.search(regexp, i):
                res += i
            else:
                res += "_" + str(ord(i))
        return res

    def normStrFile(self, s):
        return self.__n(s, "[a-zA-Z0-9_\.]")

    def normStrVariable(self, s):
        return self.__n(s, "[a-zA-Z0-9_]")

