from rpython.flowspace.model import Variable, Constant
from rpython.translator.backendopt import graphanalyze

top_set = object()
empty_set = frozenset()

CUTOFF = 1000

class WriteAnalyzer(graphanalyze.GraphAnalyzer):
    def bottom_result(self):
        return empty_set

    def top_result(self):
        return top_set

    def is_top_result(self, result):
        return result is top_set

    def result_builder(self):
        return set()

    def add_to_result(self, result, other):
        if other is top_set:
            return top_set
        if len(other) + len(result) > CUTOFF:
            return top_set
        result.update(other)
        return result

    def finalize_builder(self, result):
        if result is top_set:
            return result
        return frozenset(result)

    def join_two_results(self, result1, result2):
        if result1 is top_set or result2 is top_set:
            return top_set
        return result1.union(result2)

    def _getinteriorname(self, op):
        if (isinstance(op.args[1], Constant) and
            isinstance(op.args[1].value, str)):
            return op.args[1].value
        return op.args[2].value

    def analyze_simple_operation(self, op, graphinfo):
        if op.opname == "setfield":
            if graphinfo is None or not graphinfo.is_fresh_malloc(op.args[0]):
                return frozenset([
                    ("struct", op.args[0].concretetype, op.args[1].value)])
        elif op.opname == "setarrayitem":
            if graphinfo is None or not graphinfo.is_fresh_malloc(op.args[0]):
                return self._array_result(op.args[0].concretetype)
        elif op.opname == "setinteriorfield":
            if graphinfo is None or not graphinfo.is_fresh_malloc(op.args[0]):
                name = self._getinteriorname(op)
                return self._interiorfield_result(op.args[0].concretetype, name)
        return empty_set

    def _array_result(self, TYPE):
        return frozenset([("array", TYPE)])

    def _interiorfield_result(self, TYPE, fieldname):
        return frozenset([("interiorfield", TYPE, fieldname)])

    def compute_graph_info(self, graph):
        return FreshMallocs(graph)


class FreshMallocs(object):
    def __init__(self, graph):
        self.nonfresh = set(graph.getargs())
        pendingblocks = list(graph.iterblocks())
        self.allvariables = set()
        for block in pendingblocks:
            self.allvariables.update(block.inputargs)
        pendingblocks.reverse()
        while pendingblocks:
            block = pendingblocks.pop()
            for op in block.operations:
                self.allvariables.add(op.result)
                if (op.opname == 'malloc' or op.opname == 'malloc_varsize'
                    or op.opname == 'new'):
                    continue
                elif op.opname in ('cast_pointer', 'same_as'):
                    if self.is_fresh_malloc(op.args[0]):
                        continue
                self.nonfresh.add(op.result)
            for link in block.exits:
                self.nonfresh.update(link.getextravars())
                self.allvariables.update(link.getextravars())
                prevlen = len(self.nonfresh)
                for v1, v2 in zip(link.args, link.target.inputargs):
                    if not self.is_fresh_malloc(v1):
                        self.nonfresh.add(v2)
                if len(self.nonfresh) > prevlen:
                    pendingblocks.append(link.target)

    def is_fresh_malloc(self, v):
        if not isinstance(v, Variable):
            return False
        assert v in self.allvariables
        return v not in self.nonfresh


class ReadWriteAnalyzer(WriteAnalyzer):

    def analyze_simple_operation(self, op, graphinfo):
        if op.opname == "getfield":
            return frozenset([
                ("readstruct", op.args[0].concretetype, op.args[1].value)])
        elif op.opname == "getarrayitem":
            return frozenset([
                ("readarray", op.args[0].concretetype)])
        elif op.opname == "getinteriorfield":
            name = self._getinteriorname(op)
            return frozenset([("readinteriorfield", op.args[0].concretetype,
                            name)])
        return WriteAnalyzer.analyze_simple_operation(self, op, graphinfo)
