// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package graphalg

import "github.com/aclements/go-moremath/graph"

// IDom returns the immediate dominator of each node of g. Nodes that
// don't have an immediate dominator (including root) are assigned -1.
func IDom(g graph.BiGraph, root int) []int {
	// This implements the "engineered algorithm" of Cooper,
	// Harvey, and Kennedy, "A Simple, Fast Dominance Algorithm",
	// 2001.
	//
	// Unlike in Cooper, we mostly use the original node naming,
	// but "intersect" maps into the post-order node naming as
	// needed.

	po := PostOrder(g, root)

	// Compute the post-order node naming for the "intersect"
	// routine. poNum maps from node to post-order name.
	poNum := make([]int, g.NumNodes())
	for i, n := range po {
		poNum[n] = i
	}

	rpo, po := Reverse(po), nil

	// Initialize IDom.
	idom := make([]int, g.NumNodes())
	for i := range idom {
		idom[i] = -1
	}
	idom[root] = root

	// Iterate to convergence.
	changed := true
	for changed {
		changed = false
		for _, b := range rpo {
			if b == root {
				continue
			}

			newIdom := -1
			for _, p := range g.In(b) {
				if idom[p] == -1 {
					continue
				}
				if newIdom == -1 {
					newIdom = p
					continue
				}
				newIdom = intersect(idom, poNum, p, newIdom)
			}

			if idom[b] != newIdom {
				idom[b] = newIdom
				changed = true
			}
		}
	}

	// Clear root's dominator, which is currently a self-loop.
	idom[root] = -1

	return idom
}

func intersect(idom, poNum []int, b1, b2 int) int {
	for b1 != b2 {
		for poNum[b1] < poNum[b2] {
			b1 = idom[b1]
		}
		for poNum[b2] < poNum[b1] {
			b2 = idom[b2]
		}
	}
	return b1
}

// DomFrontier returns the dominance frontier of each node in g. idom
// must be IDom(g, root). idom may be nil, in which case this computes
// IDom.
func DomFrontier(g graph.BiGraph, root int, idom []int) [][]int {
	// This implements the dominance frontier algorithm of Cooper,
	// Harvey, and Kennedy, "A Simple, Fast Dominance Algorithm",
	// 2001.

	if idom == nil {
		idom = IDom(g, root)
	}

	df := make([][]int, g.NumNodes())
	for b, bdom := range idom {
		preds := g.In(b)
		if len(preds) < 2 {
			continue
		}

		for _, pred := range preds {
			runner := pred
			for runner != bdom {
				// Add b to runner's DF set.
				for _, rdf := range df[runner] {
					if rdf == b {
						goto found
					}
				}
				df[runner] = append(df[runner], b)
			found:
				runner = idom[runner]
			}
		}
	}

	// Make sure empty sets are filled in.
	for i := range df {
		if df[i] == nil {
			df[i] = []int{}
		}
	}
	return df
}

// Dom computes the dominator tree from the immediate dominators (as
// computed by IDom). The nodes of the resulting DomTree have the same
// numbering as the nodes in the original graph.
func Dom(idom []int) *DomTree {
	children := make([][]int, len(idom))

	// Chop up a single slice used to store the children.
	cspace := make([]int, len(idom))
	for _, parent := range idom {
		if parent != -1 {
			cspace[parent]++
		}
	}
	used := 0
	for i, n := range cspace {
		children[i] = cspace[used : used : used+n]
		used += n
	}

	// Actually create the children tree now.
	for node, parent := range idom {
		if parent != -1 {
			children[parent] = append(children[parent], node)
		}
	}

	return &DomTree{idom, children}
}

// DomTree is a dominator tree.
//
// It also satisfies the BiGraph interface, which edges pointing
// toward children.
type DomTree struct {
	idom     []int
	children [][]int
}

func (t *DomTree) IDom(n int) int {
	return t.idom[n]
}

func (t *DomTree) NumNodes() int {
	return len(t.idom)
}

func (t *DomTree) In(n int) []int {
	return t.idom[n : n+1]
}

func (t *DomTree) Out(n int) []int {
	return t.children[n]
}
