package graph

import 

// DFS performs a depth-first search on the graph, starting from the given vertex. The visit
// function will be invoked with the hash of the vertex currently visited. If it returns false, DFS
// will continue traversing the graph, and if it returns true, the traversal will be stopped. In
// case the graph is disconnected, only the vertices joined with the starting vertex are visited.
//
// This example prints all vertices of the graph in DFS-order:
//
//	g := graph.New(graph.IntHash)
//
//	_ = g.AddVertex(1)
//	_ = g.AddVertex(2)
//	_ = g.AddVertex(3)
//
//	_ = g.AddEdge(1, 2)
//	_ = g.AddEdge(2, 3)
//	_ = g.AddEdge(3, 1)
//
//	_ = graph.DFS(g, 1, func(value int) bool {
//		fmt.Println(value)
//		return false
//	})
//
// Similarly, if you have a graph of City vertices and the traversal should stop at London, the
// visit function would look as follows:
//
//	func(c City) bool {
//		return c.Name == "London"
//	}
//
// DFS is non-recursive and maintains a stack instead.
func [ comparable,  any]( Graph[, ],  ,  func() bool) error {
	,  := .AdjacencyMap()
	if  != nil {
		return fmt.Errorf("could not get adjacency map: %w", )
	}

	if ,  := []; ! {
		return fmt.Errorf("could not find start vertex with hash %v", )
	}

	 := make([], 0)
	 := make(map[]bool)

	 = append(, )

	for len() > 0 {
		 := [len()-1]

		 = [:len()-1]

		if ,  := []; ! {
			// Stop traversing the graph if the visit function returns true.
			if  := ();  {
				break
			}
			[] = true

			for  := range [] {
				 = append(, )
			}
		}
	}

	return nil
}

// BFS performs a breadth-first search on the graph, starting from the given vertex. The visit
// function will be invoked with the hash of the vertex currently visited. If it returns false, BFS
// will continue traversing the graph, and if it returns true, the traversal will be stopped. In
// case the graph is disconnected, only the vertices joined with the starting vertex are visited.
//
// This example prints all vertices of the graph in BFS-order:
//
//	g := graph.New(graph.IntHash)
//
//	_ = g.AddVertex(1)
//	_ = g.AddVertex(2)
//	_ = g.AddVertex(3)
//
//	_ = g.AddEdge(1, 2)
//	_ = g.AddEdge(2, 3)
//	_ = g.AddEdge(3, 1)
//
//	_ = graph.BFS(g, 1, func(value int) bool {
//		fmt.Println(value)
//		return false
//	})
//
// Similarly, if you have a graph of City vertices and the traversal should stop at London, the
// visit function would look as follows:
//
//	func(c City) bool {
//		return c.Name == "London"
//	}
//
// BFS is non-recursive and maintains a stack instead.
func [ comparable,  any]( Graph[, ],  ,  func() bool) error {
	 := func( ,  int) bool {
		return ()
	}
	return BFSWithDepth(, , )
}

// BFSWithDepth works just as BFS and performs a breadth-first search on the graph, but its
// visit function is passed the current depth level as a second argument. Consequently, the
// current depth can be used for deciding whether or not to proceed past a certain depth.
//
//	_ = graph.BFSWithDepth(g, 1, func(value int, depth int) bool {
//		fmt.Println(value)
//		return depth > 3
//	})
//
// With the visit function from the example, the BFS traversal will stop once a depth greater
// than 3 is reached.
func [ comparable,  any]( Graph[, ],  ,  func(, int) bool) error {
	,  := .AdjacencyMap()
	if  != nil {
		return fmt.Errorf("could not get adjacency map: %w", )
	}

	if ,  := []; ! {
		return fmt.Errorf("could not find start vertex with hash %v", )
	}

	 := make([], 0)
	 := make(map[]bool)

	[] = true
	 = append(, )
	 := 0

	for len() > 0 {
		 := [0]

		 = [1:]
		++

		// Stop traversing the graph if the visit function returns true.
		if  := (, );  {
			break
		}

		for  := range [] {
			if ,  := []; ! {
				[] = true
				 = append(, )
			}
		}

	}

	return nil
}