package graph

import (
	
	
	
)

var ErrTargetNotReachable = errors.New("target vertex not reachable from source")

// CreatesCycle determines whether adding an edge between the two given vertices
// would introduce a cycle in the graph. CreatesCycle will not create an edge.
//
// A potential edge would create a cycle if the target vertex is also a parent
// of the source vertex. In order to determine this, CreatesCycle runs a DFS.
func [ comparable,  any]( Graph[, ], ,  ) (bool, error) {
	if ,  := .Vertex();  != nil {
		return false, fmt.Errorf("could not get vertex with hash %v: %w", , )
	}

	if ,  := .Vertex();  != nil {
		return false, fmt.Errorf("could not get vertex with hash %v: %w", , )
	}

	if  ==  {
		return true, nil
	}

	,  := .PredecessorMap()
	if  != nil {
		return false, fmt.Errorf("failed to get predecessor map: %w", )
	}

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

	 = append(, )

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

		if ,  := []; ! {
			// If the adjacent vertex also is the target vertex, the target is a
			// parent of the source vertex. An edge would introduce a cycle.
			if  ==  {
				return true, nil
			}

			[] = true

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

	return false, nil
}

// ShortestPath computes the shortest path between a source and a target vertex
// under consideration of the edge weights. It returns a slice of hash values of
// the vertices forming that path.
//
// The returned path includes the source and target vertices. If the target is
// not reachable from the source, ErrTargetNotReachable will be returned. Should
// there be multiple shortest paths, and arbitrary one will be returned.
//
// ShortestPath has a time complexity of O(|V|+|E|log(|V|)).
func [ comparable,  any]( Graph[, ], ,  ) ([], error) {
	 := make(map[]float64)
	 := make(map[]bool)

	[] = 0
	[] = true

	 := newPriorityQueue[]()
	,  := .AdjacencyMap()
	if  != nil {
		return nil, fmt.Errorf("could not get adjacency map: %w", )
	}

	for  := range  {
		if  !=  {
			[] = math.Inf(1)
			[] = false
		}

		.Push(, [])
	}

	// bestPredecessors stores the cheapest or least-weighted predecessor for
	// each vertex. Given an edge AC with weight=4 and an edge BC with weight=2,
	// the cheapest predecessor for C is B.
	 := make(map[])

	for .Len() > 0 {
		,  := .Pop()
		 := math.IsInf([], 1)

		for ,  := range [] {
			 := .Properties.Weight

			// Setting the weight to 1 is required for unweighted graphs whose
			// edge weights are 0. Otherwise, all paths would have a sum of 0
			// and a random path would be returned.
			if !.Traits().IsWeighted {
				 = 1
			}

			 := [] + float64()

			if  < [] && ! {
				[] = 
				[] = 
				.UpdatePriority(, )
			}
		}
	}

	 := []{}
	 := 

	for  !=  {
		// If the current vertex is not present in bestPredecessors, current is
		// set to the zero value of K. Without this check, this would lead to an
		// endless prepending of zero values to the path. Also, the target would
		// not be reachable from one of the preceding vertices.
		if ,  := []; ! {
			return nil, ErrTargetNotReachable
		}
		 = []
		 = append([]{}, ...)
	}

	return , nil
}

type sccState[ comparable] struct {
	adjacencyMap map[]map[]Edge[]
	components   [][]
	stack        []
	onStack      map[]bool
	visited      map[]struct{}
	lowlink      map[]int
	index        map[]int
	time         int
}

// StronglyConnectedComponents detects all strongly connected components within
// the graph and returns the hashes of the vertices shaping these components, so
// each component is represented by a []K.
//
// StronglyConnectedComponents can only run on directed graphs.
func [ comparable,  any]( Graph[, ]) ([][], error) {
	if !.Traits().IsDirected {
		return nil, errors.New("SCCs can only be detected in directed graphs")
	}

	,  := .AdjacencyMap()
	if  != nil {
		return nil, fmt.Errorf("could not get adjacency map: %w", )
	}

	 := &sccState[]{
		adjacencyMap: ,
		components:   make([][], 0),
		stack:        make([], 0),
		onStack:      make(map[]bool),
		visited:      make(map[]struct{}),
		lowlink:      make(map[]int),
		index:        make(map[]int),
	}

	for  := range .adjacencyMap {
		if ,  := .visited[]; ! {
			findSCC(, )
		}
	}

	return .components, nil
}

func findSCC[ comparable]( ,  *sccState[]) {
	.stack = append(.stack, )
	.onStack[] = true
	.visited[] = struct{}{}
	.index[] = .time
	.lowlink[] = .time

	.time++

	for  := range .adjacencyMap[] {
		if ,  := .visited[]; ! {
			(, )

			 := math.Min(
				float64(.lowlink[]),
				float64(.lowlink[]),
			)
			.lowlink[] = int()
		} else {
			// If the adjacent vertex already is on the stack, the edge joining
			// the current and the adjacent vertex is a back ege. Therefore, the
			// lowlink value of the vertex has to be updated to the index of the
			// adjacent vertex if it is smaller than the current lowlink value.
			if .onStack[] {
				 := math.Min(
					float64(.lowlink[]),
					float64(.index[]),
				)
				.lowlink[] = int()
			}
		}
	}

	// If the lowlink value of the vertex is equal to its DFS value, this is the
	// head vertex of a strongly connected component that's shaped by the vertex
	// and all vertices on the stack.
	if .lowlink[] == .index[] {
		var  
		var  []

		for  !=  {
			 = .stack[len(.stack)-1]
			.stack = .stack[:len(.stack)-1]
			.onStack[] = false

			 = append(, )
		}

		.components = append(.components, )
	}
}

// AllPathsBetween computes and returns all paths between two given vertices. A
// path is represented as a slice of vertex hashes. The returned slice contains
// these paths.
//
// AllPathsBetween utilizes a non-recursive, stack-based implementation. It has
// an estimated runtime complexity of O(n^2) where n is the number of vertices.
func [ comparable,  any]( Graph[, ], ,  ) ([][], error) {
	,  := .AdjacencyMap()
	if  != nil {
		return nil, 
	}

	// The algorithm used relies on stacks instead of recursion. It is described
	// here: https://boycgit.github.io/all-paths-between-two-vertex/
	 := newStack[]()
	 := newStack[stack[]]()

	 := func() error {
		if .isEmpty() || .isEmpty() {
			return errors.New("empty stack")
		}
		return nil
	}

	 := func( ) {
		.push()

		 := newStack[]()
		for  := range [] {
			var  bool
			.forEach(func( ) {
				if  ==  {
					 = true
				}
			})
			if  {
				continue
			}
			.push()
		}
		.push()
	}

	 := func() error {
		if  = ();  != nil {
			return fmt.Errorf("unable to build stack: %w", )
		}

		,  := .top()

		for !.isEmpty() {
			,  := .pop()
			()
			, _ = .top()
		}

		return nil
	}

	 := func() error {
		if  = ();  != nil {
			return fmt.Errorf("unable to remove layer: %w", )
		}

		if ,  := .top(); !.isEmpty() {
			return errors.New("the top element of vice-stack is not empty")
		}

		_, _ = .pop()
		_, _ = .pop()

		return nil
	}

	()

	 := make([][], 0)

	for !.isEmpty() {
		,  := .top()
		,  := .top()

		if .isEmpty() {
			if  ==  {
				 := make([], 0)
				.forEach(func( ) {
					 = append(, )
				})
				 = append(, )
			}

			 = ()
			if  != nil {
				return nil, 
			}
		} else {
			if  = ();  != nil {
				return nil, 
			}
		}
	}

	return , nil
}