from rpython.translator.cli.metavm import  Call, CallMethod, \
     IndirectCall, GetField, SetField, DownCast, NewCustomDict,\
     MapException, Box, Unbox, NewArray, GetArrayElem, SetArrayElem,\
     TypeOf, CastPrimitive, EventHandler, GetStaticField, SetStaticField, \
     DebugPrint, UnboxInt
from rpython.translator.oosupport.metavm import PushArg, PushAllArgs, StoreResult, InstructionList,\
    New, RuntimeNew, CastTo, PushPrimitive, OOString, OOUnicode, OONewArray
from rpython.translator.cli.cts import WEAKREF
from rpython.rtyper.ootypesystem import ootype

# some useful instruction patterns
Not = ['ldc.i4.0', 'ceq']
DoNothing = [PushAllArgs]
Ignore = []

def _not(op):
    return [PushAllArgs, op]+Not

def _abs(type_):
    return [PushAllArgs, 'call %s class [mscorlib]System.Math::Abs(%s)' % (type_, type_), StoreResult]

def _check_ovf(op, catch_arithmexic_exception=False):
    mapping = [('[mscorlib]System.OverflowException', 'exceptions.OverflowError')]
    if catch_arithmexic_exception:
        mapping.append(('[mscorlib]System.ArithmeticException', 'exceptions.OverflowError'))
    return [MapException(op, mapping)]

def _check_zer(op):
    mapping = [('[mscorlib]System.DivideByZeroException', 'exceptions.ZeroDivisionError')]
    return [MapException(op, mapping)]

def _check_ovf_zer(op):
    mapping = [('[mscorlib]System.OverflowException', 'exceptions.OverflowError'),
               ('[mscorlib]System.DivideByZeroException', 'exceptions.ZeroDivisionError'),
               ('[mscorlib]System.ArithmeticException', 'exceptions.OverflowError')]
    return [MapException(op, mapping)]

# __________ object oriented & misc operations __________
misc_ops = {
    'new':                      [New],
    'runtimenew':               [RuntimeNew],
    'oosetfield':               [SetField],
    'oogetfield':               [GetField],
    'oosend':                   [CallMethod],
    'ooupcast':                 DoNothing,
    'oodowncast':               [DownCast],
    'cast_to_object':           DoNothing,
    'cast_from_object':         [DownCast],
    'clibox':                   [Box],
    'cliunbox':                 [Unbox],
    'oobox_int':                [Box],
    'oounbox_int':              [UnboxInt],
    'cli_newarray':             [NewArray],
    'cli_getelem':              [GetArrayElem],
    'cli_setelem':              [SetArrayElem],
    'cli_typeof':               [TypeOf],
    'cli_arraylength':          'ldlen',
    'cli_eventhandler':         [EventHandler],
    'cli_getstaticfield':       [GetStaticField],
    'cli_setstaticfield':       [SetStaticField],
    'classof':                  [PushAllArgs, 'callvirt instance class [mscorlib]System.Type object::GetType()'],
    'instanceof':               [CastTo, 'ldnull', 'cgt.un'],
    'subclassof':               [PushAllArgs, 'call bool [pypylib]pypy.runtime.Utils::SubclassOf(class [mscorlib]System.Type, class[mscorlib]System.Type)'],
    'gc_id':                    [PushAllArgs, 'call int32 [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetHashCode(object)'],   # XXX not implemented
    'gc_identityhash':          [PushAllArgs, 'call int32 [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetHashCode(object)'],
    'oostring':                 [OOString],
    'oounicode':                [OOUnicode],
    'ooparse_int':              [PushAllArgs, 'call int32 [pypylib]pypy.runtime.Utils::OOParseInt(string, int32)'],
    'ooparse_float':            [PushAllArgs, 'call float64 [pypylib]pypy.runtime.Utils::OOParseFloat(string)'],
    'oonewcustomdict':          [NewCustomDict],
    'oonewarray':               [OONewArray, StoreResult],
    
    'hint':                     [PushArg(0), StoreResult],
    'direct_call':              [Call],
    'indirect_call':            [IndirectCall],
    'int_between':              [PushAllArgs, 'call bool [pypylib]pypy.runtime.Utils::IntBetween(int32, int32, int32)'],


    'cast_ptr_to_weakadr':      [PushAllArgs, 'newobj instance void class %s::.ctor(object)' % WEAKREF],
    'gc__collect':              'call void class [mscorlib]System.GC::Collect()',
    'gc_set_max_heap_size':     Ignore,
    'debug_assert':             Ignore,
    'debug_start_traceback':    Ignore,
    'debug_record_traceback':   Ignore,
    'debug_catch_exception':    Ignore,
    'debug_reraise_traceback':  Ignore,
    'debug_print_traceback':    Ignore,
    'debug_print':              [DebugPrint],
    'debug_flush':              [PushAllArgs, 'call void [pypylib]pypy.runtime.DebugPrint::DEBUG_FLUSH()'],
    'debug_offset':             [PushAllArgs, 'call int32 [pypylib]pypy.runtime.DebugPrint::DEBUG_OFFSET()'],
    'debug_start':              [PushAllArgs, 'call void [pypylib]pypy.runtime.DebugPrint::DEBUG_START(string)'],
    'debug_stop':               [PushAllArgs, 'call void [pypylib]pypy.runtime.DebugPrint::DEBUG_STOP(string)'],
    'have_debug_prints':        [PushAllArgs, 'call bool [pypylib]pypy.runtime.DebugPrint::HAVE_DEBUG_PRINTS()'],
    'debug_fatalerror':         [PushAllArgs, 'call void [pypylib]pypy.runtime.Debug::DEBUG_FATALERROR(string)'],
    'keepalive':                Ignore,
    'jit_marker':               Ignore,
    'jit_force_quasi_immutable':Ignore,
    'jit_force_virtualizable':  Ignore,
    'jit_force_virtual':        DoNothing,
    'jit_force_quasi_immutable':Ignore,
    'jit_is_virtual':           [PushPrimitive(ootype.Bool, False)],
    'jit_ffi_save_result':      Ignore,
    }

# __________ numeric operations __________

unary_ops = {
    'same_as':                  DoNothing,
    
    'bool_not':                 [PushAllArgs]+Not,

    'int_is_true':              [PushAllArgs, 'ldc.i4.0', 'cgt.un'],
    'int_neg':                  'neg',
    'int_neg_ovf':              _check_ovf(['ldc.i4.0', PushAllArgs, 'sub.ovf', StoreResult]),
    'int_abs':                  _abs('int32'),
    'int_abs_ovf':              _check_ovf(_abs('int32')),
    'int_invert':               'not',

    'uint_is_true':             [PushAllArgs, 'ldc.i4.0', 'cgt.un'],
    'uint_invert':              'not',

    'float_is_true':            [PushAllArgs, 'ldc.r8 0', 'ceq']+Not,
    'float_neg':                'neg',
    'float_abs':                _abs('float64'),

    'llong_is_true':            [PushAllArgs, 'ldc.i8 0', 'cgt.un'],
    'llong_neg':                'neg',
    'llong_neg_ovf':            _check_ovf(['ldc.i8 0', PushAllArgs, 'sub.ovf', StoreResult]),
    'llong_abs':                _abs('int64'),
    'llong_abs_ovf':            _check_ovf(_abs('int64')),
    'llong_invert':             'not',

    'ullong_is_true':            [PushAllArgs, 'ldc.i8 0', 'cgt.un'],
    'ullong_invert':             'not',

    'ooisnull':                 [PushAllArgs, 'ldnull', 'ceq'],
    'oononnull':                [PushAllArgs, 'ldnull', 'ceq']+Not,

    # when casting from bool we want that every truth value is casted
    # to 1: we can't simply DoNothing, because the CLI stack could
    # contains a truth value not equal to 1, so we should use the !=0
    # trick.
    'cast_bool_to_int':         [PushAllArgs, 'ldc.i4.0', 'ceq']+Not,
    'cast_bool_to_uint':        [PushAllArgs, 'ldc.i4.0', 'ceq']+Not,
    'cast_bool_to_float':       [PushAllArgs, 'ldc.i4.0', 'ceq']+Not+['conv.r8'],
    'cast_char_to_int':         DoNothing,
    'cast_unichar_to_int':      DoNothing,
    'cast_int_to_char':         DoNothing,
    'cast_int_to_unichar':      DoNothing,
    'cast_int_to_uint':         DoNothing,
    'cast_int_to_float':        'conv.r8',
    'cast_int_to_longlong':     'conv.i8',
    'cast_uint_to_int':         DoNothing,
    'cast_uint_to_float':       [PushAllArgs, 'conv.u8', 'conv.r8'],
    'cast_float_to_int':        'conv.i4',
    'cast_float_to_uint':       'conv.u4',
    'cast_longlong_to_float':   'conv.r8',
    'cast_float_to_longlong':   'conv.i8',
    'cast_ulonglong_to_float':  'conv.r8',
    'cast_float_to_ulonglong':  'conv.u8',
    'cast_primitive':           [PushAllArgs, CastPrimitive],
    'force_cast':               [PushAllArgs, CastPrimitive],
    'truncate_longlong_to_int': 'conv.i4',
    }

binary_ops = {
    'char_lt':                  'clt',
    'char_le':                  _not('cgt'),
    'char_eq':                  'ceq',
    'char_ne':                  _not('ceq'),
    'char_gt':                  'cgt',
    'char_ge':                  _not('clt'),

    'unichar_eq':               'ceq',
    'unichar_ne':               _not('ceq'),

    'int_add':                  'add',
    'int_sub':                  'sub',
    'int_mul':                  'mul',
    'int_floordiv':             'div',
    'int_floordiv_zer':         _check_zer('div'),
    'int_mod':                  'rem',
    'int_lt':                   'clt',
    'int_le':                   _not('cgt'),
    'int_eq':                   'ceq',
    'int_ne':                   _not('ceq'),
    'int_gt':                   'cgt',
    'int_ge':                   _not('clt'),
    'int_and':                  'and',
    'int_or':                   'or',
    'int_lshift':               'shl',
    'int_rshift':               'shr',
    'int_xor':                  'xor',
    'int_add_ovf':              _check_ovf('add.ovf'),
    'int_add_nonneg_ovf':       _check_ovf('add.ovf'),
    'int_sub_ovf':              _check_ovf('sub.ovf'),
    'int_mul_ovf':              _check_ovf('mul.ovf'),
    'int_floordiv_ovf':         _check_ovf('div', catch_arithmexic_exception=True),
    'int_mod_ovf':              _check_ovf('rem', catch_arithmexic_exception=True),
    'int_lt_ovf':               'clt',
    'int_le_ovf':               _not('cgt'),
    'int_eq_ovf':               'ceq',
    'int_ne_ovf':               _not('ceq'),
    'int_gt_ovf':               'cgt',
    'int_ge_ovf':               _not('clt'),
    'int_and_ovf':              'and',
    'int_or_ovf':               'or',

    'int_lshift_ovf':           _check_ovf([PushArg(0),'conv.i8',PushArg(1), 'shl',
                                            'conv.ovf.i4', StoreResult]),

    'int_rshift_ovf':           'shr', # these can't overflow!
    'int_xor_ovf':              'xor',
    'int_floordiv_ovf_zer':     _check_ovf_zer('div'),
    'int_mod_ovf_zer':          _check_ovf_zer('rem'),
    'int_mod_zer':              _check_zer('rem'),

    'uint_add':                 'add',
    'uint_sub':                 'sub',
    'uint_mul':                 'mul',
    'uint_div':                 'div.un',
    'uint_floordiv':            'div.un',
    'uint_mod':                 'rem.un',
    'uint_lt':                  'clt.un',
    'uint_le':                  _not('cgt.un'),
    'uint_eq':                  'ceq',
    'uint_ne':                  _not('ceq'),
    'uint_gt':                  'cgt.un',
    'uint_ge':                  _not('clt.un'),
    'uint_and':                 'and',
    'uint_or':                  'or',
    'uint_lshift':              'shl',
    'uint_rshift':              'shr.un',
    'uint_xor':                 'xor',

    'float_add':                'add',
    'float_sub':                'sub',
    'float_mul':                'mul',
    'float_truediv':            'div', 
    'float_lt':                 'clt',
    'float_le':                 _not('cgt'),
    'float_eq':                 'ceq',
    'float_ne':                 _not('ceq'),
    'float_gt':                 'cgt',
    'float_ge':                 _not('clt'),

    'llong_add':                'add',
    'llong_sub':                'sub',
    'llong_mul':                'mul',
    'llong_div':                'div',
    'llong_floordiv':           'div',
    'llong_floordiv_zer':       _check_zer('div'),
    'llong_mod':                'rem',
    'llong_mod_zer':            _check_zer('rem'),
    'llong_lt':                 'clt',
    'llong_le':                 _not('cgt'),
    'llong_eq':                 'ceq',
    'llong_ne':                 _not('ceq'),
    'llong_gt':                 'cgt',
    'llong_ge':                 _not('clt'),
    'llong_and':                'and',
    'llong_or':                 'or',
    'llong_lshift':             'shl',
    'llong_rshift':             [PushAllArgs, 'conv.i4', 'shr'],
    'llong_xor':                'xor',

    'ullong_add':               'add',
    'ullong_sub':               'sub',
    'ullong_mul':               'mul',
    'ullong_div':               'div.un',
    'ullong_floordiv':          'div.un',
    'ullong_mod':               'rem.un',
    'ullong_lt':                'clt.un',
    'ullong_le':                _not('cgt.un'),
    'ullong_eq':                'ceq',
    'ullong_ne':                _not('ceq'),
    'ullong_gt':                'cgt.un',
    'ullong_ge':                _not('clt.un'),
    'ullong_lshift':            [PushAllArgs, 'conv.u4', 'shl'],
    'ullong_rshift':            [PushAllArgs, 'conv.i4', 'shr'],
    'ullong_and':               'and',
    'ullong_or':                'or',

    'oois':                     'ceq',
    'ooisnot':                  _not('ceq'),
}

opcodes = misc_ops.copy()
opcodes.update(unary_ops)
opcodes.update(binary_ops)

for key, value in opcodes.iteritems():
    if type(value) is str:
        value = InstructionList([PushAllArgs, value, StoreResult])
    elif value is not None:
        if value is not Ignore and StoreResult not in value and not isinstance(value[0], MapException):
            value.append(StoreResult)
        value = InstructionList(value)

    opcodes[key] = value

