// Copyright ©2014 The Gonum 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 topo

import (
	"gonum.org/v1/gonum/graph"
	"gonum.org/v1/gonum/graph/internal/ordered"
	"gonum.org/v1/gonum/graph/traverse"
)

// IsPathIn returns whether path is a path in g.
//
// As special cases, IsPathIn returns true for a zero length path or for
// a path of length 1 when the node in path exists in the graph.
func IsPathIn(g graph.Graph, path []graph.Node) bool {
	switch len(path) {
	case 0:
		return true
	case 1:
		return g.Node(path[0].ID()) != nil
	default:
		var canReach func(uid, vid int64) bool
		switch g := g.(type) {
		case graph.Directed:
			canReach = g.HasEdgeFromTo
		default:
			canReach = g.HasEdgeBetween
		}

		for i, u := range path[:len(path)-1] {
			if !canReach(u.ID(), path[i+1].ID()) {
				return false
			}
		}
		return true
	}
}

// PathExistsIn returns whether there is a path in g starting at 'from' extending
// to 'to'.
//
// PathExistsIn exists as a helper function. If many tests for path existence
// are being performed, other approaches will be more efficient.
func PathExistsIn(g graph.Graph, from, to graph.Node) bool {
	var t traverse.BreadthFirst
	return t.Walk(g, from, func(n graph.Node, _ int) bool { return n.ID() == to.ID() }) != nil
}

// ConnectedComponents returns the connected components of the undirected graph g.
func ConnectedComponents(g graph.Undirected) [][]graph.Node {
	var (
		w  traverse.DepthFirst
		c  []graph.Node
		cc [][]graph.Node
	)
	during := func(n graph.Node) {
		c = append(c, n)
	}
	after := func() {
		cc = append(cc, []graph.Node(nil))
		cc[len(cc)-1] = append(cc[len(cc)-1], c...)
		c = c[:0]
	}
	w.WalkAll(g, nil, after, during)

	return cc
}

// Equal returns whether two graphs are topologically equal. To be
// considered topologically equal, a and b must have identical sets
// of nodes and be identically traversable.
func Equal(a, b graph.Graph) bool {
	aNodes := a.Nodes()
	bNodes := b.Nodes()
	if aNodes.Len() != bNodes.Len() {
		return false
	}

	aNodeSlice := graph.NodesOf(aNodes)
	bNodeSlice := graph.NodesOf(bNodes)
	ordered.ByID(aNodeSlice)
	ordered.ByID(bNodeSlice)
	for i, aU := range aNodeSlice {
		id := aU.ID()
		if id != bNodeSlice[i].ID() {
			return false
		}

		toA := a.From(id)
		toB := b.From(id)
		if toA.Len() != toB.Len() {
			return false
		}

		aAdjacent := graph.NodesOf(toA)
		bAdjacent := graph.NodesOf(toB)
		ordered.ByID(aAdjacent)
		ordered.ByID(bAdjacent)
		for i, aV := range aAdjacent {
			id := aV.ID()
			if id != bAdjacent[i].ID() {
				return false
			}
		}
	}

	return true
}
