"""Provides the Module and Type base classes that user code inherits from."""

__all__ = ["Module", "Type", "member"]

from framer import struct, template
from framer.function import Function, Method
from framer.member import member
from framer.slots import *
from framer.util import cstring, unindent

from types import FunctionType

# The Module and Type classes are implemented using metaclasses,
# because most of the methods are class methods.  It is easier to use
# metaclasses than the cumbersome classmethod() builtin.  They have
# class methods because they are exposed to user code as base classes.

class BaseMetaclass(type):
    """Shared infrastructure for generating modules and types."""

    # just methoddef so far

    def dump_methoddef(self, f, functions, vars):
        def p(templ, vars=vars): # helper function to generate output
            print(templ % vars, file=f)

        if not functions:
            return
        p(template.methoddef_start)
        for name, func in sorted(functions.items()):
            if func.__doc__:
                p(template.methoddef_def_doc, func.vars)
            else:
                p(template.methoddef_def, func.vars)
        p(template.methoddef_end)

class ModuleMetaclass(BaseMetaclass):
    """Provides methods for Module class."""

    def gen(self):
        self.analyze()
        self.initvars()
        f = open(self.__filename, "w")
        self.dump(f)
        f.close()

    def analyze(self):
        self.name = getattr(self, "abbrev", self.__name__)
        self.__functions = {}
        self.__types = {}
        self.__members = False

        for name, obj in self.__dict__.items():
            if isinstance(obj, FunctionType):
                self.__functions[name] = Function(obj, self)
            elif isinstance(obj, TypeMetaclass):
                obj._TypeMetaclass__module = self.name
                obj.analyze()
                self.__types[name] = obj
                if obj.has_members():
                    self.__members = True

    def initvars(self):
        v = self.__vars = {}
        filename = getattr(self, "__file__", None)
        if filename is None:
            filename = self.__name__ + "module.c"
        self.__filename = v["FileName"] = filename
        name = v["ModuleName"] = self.__name__
        v["MethodDefName"] = "%s_methods" % name
        v["ModuleDocstring"] = cstring(unindent(self.__doc__))

    def dump(self, f):
        def p(templ, vars=self.__vars): # helper function to generate output
            print(templ % vars, file=f)

        p(template.module_start)
        if self.__members:
            p(template.member_include)
        print(file=f)

        if self.__doc__:
            p(template.module_doc)

        for name, type in sorted(self.__types.items()):
            type.dump(f)

        for name, func  in sorted(self.__functions.items()):
            func.dump(f)

        self.dump_methoddef(f, self.__functions, self.__vars)

        p(template.module_init_start)
        for name, type in sorted(self.__types.items()):
            type.dump_init(f)

        p("}")

class Module(metaclass=ModuleMetaclass):
    pass

class TypeMetaclass(BaseMetaclass):

    def dump(self, f):
        self.initvars()

        # defined after initvars() so that __vars is defined
        def p(templ, vars=self.__vars):
            print(templ % vars, file=f)

        if self.struct is not None:
            print(unindent(self.struct, False), file=f)

        if self.__doc__:
            p(template.docstring)

        for name, func in sorted(self.__methods.items()):
            func.dump(f)

        self.dump_methoddef(f, self.__methods, self.__vars)
        self.dump_memberdef(f)
        self.dump_slots(f)

    def has_members(self):
        if self.__members:
            return True
        else:
            return False

    def analyze(self):
        # called by ModuleMetaclass analyze()
        self.name = getattr(self, "abbrev", self.__name__)
        src = getattr(self, "struct", None)
        if src is not None:
            self.__struct = struct.parse(src)
        else:
            self.__struct = None
        self.__methods = {}
        self.__members = {}
        for cls in self.__mro__:
            for k, v in cls.__dict__.items():
                if isinstance(v, FunctionType):
                    self.__methods[k] = Method(v, self)
                if isinstance(v, member):
                    self.__members[k] = v
                    assert self.__struct is not None
                    v.register(k, self.__struct)
        self.analyze_slots()

    def analyze_slots(self):
        self.__slots = {}
        for s in Slots:
            if s.special is not None:
                meth = self.__methods.get(s.special)
                if meth is not None:
                    self.__slots[s] = meth
        self.__slots[TP_NAME] = '"%s.%s"' % (self.__module, self.__name__)
        if self.__doc__:
            self.__slots[TP_DOC] = "%s_doc" % self.name
        if self.__struct is not None:
            self.__slots[TP_BASICSIZE] = "sizeof(%s)" % self.__struct.name
            self.__slots[TP_DEALLOC] = "%s_dealloc" % self.name
        if self.__methods:
            self.__slots[TP_METHODS] = "%s_methods" % self.name
        if self.__members:
            self.__slots[TP_MEMBERS] = "%s_members" % self.name

    def initvars(self):
        v = self.__vars = {}
        v["TypeName"] = self.__name__
        v["CTypeName"] = "Py%s_Type" % self.__name__
        v["MethodDefName"] = self.__slots[TP_METHODS]
        if self.__doc__:
            v["DocstringVar"] = self.__slots[TP_DOC]
            v["Docstring"] = cstring(unindent(self.__doc__))
        if self.__struct is not None:
            v["StructName"] = self.__struct.name
        if self.__members:
            v["MemberDefName"] = self.__slots[TP_MEMBERS]

    def dump_memberdef(self, f):
        def p(templ, vars=self.__vars):
            print(templ % vars, file=f)

        if not self.__members:
            return
        p(template.memberdef_start)
        for name, slot in sorted(self.__members.items()):
            slot.dump(f)
        p(template.memberdef_end)

    def dump_slots(self, f):
        def p(templ, vars=self.__vars):
            print(templ % vars, file=f)

        if self.struct:
            p(template.dealloc_func, {"name" : self.__slots[TP_DEALLOC]})

        p(template.type_struct_start)
        for s in Slots[:-5]: # XXX
            val = self.__slots.get(s, s.default)
            ntabs = 4 - (4 + len(val)) // 8
            line = "        %s,%s/* %s */" % (val, "\t" * ntabs, s.name)
            print(line, file=f)
        p(template.type_struct_end)

    def dump_init(self, f):
        def p(templ):
            print(templ % self.__vars, file=f)

        p(template.type_init_type)
        p(template.module_add_type)

class Type(metaclass=TypeMetaclass):
    pass
