from pypy.translator.cli import oopspec
from pypy.rpython.ootypesystem import ootype
from pypy.translator.oosupport.metavm import Generator, InstructionList, MicroInstruction,\
     PushAllArgs, StoreResult, GetField, SetField, DownCast
from pypy.translator.cli.comparer import EqualityComparer
from pypy.translator.cli.cts import WEAKREF
from pypy.translator.cli.dotnet import _static_meth, NativeInstance

STRING_HELPER_CLASS = '[pypylib]pypy.runtime.String'

class _Call(MicroInstruction):
    def render(self, generator, op):
        callee = op.args[0].value
        if isinstance(callee, _static_meth):
            self._render_native_function(generator, callee, op.args)
        elif hasattr(callee, "graph"):
            graph = callee.graph
            method_name = oopspec.get_method_name(graph, op)
            if method_name is None:
                self._render_function(generator, graph, op.args)
            else:
                self._render_method(generator, method_name, op.args[1:])
        else:
            self._render_primitive_function(generator, callee, op)


    def _load_arg_or_null(self, generator, arg):
        if arg.concretetype is ootype.Void:
            if arg.value is None:
                generator.ilasm.opcode('ldnull') # special-case: use None as a null value
            else:
                assert False, "Don't know how to load this arg"
        else:
            generator.load(arg)

    def _render_native_function(self, generator, funcdesc, args):
        for func_arg in args[1:]: # push parameters
            self._load_arg_or_null(generator, func_arg)
        cts = generator.cts
        ret_type = cts.lltype_to_cts(funcdesc._TYPE.RESULT)
        arg_types = [cts.lltype_to_cts(arg) for arg in funcdesc._TYPE.ARGS if arg is not ootype.Void]
        arg_list = ', '.join(arg_types)
        signature = '%s %s::%s(%s)' % (ret_type, funcdesc._cls._name, funcdesc._name, arg_list)
        generator.call_signature(signature)

    def _render_function(self, generator, graph, args):
        primitive = getattr(graph.func, 'suggested_primitive', False)
        for func_arg in args[1:]: # push parameters
            generator.load(func_arg)

        if primitive:
            _, module = graph.func.__module__.rsplit('.', 1)
            func_name = '[pypylib]pypy.builtin.%s::%s' % (module, graph.func.func_name)
            generator.call_graph(graph, func_name)
        else:
            generator.call_graph(graph)

    def _render_method(self, generator, method_name, args):
        this = args[0]
        native = isinstance(this.concretetype, NativeInstance)
        for arg in args: # push parametes
            if native:
                self._load_arg_or_null(generator, arg)
            else:
                generator.load(arg)

        # XXX: very hackish, need refactoring
        if this.concretetype is ootype.String:
            # special case for string: don't use methods, but plain functions
            METH = this.concretetype._METHODS[method_name]
            cts = generator.cts
            ret_type = cts.lltype_to_cts(METH.RESULT)
            arg_types = [cts.lltype_to_cts(arg) for arg in METH.ARGS if arg is not ootype.Void]
            arg_types.insert(0, cts.lltype_to_cts(ootype.String))
            arg_list = ', '.join(arg_types)
            signature = '%s %s::%s(%s)' % (ret_type, STRING_HELPER_CLASS, method_name, arg_list)
            generator.call_signature(signature)
        else:
            generator.call_method(this.concretetype, method_name)
            
            # special case: DictItemsIterator(XXX,
            # Void).ll_current_value needs to return an int32 because
            # we can't use 'void' as a parameter of a Generic. This
            # means that after the call to ll_current_value there will
            # be a value on the stack, and we need to explicitly pop
            # it.
            if isinstance(this.concretetype, ootype.DictItemsIterator) and \
                   ((this.concretetype._VALUETYPE is ootype.Void and \
                     method_name == 'll_current_value') or \
                    (this.concretetype._KEYTYPE is ootype.Void and \
                     method_name == 'll_current_key')):
                generator.ilasm.pop()

    def _render_primitive_function(self, generator, callee, op):
        for func_arg in op.args[1:]: # push parameters
            self._load_arg_or_null(generator, func_arg)
        module, name = callee._name.split(".")
        func_name = '[pypylib]pypy.builtin.%s::%s' % (module, name)
        generator.call_op(op, func_name)

class _CallMethod(_Call):
    def render(self, generator, op):
        method = op.args[0]
        self._render_method(generator, method.value, op.args[1:])


class _IndirectCall(_Call):
    def render(self, generator, op):
        # discard the last argument because it's used only for analysis
        self._render_method(generator, 'Invoke', op.args[:-1])

class _RuntimeNew(MicroInstruction):
    def render(self, generator, op):
        generator.load(op.args[0])
        generator.call_signature('object [pypylib]pypy.runtime.Utils::RuntimeNew(class [mscorlib]System.Type)')
        generator.cast_to(op.result.concretetype)

class _OOString(MicroInstruction):
    def render(self, generator, op):
        ARGTYPE = op.args[0].concretetype
        if isinstance(ARGTYPE, ootype.Instance):
            argtype = 'object'
        else:
            argtype = generator.cts.lltype_to_cts(ARGTYPE)
        generator.load(op.args[0])
        generator.load(op.args[1])
        generator.call_signature('string [pypylib]pypy.runtime.Utils::OOString(%s, int32)' % argtype)

class _NewCustomDict(MicroInstruction):
    def render(self, generator, op):
        DICT = op.args[0].value
        comparer = EqualityComparer(generator.db, DICT._KEYTYPE,
                                    (op.args[1], op.args[2], op.args[3]),
                                    (op.args[4], op.args[5], op.args[6]))
        generator.db.pending_node(comparer)
        dict_type = generator.cts.lltype_to_cts(DICT)

        generator.ilasm.new(comparer.get_ctor())
        generator.ilasm.new('instance void %s::.ctor(class'
                            '[mscorlib]System.Collections.Generic.IEqualityComparer`1<!0>)'
                            % dict_type)

class _CastWeakAdrToPtr(MicroInstruction):
    def render(self, generator, op):
        RESULTTYPE = op.result.concretetype
        resulttype = generator.cts.lltype_to_cts(RESULTTYPE)
        generator.load(op.args[0])
        generator.ilasm.call_method('object class %s::get_Target()' % WEAKREF, True)
        generator.ilasm.opcode('castclass', resulttype)

class MapException(MicroInstruction):
    COUNT = 0
    
    def __init__(self, instr, mapping):
        if isinstance(instr, str):
            self.instr = InstructionList([PushAllArgs, instr, StoreResult])
        else:
            self.instr = InstructionList(instr)
        self.mapping = mapping

    def render(self, generator, op):
        from pypy.translator.cli.function import LastExceptionHandler
        if isinstance(generator, LastExceptionHandler):
            self.render_last(generator, op)
        else:
            self.render_native(generator, op)

    def render_native(self, generator, op):
        ilasm = generator.ilasm
        label = '__check_block_%d' % MapException.COUNT
        MapException.COUNT += 1
        ilasm.begin_try()
        self.instr.render(generator, op)
        ilasm.leave(label)
        ilasm.end_try()
        for cli_exc, py_exc in self.mapping:
            ilasm.begin_catch(cli_exc)
            ilasm.new('instance void class %s::.ctor()' % py_exc)
            ilasm.opcode('throw')
            ilasm.end_catch()
        ilasm.label(label)
        ilasm.opcode('nop')

    def render_last(self, generator, op):
        ilasm = generator.ilasm
        stdflow = '__check_block_%d' % MapException.COUNT
        MapException.COUNT += 1
        premature_return = '__check_block_%d' % MapException.COUNT
        MapException.COUNT += 1
        ilasm.begin_try()
        self.instr.render(generator, op)
        ilasm.leave(stdflow)
        ilasm.end_try()
        for cli_exc, py_exc in self.mapping:
            ilasm.begin_catch(cli_exc)
            ilasm.new('instance void class %s::.ctor()' % py_exc)
            ilasm.opcode('stsfld', 'object last_exception')
            ilasm.leave(stdflow)
            ilasm.end_catch()
        ilasm.label(stdflow)
        ilasm.opcode('nop')

class _Box(MicroInstruction): 
    def render(self, generator, op):
        generator.load(op.args[0])
        TYPE = op.args[0].concretetype
        boxtype = generator.cts.lltype_to_cts(TYPE)
        generator.ilasm.opcode('box', boxtype)

class _Unbox(MicroInstruction):
    def render(self, generator, op):
        v_obj, v_type = op.args
        assert v_type.concretetype is ootype.Void
        TYPE = v_type.value
        boxtype = generator.cts.lltype_to_cts(TYPE)
        generator.load(v_obj)
        generator.ilasm.opcode('unbox.any', boxtype)

class _NewArray(MicroInstruction):
    def render(self, generator, op):
        v_type, v_length = op.args
        assert v_type.concretetype is ootype.Void
        TYPE = v_type.value._INSTANCE
        typetok = generator.cts.lltype_to_cts(TYPE)
        generator.load(v_length)
        generator.ilasm.opcode('newarr', typetok)

class _GetArrayElem(MicroInstruction):
    def render(self, generator, op):
        generator.load(op.args[0])
        generator.load(op.args[1])
        rettype = generator.cts.lltype_to_cts(op.result.concretetype)
        generator.ilasm.opcode('ldelem', rettype)

class _SetArrayElem(MicroInstruction):
    def render(self, generator, op):
        v_array, v_index, v_elem = op.args
        generator.load(v_array)
        generator.load(v_index)
        if v_elem.concretetype is ootype.Void and v_elem.value is None:
            generator.ilasm.opcode('ldnull')
        else:
            generator.load(v_elem)
        elemtype = generator.cts.lltype_to_cts(v_array.concretetype)
        generator.ilasm.opcode('stelem', elemtype)

class _TypeOf(MicroInstruction):
    def render(self, generator, op):
        v_type, = op.args
        assert v_type.concretetype is ootype.Void
        cliClass = v_type.value
        fullname = cliClass._INSTANCE._name
        generator.ilasm.opcode('ldtoken', fullname)
        generator.ilasm.call('class [mscorlib]System.Type class [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)')


Call = _Call()
CallMethod = _CallMethod()
IndirectCall = _IndirectCall()
RuntimeNew = _RuntimeNew()
OOString = _OOString()
NewCustomDict = _NewCustomDict()
CastWeakAdrToPtr = _CastWeakAdrToPtr()
Box = _Box()
Unbox = _Unbox()
NewArray = _NewArray()
GetArrayElem = _GetArrayElem()
SetArrayElem = _SetArrayElem()
TypeOf = _TypeOf()
