"""Computes dominator trees for control-flow graphs."""

from collections import defaultdict
import modelverse_jit.cfg_ir as cfg_ir

def sort_postorder(entry_point):
    """Produces a postorder traversal of the graph with the given block as entry point."""
    processed = set()
    results = []
    def __sort_postorder_step(block):
        if block in processed:
            return

        processed.add(block)
        for branch in block.flow.branches():
            __sort_postorder_step(branch.block)

        results.append(block)

    __sort_postorder_step(entry_point)
    return results

class DominatorTree(object):
    """A data structure that represents a dominator tree."""
    def __init__(self, immediate_dominators):
        self.immediate_dominators = immediate_dominators
        self.dominator_sets = {}

    def get_immediate_dominator(self, block):
        """Gets the given block's immediate dominator."""
        return self.immediate_dominators[block]

    def get_dominators(self, block):
        """Gets the given block's set of all dominators."""
        if block in self.dominator_sets:
            return self.dominator_sets
        else:
            idom = self.get_immediate_dominator(block)
            if idom == block:
                results = set([block])
            else:
                results = set(self.get_dominators(idom))
                results.add(block)

            self.dominator_sets[block] = results
            return results

    def dominates_block(self, dominator, dominated):
        """Tests if the first block dominates the second."""
        return dominator in self.get_dominators(dominated)

    def dominates_instruction(self, dominator, dominated):
        """Tests if the first instruction dominates the second."""
        dominator_block = dominator.block
        dominated_block = dominated.block
        if dominator_block == dominated_block:
            return dominator.definition_index < dominated.definition_index
        else:
            return self.dominates_block(dominator_block, dominated_block)

# The algorithms below are based on "A Simple, Fast Dominance Algorithm" by
# Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
# (http://www.cs.rice.edu/~keith/Embed/dom.pdf)

def intersect_immediate_dominators(block1, block2, idoms, postorder_nums):
    """Computes the intersection of the given blocks' immediate dominators."""
    finger1 = block1
    finger2 = block2
    while finger1 != finger2:
        while postorder_nums[finger1] < postorder_nums[finger2]:
            finger1 = idoms[finger1]

        while postorder_nums[finger2] < postorder_nums[finger1]:
            finger2 = idoms[finger2]
    return finger1

def get_immediate_dominators(entry_point):
    """Computes the immediate dominators of the control-flow graph defined by the given entry
       point."""
    predecessor_map = cfg_ir.get_all_predecessor_blocks(entry_point)
    idoms = {}
    postorder_sort = sort_postorder(entry_point)
    postorder_nums = {}
    for i, elem in enumerate(postorder_sort):
        postorder_nums[elem] = i
        idoms[elem] = None

    idoms[entry_point] = entry_point

    changed = True
    while changed:
        changed = False
        for block in reversed(postorder_sort):
            if block == entry_point:
                continue

            new_idom = None
            for pred in predecessor_map[block]:
                assert pred in postorder_nums
                if new_idom is None:
                    new_idom = pred
                elif idoms[pred] is not None:
                    new_idom = intersect_immediate_dominators(pred, new_idom, idoms, postorder_nums)

            if idoms[block] is not new_idom:
                idoms[block] = new_idom
                changed = True

    return idoms

def get_dominator_tree(entry_point):
    """Constructs the dominator tree for the control-flow graph defined by the given entry point."""
    return DominatorTree(get_immediate_dominators(entry_point))
