# 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.addins.cgenUML14.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("")


def addPrelude(f):
    f.add("/*")
    # 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!")
    f.add(" * Generated by the Coral modelling framework. Do not edit!")
    f.add(" */")
    f.add("")
    

class CppCodeGenerator(CodeGenerator):
    def __init__(self, topleveldir):
        CodeGenerator.__init__(self, topleveldir)

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

    def _writePackage(self, pkg):
        list_of_pkgs = coral.Element__getParentsAndMyself__(pkg)
        d = os.sep.join([str(p.name) for p in list_of_pkgs])
        f_cpp = self.getFile(d + ".cpp")
        f_header = self.getFile(d + ".h")

        prelude_cpp = f_cpp.getStream("prelude", "notice_generate")
        if prelude_cpp:
            addPrelude(prelude_cpp)
            s = "/".join([ self.normStrVariable(str(i.name)) for i in list_of_pkgs])
            prelude_cpp.add("#include <" + s + ".h>")
            prelude_cpp.add("")
        prelude_header = f_header.getStream("prelude", "notice_generate")
        stream_cpp = f_cpp.getStream("definitions")
        if prelude_header:
            ifndef = self.normStrVariable("H_" + "_".join([ str(i.name).upper() for i in list_of_pkgs]))
            prelude_header.add("#ifndef " + ifndef)
            prelude_header.add("#define " + ifndef)
            addPrelude(prelude_header)
            for i in list_of_pkgs:
                ns = self.normStrVariable(i.name)
                prelude_header.add("namespace " + ns + " {")
                stream_cpp.add("namespace " + ns + " {")
            stream_cpp.add("")
            prelude_header.add("")
        stream_h   = f_header.getStream("definitions")
        for a in pkg.ownedElement:
            if a.oclIsKindOf(a.__type__().__language__().getElement("Class")):
                self._writeClass(stream_cpp, stream_h, a)
            elif a.oclIsKindOf(a.__type__().__language__().getElement("Package")):
                self._writePackage(a)
        postlude_cpp = f_cpp.getStream("postlude")
        postlude_cpp.add("")
        postlude = f_header.getStream("postlude")
        postlude.add("")
        for i in list_of_pkgs:
            postlude.add("}")
            postlude_cpp.add("}")
        postlude.add("#endif")
        postlude.add("")

    def _writeMethod(self, stream, importstream, method):
        res = ""
        assert method.oclIsKindOf(method.__type__().__language__().getElement("Method"))
        assert method.specification
        if method.body:
            if method.body.language != "c++":
                return False
        # The class in which the method is
        res += self.normStrVariable(unicode(method.__parent__().name)) + "::"
        # Method definition
        res += self.normStrVariable(unicode(method.specification.name)) + "("
        # Parameters
        params = []
        t_static = ""
        # Static / Instance method
        if method.specification.ownerScope == "instance":
            pass
        elif method.specification.ownerScope == "classifier":
            t_static = "static "
        else:
            assert 0

        # Loop through method formal parameters
        for param in method.specification.parameter:
            if param.kind == "return":
                res = param.type.name + " " + res
                continue
            params.append(param.type.name + " " + unicode(param.name))
            
        # BUG loop through keywords args
        # BUG remember to set default values.
        res += ", ".join(params)
        res += ")"
        header = res
        if method.specification.ownerScope == "classifier":
            header = "static " + res
        importstream.add(header + ";")
        stream.add(res + " {")
        # Method body
        stream.indent(1)
        b = ""
        if method.body:
            b = unicode(method.body.body).strip()
        if b == "":
            stream.add("/* No method body defined */")
        else:
            b = b.split("\n")
            for i in b:
                stream.add(i)
        stream.indent(-1)
        stream.add("}")
        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 _getSuperclasses(self, stream, importstream, aclass):
        if not aclass.generalization:
            return ""
        #assert 0
        gens = aclass.generalization
        s = ""
        import_these = {}
        for i in gens:
            p = i.parent.__parent__()
            if p:
                import_these[p] = True

        _s = []
        for i in gens:
            sc = i.parent
            if sc.namespace == aclass.namespace:
                list_of_pkgs = [ sc ]
            else:
                list_of_pkgs = coral.Element__getParentsAndMyself__(sc)
            imp = "::".join( [ self.normStrFile(i.name) for i in list_of_pkgs ] )
            _s.append(unicode(i.visibility) + " " + imp)
        s = ", ".join([i for i in _s ])
        return s
     
    def _writeClass(self, stream, importstream, aclass):
        s = self._getSuperclasses(stream, importstream, aclass)
        # Class definition
        if s:
            s = " : " + s
        importstream.add("class " + self.normStrVariable(aclass.name) + s + " {")
        # BUG __init__
        # BUG __del__
        # BUG other special methods
        publicstream = CodeArtifact()
        protectedstream = CodeArtifact()
        privatestream = CodeArtifact()
        for method in aclass.feature:
            if not method.oclIsKindOf(method.__type__().__language__().getElement("Method")):
                continue
            s = None
            if method.visibility == "private":
                s = privatestream
            elif method.visibility == "protected":
                s = protectedstream
            elif method.visibility == "public":
                s = publicstream
            else:
                assert 0
            self._writeMethod(stream, s, method)
        if privatestream.text:
            importstream.add("private:")
            importstream.indent(1)
            importstream.addCodeArtifact(privatestream)
            importstream.indent(-1)
        if protectedstream.text:
            importstream.add("protected:")
            importstream.indent(1)
            importstream.addCodeArtifact(protectedstream)
            importstream.indent(-1)
        if publicstream.text:
            importstream.add("public:")
            importstream.indent(1)
            importstream.addCodeArtifact(publicstream)
            importstream.indent(-1)
        importstream.add("};")
        importstream.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_]")

