from rpython.flowspace import model as flowmodel
from rpython.rlib.objectmodel import CDefinedIntSymbolic
from rpython.rtyper.lltypesystem.lltype import Void
from rpython.rtyper.ootypesystem import ootype
from rpython.translator.oosupport.treebuilder import SubOperation
from rpython.translator.oosupport.function import Function as OOFunction, render_sub_op
from rpython.translator.oosupport.constant import push_constant
from rpython.translator.cli.option import getoption
from rpython.translator.cli.cts import CTS
from rpython.translator.cli.opcodes import opcodes
from rpython.translator.cli.metavm import InstructionList, Generator
from rpython.translator.cli.node import Node
from rpython.translator.cli.class_ import Class
from rpython.translator.cli.support import log
from rpython.translator.cli.ilgenerator import CLIBaseGenerator

class Function(OOFunction, Node, CLIBaseGenerator):

    auto_propagate_exceptions = True

    def __init__(self, *args, **kwargs):
        OOFunction.__init__(self, *args, **kwargs)

        if hasattr(self.db.genoo, 'exceptiontransformer'):
            self.auto_propagate_exceptions = False
        
        namespace = getattr(self.graph.func, '_namespace_', None)
        if namespace:
            if '.' in namespace:
                self.namespace, self.classname = namespace.rsplit('.', 1)
            else:
                self.namespace = None
                self.classname = namespace
        else:
            self.namespace = None
            self.classname = None

    def _create_generator(self, ilasm):
        return self # Function implements the Generator interface

    def record_ll_meta_exc(self, ll_meta_exc):
        # record the type only if it doesn't belong to a native_class
        ll_exc = ll_meta_exc._INSTANCE
        NATIVE_INSTANCE = ll_exc._hints.get('NATIVE_INSTANCE', None)
        if NATIVE_INSTANCE is None:
            OOFunction.record_ll_meta_exc(self, ll_meta_exc)

    def _trace_enabled(self):
        return getoption('trace')

    def _trace(self, s, writeline=False):
        self.ilasm.stderr(s, writeline=writeline)

    def _trace_value(self, prompt, v):
        self.ilasm.stderr('  ' + prompt + ': ', writeline=False)
        self.ilasm.load_stderr()
        self.load(v)
        if v.concretetype is not ootype.String:
            from rpython.translator.cli.test.runtest import format_object
            format_object(v.concretetype, self.cts, self.ilasm)
        self.ilasm.write_stderr()

    def begin_render(self):
        self._set_args()
        self._set_locals()
        
        returntype, returnvar = self.cts.llvar_to_cts(self.graph.getreturnvar())
        if self.is_method:
            args = self.args[1:] # self is implicit
            meth_type = 'virtual' # TODO: mark as virtual only when strictly necessary
        else:
            args = self.args
            meth_type = 'static'

        if self.namespace:
            self.ilasm.begin_namespace(self.namespace)
        if self.classname:
            self.ilasm.begin_class(self.classname)
        self.ilasm.begin_function(self.name, args, returntype, self.is_entrypoint, meth_type)
        self.ilasm.locals(self.locals)

    def end_render(self):
        self.ilasm.end_function()
        if self.classname:
            self.ilasm.end_class()
        if self.namespace:
            self.ilasm.end_namespace()

    def set_label(self, label):
        self.ilasm.label(label)

    def render_return_block(self, block):
        return_var = block.inputargs[0]
        if return_var.concretetype is not Void:
            self.load(return_var)
        self.ilasm.opcode('ret')

    def begin_try(self, cond):
        if cond:
            self.ilasm.begin_try()

    def end_try(self, target_label, cond):
        if cond:
            self.ilasm.leave(target_label)
            self.ilasm.end_try()
        else:
            self.ilasm.branch(target_label)

    def begin_catch(self, llexitcase):
        ll_meta_exc = llexitcase
        ll_exc = ll_meta_exc._INSTANCE
        cts_exc = self.cts.lltype_to_cts(ll_exc)
        self.ilasm.begin_catch(cts_exc.classname())

    def end_catch(self, target_label):
        self.ilasm.leave(target_label)
        self.ilasm.end_catch()

    def render_raise_block(self, block):
        exc = block.inputargs[1]
        self.load(exc)
        self.ilasm.opcode('throw')

    def store_exception_and_link(self, link):
        if self._is_raise_block(link.target):
            # the exception value is on the stack, use it as the 2nd target arg
            assert len(link.args) == 2
            assert len(link.target.inputargs) == 2
            self.store(link.target.inputargs[1])
        else:
            # the exception value is on the stack, store it in the proper place
            if isinstance(link.last_exception, flowmodel.Variable):
                self.ilasm.opcode('dup')
                self.store(link.last_exc_value)
                self.ilasm.call_method(
                    'class [mscorlib]System.Type object::GetType()',
                    virtual=True)
                self.store(link.last_exception)
            else:
                self.store(link.last_exc_value)
            self._setup_link(link)

    def _dont_store(self, to_load, to_store):
        # ugly workaround to make the exceptiontransformer work with
        # valuetypes: when exceptiontransforming a function whose result is a
        # .NET valuetype, it tries to store a null into the return variable.
        # Since it is not possible to store a null into a valuetype, and that
        # in that case the value is not used anyway, we simply ignore it.
        from rpython.translator.cli.dotnet import NativeInstance
        if isinstance(to_load, flowmodel.Constant):
            value = to_load.value
            is_null = (not isinstance(value, CDefinedIntSymbolic)) and (not value)
            T = ootype.typeOf(to_load.value)
            if isinstance(T, NativeInstance) and T._is_value_type and is_null:
                return True
        return OOFunction._dont_store(self, to_load, to_store)

    def render_numeric_switch(self, block):
        if block.exitswitch.concretetype in (ootype.SignedLongLong, ootype.UnsignedLongLong):
            # TODO: it could be faster to check is the values fit in
            # 32bit, and perform a cast in that case
            self.render_numeric_switch_naive(block)
            return

        cases, min_case, max_case, default = self._collect_switch_cases(block)
        is_sparse = self._is_sparse_switch(cases, min_case, max_case)

        naive = (min_case < 0) or is_sparse
        if naive:
            self.render_numeric_switch_naive(block)
            return

        targets = []
        for i in xrange(max_case+1):
            link, lbl = cases.get(i, default)
            targets.append(lbl)
        self.generator.load(block.exitswitch)
        self.ilasm.switch(targets)
        self.render_switch_case(*default)
        for link, lbl in cases.itervalues():
            self.render_switch_case(link, lbl)

    def call_oostring(self, ARGTYPE):
        if isinstance(ARGTYPE, ootype.Instance):
            argtype = self.cts.types.object
        else:
            argtype = self.cts.lltype_to_cts(ARGTYPE)
        self.call_signature('string [pypylib]pypy.runtime.Utils::OOString(%s, int32)' % argtype)

    def call_oounicode(self, ARGTYPE):
        argtype = self.cts.lltype_to_cts(ARGTYPE)
        self.call_signature('string [pypylib]pypy.runtime.Utils::OOUnicode(%s)' % argtype)

    # Those parts of the generator interface that are function
    # specific

    def load(self, v):
        if isinstance(v, flowmodel.Variable):
            if v.concretetype is ootype.Void:
                return # ignore it
            if v.name in self.argset:
                selftype, selfname = self.args[0]
                if self.is_method and v.name == selfname:
                    self.ilasm.load_self() # special case for 'self'
                else:
                    self.ilasm.load_arg(v)
            else:
                self.ilasm.load_local(v)
        elif isinstance(v, SubOperation):
            render_sub_op(v, self.db, self.generator)
        else:
            super(Function, self).load(v)

    def store(self, v):
        if isinstance(v, flowmodel.Variable):
            if v.concretetype is not Void:
                if v.name in self.argset:
                    self.ilasm.store_arg(v)
                else:
                    self.ilasm.store_local(v)
        else:
            assert False
