from pypy.rpython.ootypesystem import ootype
from pypy.translator.cli.node import Node
from pypy.translator.cli.cts import CTS
from pypy.translator.oosupport.constant import push_constant
from pypy.translator.cli.ilgenerator import CLIBaseGenerator

try:
    set
except NameError:
    from sets import Set as set

class Class(Node):
    def __init__(self, db, INSTANCE, namespace, name):
        self.db = db
        self.cts = db.genoo.TypeSystem(db)
        self.INSTANCE = INSTANCE
        self.namespace = namespace
        self.name = name

    def dependencies(self):
        if not self.is_root(self.INSTANCE):
            self.db.pending_class(self.INSTANCE._superclass)

    def __hash__(self):
        return hash(self.INSTANCE)

    def __eq__(self, other):
        return self.INSTANCE == other.INSTANCE

    def __ne__(self, other):
        return not self == other

    def is_root(INSTANCE):
        return INSTANCE._superclass is None
    is_root = staticmethod(is_root)

    def get_name(self):
        return self.name

    def __repr__(self):
        return '<Class %s>' % self.name

    def get_base_class(self):
        base_class = self.INSTANCE._superclass
        if self.INSTANCE is self.db.genoo.EXCEPTION:
            assert self.is_root(base_class)
            return '[mscorlib]System.Exception'
        if self.is_root(base_class):
            return '[mscorlib]System.Object'
        else:
            return self.db.class_name(base_class)

    def is_abstract(self):
        return False # XXX
        
        # if INSTANCE has an abstract method, the class is abstract
        method_names = set()
        for m_name, m_meth in self.INSTANCE._methods.iteritems():
            if not hasattr(m_meth, 'graph'):
                return True
            method_names.add(m_name)

        # if superclasses have abstract methods not overriden by
        # INSTANCE, the class is abstract
        abstract_method_names = set()
        cls = self.INSTANCE._superclass
        while cls is not None:
            abstract_method_names.update(cls._methods.keys())
            cls = cls._superclass
        not_overriden = abstract_method_names.difference(method_names)
        if not_overriden:
            return True
        
        return False

    def render(self, ilasm):        
        if self.is_root(self.INSTANCE):
            return

        self.ilasm = ilasm
        self.gen = CLIBaseGenerator(self.db, ilasm)

        if self.namespace:
            ilasm.begin_namespace(self.namespace)

        ilasm.begin_class(self.name, self.get_base_class(), abstract=self.is_abstract())
        for f_name, (f_type, f_default) in self.INSTANCE._fields.iteritems():
            cts_type = self.cts.lltype_to_cts(f_type)
            f_name = self.cts.escape_name(f_name)
            if cts_type != CTS.types.void:
                ilasm.field(f_name, cts_type)

        self._ctor()
        self._toString()

        for m_name, m_meth in self.INSTANCE._methods.iteritems():
            if hasattr(m_meth, 'graph'):
                # if the first argument's type is not a supertype of
                # this class it means that this method this method is
                # not really used by the class: don't render it, else
                # there would be a type mismatch.
                args =  m_meth.graph.getargs()
                SELF = args[0].concretetype
##                if not ootype.isSubclass(self.INSTANCE, SELF):
##                    continue
                f = self.db.genoo.Function(self.db, m_meth.graph, m_name, is_method = True)
                f.render(ilasm)
            else:
                # abstract method
                METH = m_meth._TYPE
                arglist = [(self.cts.lltype_to_cts(ARG), 'v%d' % i)
                           for i, ARG in enumerate(METH.ARGS)
                           if ARG is not ootype.Void]
                returntype = self.cts.lltype_to_cts(METH.RESULT)
                ilasm.begin_function(m_name, arglist, returntype, False, 'virtual') #, 'abstract')
                ilasm.add_comment('abstract method')
                if isinstance(METH.RESULT, ootype.OOType):
                    ilasm.opcode('ldnull')
                else:
                    push_constant(self.db, METH.RESULT, METH.RESULT._defl(), self.gen)
                ilasm.opcode('ret')
                ilasm.end_function()

        ilasm.end_class()

        if self.namespace:
            ilasm.end_namespace()

    def _ctor(self):
        self.ilasm.begin_function('.ctor', [], 'void', False, 'specialname', 'rtspecialname', 'instance')
        self.ilasm.opcode('ldarg.0')
        self.ilasm.call('instance void %s::.ctor()' % self.get_base_class())
        # set default values for fields
        default_values = self.INSTANCE._fields.copy()
        default_values.update(self.INSTANCE._overridden_defaults)
        for f_name, (F_TYPE, f_default) in default_values.iteritems():
            if getattr(F_TYPE, '_is_value_type', False):
                continue # we can't set it to null
            INSTANCE_DEF, _ = self.INSTANCE._lookup_field(f_name)
            cts_type = self.cts.lltype_to_cts(F_TYPE)
            f_name = self.cts.escape_name(f_name)
            if cts_type != CTS.types.void:
                self.ilasm.opcode('ldarg.0')
                push_constant(self.db, F_TYPE, f_default, self.gen)
                class_name = self.db.class_name(INSTANCE_DEF)
                self.ilasm.set_field((cts_type, class_name, f_name))

        self.ilasm.opcode('ret')
        self.ilasm.end_function()

    def _toString(self):
        if ootype.isSubclass(self.INSTANCE, self.db.genoo.EXCEPTION):
            return # don't override the default ToString, which prints a traceback
        self.ilasm.begin_function('ToString', [], 'string', False, 'virtual', 'instance', 'default')
        self.ilasm.opcode('ldarg.0')
        self.ilasm.call('string class [pypylib]pypy.test.Result::InstanceToPython(object)')
        self.ilasm.ret()
        self.ilasm.end_function()

