package goraph

// BFS does breadth-first search, and returns the list of vertices.
// (https://en.wikipedia.org/wiki/Breadth-first_search)
//
//	 0. BFS(G, v):
//	 1.
//	 2. 	let Q be a queue
//	 3. 	Q.push(v)
//	 4. 	label v as visited
//	 5.
//	 6. 	while Q is not empty:
//	 7.
//	 8. 		u = Q.dequeue()
//	 9.
//	10. 		for each vertex w adjacent to u:
//	11.
//	12. 			if w is not visited yet:
//	13. 				Q.push(w)
//	14. 				label w as visited
//
func BFS(g Graph, id ID) []ID {
	if g.GetNode(id) == nil {
		return nil
	}

	q := []ID{id}
	visited := make(map[ID]bool)
	visited[id] = true
	rs := []ID{id}

	// while Q is not empty:
	for len(q) != 0 {

		u := q[0]
		q = q[1:len(q):len(q)]

		// for each vertex w adjacent to u:
		cmap, _ := g.GetTargets(u)
		for _, w := range cmap {
			// if w is not visited yet:
			if _, ok := visited[w.ID()]; !ok {
				q = append(q, w.ID())  // Q.push(w)
				visited[w.ID()] = true // label w as visited

				rs = append(rs, w)
			}
		}
		pmap, _ := g.GetSources(u)
		for _, w := range pmap {
			// if w is not visited yet:
			if _, ok := visited[w.ID()]; !ok {
				q = append(q, w.ID())  // Q.push(w)
				visited[w.ID()] = true // label w as visited

				rs = append(rs, w.ID())
			}
		}
	}

	return rs
}

// DFS does depth-first search, and returns the list of vertices.
// (https://en.wikipedia.org/wiki/Depth-first_search)
//
//	 0. DFS(G, v):
//	 1.
//	 2. 	let S be a stack
//	 3. 	S.push(v)
//	 4.
//	 5. 	while S is not empty:
//	 6.
//	 7. 		u = S.pop()
//	 8.
//	 9. 		if u is not visited yet:
//	10.
//	11. 			label u as visited
//	12.
//	13. 			for each vertex w adjacent to u:
//	14.
//	15. 				if w is not visited yet:
//	16. 					S.push(w)
//
func DFS(g Graph, id ID) []ID {
	if g.GetNode(id) == nil {
		return nil
	}

	s := []ID{id}
	visited := make(map[ID]bool)
	rs := []ID{}

	// while S is not empty:
	for len(s) != 0 {

		u := s[len(s)-1]
		s = s[:len(s)-1 : len(s)-1]

		// if u is not visited yet:
		if _, ok := visited[u]; !ok {
			// label u as visited
			visited[u] = true

			rs = append(rs, u)

			// for each vertex w adjacent to u:
			cmap, _ := g.GetTargets(u)
			for _, w := range cmap {
				// if w is not visited yet:
				if _, ok := visited[w.ID()]; !ok {
					s = append(s, w.ID()) // S.push(w)
				}
			}
			pmap, _ := g.GetSources(u)
			for _, w := range pmap {
				// if w is not visited yet:
				if _, ok := visited[w.ID()]; !ok {
					s = append(s, w.ID()) // S.push(w)
				}
			}
		}
	}

	return rs
}

// DFSRecursion does depth-first search recursively.
//
//	 0. DFS(G, v):
//	 1.
//	 2. 	if v is visited:
//	 3. 		return
//	 4.
//	 5. 	label v as visited
//	 6.
//	 7. 	for each vertex u adjacent to v:
//	 8.
//	 9. 		if u is not visited yet:
//	10. 			recursive DFS(G, u)
//
func DFSRecursion(g Graph, id ID) []ID {
	if g.GetNode(id) == nil {
		return nil
	}

	visited := make(map[ID]bool)
	rs := []ID{}

	dfsRecursion(g, id, visited, &rs)

	return rs
}

func dfsRecursion(g Graph, id ID, visited map[ID]bool, rs *[]ID) {
	// base case of recursion
	//
	// if v is visited:
	if _, ok := visited[id]; ok {
		return
	}

	// label v as visited
	visited[id] = true
	*rs = append(*rs, id)

	// for each vertex u adjacent to v:
	cmap, _ := g.GetTargets(id)
	for _, u := range cmap {
		// if u is not visited yet:
		if _, ok := visited[u.ID()]; !ok {
			// recursive DFS(G, u)
			dfsRecursion(g, u.ID(), visited, rs)
		}
	}
	pmap, _ := g.GetSources(id)
	for _, u := range pmap {
		// if u is not visited yet:
		if _, ok := visited[u.ID()]; !ok {
			// recursive DFS(G, u)
			dfsRecursion(g, u.ID(), visited, rs)
		}
	}
}
