from pypy.rpython.lltypesystem.lloperation import LLOp, LL_OPERATIONS as LL_OPS
from pypy.rpython.ootypesystem import ootype
from pypy.objspace.flow import model as flowmodel

LL_OPERATIONS = {
    'clibox':               LLOp(oo=True, canfold=True),
    'cliunbox':             LLOp(oo=True, canfold=True),
    'cli_newarray':         LLOp(oo=True, canfold=True),
    'cli_getelem':          LLOp(oo=True, sideeffects=False),
    'cli_setelem':          LLOp(oo=True),
    'cli_typeof':           LLOp(oo=True, canfold=True),
    'cli_arraylength':      LLOp(oo=True, canfold=True),
    }
LL_OPERATIONS.update(LL_OPS)

class SubOperation(object):
    def __init__(self, op):
        self.op = op
        self.concretetype = op.result.concretetype

    def __repr__(self):
        return "[%s(%s)]" % (self.op.opname,
                           ", ".join(map(repr, self.op.args)))

def is_mutable(TYPE):
    return isinstance(TYPE, (ootype.Instance,
                             ootype.Record,
                             ootype.List,
                             ootype.Dict,
                             ootype.StringBuilder.__class__,
                             ootype.CustomDict,
                             ootype.DictItemsIterator))

# TODO: analyze graphs to determine which functions calls could have
# side effects and which can be inlined safely.
def can_be_inlined(op):
    try:
        llop = LL_OPERATIONS[op.opname]
        return llop.canfold
    except KeyError:
        return False

def build_op_map(block):
    var_count = {}
    var_to_op = {}
    def inc(v):
        if isinstance(v, flowmodel.Variable):
            var_count[v] = var_count.get(v, 0) + 1

    for i, op in enumerate(block.operations):
        var_to_op[op.result] = i, op
        for v in op.args:
            inc(v)
    if block.exitswitch is not None:
        inc(block.exitswitch)
    for link in block.exits:
        for v in link.args:
            inc(v)
    return var_count, var_to_op

def build_trees_for_block(block):
    var_count, var_to_op = build_op_map(block)
    for op in block.operations:
        for i, v in enumerate(op.args):
            if var_count.get(v, None) == 1 and v not in block.inputargs: # "inline" the operation
                sub_i, sub_op = var_to_op[v]
                if can_be_inlined(sub_op):
                    op.args[i] = SubOperation(sub_op)
                    block.operations[sub_i] = None
    if block.operations != ():
        block.operations = [op for op in block.operations if op is not None]

def build_trees(graph):
    if not getattr(graph, 'tree_built', False):
        for block in graph.iterblocks():
            build_trees_for_block(block)
        graph.tree_built = True

