"""Defines optimizations that replace Modelverse data structures by Python data structures."""

import modelverse_jit.cfg_ir as cfg_ir

SET_DEF_REWRITE_RULES = {
    'dict_keys':
    lambda def_def:
    def_def.redefine(
        cfg_ir.DirectFunctionCall(
            cfg_ir.REVERSE_LIST_MACRO_NAME,
            [('seq',
              def_def.insert_before(
                  cfg_ir.DirectFunctionCall(
                      cfg_ir.READ_DICT_KEYS_MACRO_NAME,
                      cfg_ir.get_def_value(def_def).argument_list,
                      cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION,
                      has_side_effects=False)))],
            cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION,
            has_side_effects=False))
}

def __redefine_as_list_len(use_def, def_def):
    use_def.redefine(
        cfg_ir.CreateNode(
            use_def.insert_before(
                cfg_ir.DirectFunctionCall(
                    'len', [('seq', def_def)],
                    cfg_ir.SIMPLE_POSITIONAL_CALLING_CONVENTION,
                    has_side_effects=False))))

SET_USE_REWRITE_RULES = {
    ('set_pop', 'a'):
    lambda use_def, def_def:
    use_def.redefine(
        cfg_ir.DirectFunctionCall(
            'pop', [('self', def_def)],
            cfg_ir.SELF_POSITIONAL_CALLING_CONVENTION)),
    ('list_len', 'a'): __redefine_as_list_len,
    ('read_nr_out', 'a'): __redefine_as_list_len
}

def get_call_def_rewriter(definition, def_rewrite_rules):
    """Gets an appropriate rewrite rule from the given dictionary of call rewrite rules."""
    if cfg_ir.is_call(definition, calling_convention=cfg_ir.JIT_CALLING_CONVENTION):
        call = cfg_ir.get_def_value(definition)
        if call.target_name in def_rewrite_rules:
            return def_rewrite_rules[call.target_name]

    return None

def get_call_use_rewriter(use_definition, def_definition, use_rewrite_rules):
    """Gets an appropriate rewrite rule from the given dictionary of call rewrite rules."""
    if not cfg_ir.is_call(use_definition):
        return None

    call = cfg_ir.get_def_value(use_definition)
    if call.calling_convention not in (
            cfg_ir.JIT_CALLING_CONVENTION,
            cfg_ir.JIT_NO_GC_CALLING_CONVENTION,
            cfg_ir.JIT_CFG_INTRINSIC_CALLING_CONVENTION):
        return None

    for arg_name, arg_def in call.argument_list:
        if arg_def == def_definition:
            key = (call.target_name, arg_name)
            if key in use_rewrite_rules:
                return use_rewrite_rules[key]

    return None

def apply_rewrite_rules(
        entry_point,
        get_def_rewriter,
        get_use_rewriter):
    """Applies the given definition and use rewrite rules to all definitions and uses where a
       rewrite rule can be found for both the definitions and the uses."""
    # pylint: disable=I0011,W0108
    cfg_ir.match_and_rewrite(
        entry_point,
        lambda def_def:
        get_def_rewriter(def_def) is not None,
        lambda use_def, def_def:
        get_use_rewriter(use_def, def_def) is not None,
        lambda def_def:
        get_def_rewriter(def_def)(def_def),
        lambda use_def, def_def:
        get_use_rewriter(use_def, def_def)(use_def, def_def))

def optimize_data_structures(entry_point):
    """Optimizes data structures in the graph defined by the given entry point."""
    apply_rewrite_rules(
        entry_point,
        lambda def_def:
        get_call_def_rewriter(def_def, SET_DEF_REWRITE_RULES),
        lambda use_def, def_def:
        get_call_use_rewriter(use_def, def_def, SET_USE_REWRITE_RULES))
