// Mgmt
// Copyright (C) James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.

//go:build !root

package pgraph

import (
	"reflect"
	"testing"
)

func TestCount1(t *testing.T) {
	g := &Graph{}

	if i := g.NumVertices(); i != 0 {
		t.Errorf("should have 0 vertices instead of: %d", i)
	}

	if i := g.NumEdges(); i != 0 {
		t.Errorf("should have 0 edges instead of: %d", i)
	}

	v1 := NV("v1")
	v2 := NV("v2")
	e1 := NE("e1")
	g.AddEdge(v1, v2, e1)

	if i := g.NumVertices(); i != 2 {
		t.Errorf("should have 2 vertices instead of: %d", i)
	}

	if i := g.NumEdges(); i != 1 {
		t.Errorf("should have 1 edges instead of: %d", i)
	}
}

func TestAddVertex1(t *testing.T) {
	g := &Graph{Name: "g2"}
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	//e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v1, e3)

	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)

	if i := g.NumVertices(); i != 6 {
		t.Errorf("should have 6 vertices instead of: %d", i)
	}
}

func TestAddEdge1(t *testing.T) {
	g, _ := NewGraph("g")

	v1 := NV("v1")
	v2 := NV("v2")

	g.AddVertex(v1)
	g.AddVertex(v2)

	if i := g.NumEdges(); i != 0 {
		t.Errorf("should have 0 edges but got %d", i)
	}

	e1 := NE("e1")
	g.AddEdge(v1, v2, e1)

	if i := g.NumEdges(); i != 1 {
		t.Errorf("should have 1 edge but got %d", i)
	}
}

func TestDFS1(t *testing.T) {
	g, _ := NewGraph("g3")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	//e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v1, e3)

	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)
	//g.AddEdge(v6, v4, e6)
	out1 := g.DFS(v1)
	if i := len(out1); i != 3 {
		t.Errorf("should have 3 vertices instead of: %d", i)
		t.Errorf("found: %v", out1)
		for _, v := range out1 {
			t.Errorf("value: %s", v)
		}
	}

	out2 := g.DFS(v4)
	if i := len(out2); i != 3 {
		t.Errorf("should have 3 vertices instead of: %d", i)
		t.Errorf("found: %v", out1)
		for _, v := range out1 {
			t.Errorf("value: %s", v)
		}
	}
}

func TestDFS2(t *testing.T) {
	g, _ := NewGraph("g4")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v1, e3)

	out := g.DFS(v1)
	if i := len(out); i != 3 {
		t.Errorf("should have 3 vertices instead of: %d", i)
		t.Errorf("found: %v", out)
		for _, v := range out {
			t.Errorf("value: %s", v)
		}
	}
}

func TestFilterGraph1(t *testing.T) {
	g, _ := NewGraph("g5")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	//e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v1, e3)

	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)
	//g.AddEdge(v6, v4, e6)

	save := []Vertex{v1, v2, v3}
	out, err := g.FilterGraph(save)
	if err != nil {
		t.Errorf("failed with: %v", err)
	}

	if i := out.NumVertices(); i != 3 {
		t.Errorf("should have 3 vertices instead of: %d", i)
	}
}

func TestFilterGraph2(t *testing.T) {
	g, _ := NewGraph("g5")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	e1 := NE("e1")
	g.AddEdge(v1, v2, e1)
	g.AddVertex(v3)
	g.AddVertex(v4)

	save := []Vertex{v1, v2, v3}
	out, err := g.FilterGraph(save)
	if err != nil {
		t.Errorf("failed with: %v", err)
	}

	if c, i := len(save), out.NumVertices(); c != i {
		t.Errorf("should have %d vertices instead of: %d", c, i)
	}
}

func TestFilterGraph3(t *testing.T) {
	g, _ := NewGraph("g5")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	e1 := NE("e1")
	e2 := NE("e2")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v3, v4, e2)
	g.AddVertex(v5)

	save := []Vertex{v1, v2, v3}
	out, err := g.FilterGraph(save)
	if err != nil {
		t.Errorf("failed with: %v", err)
	}

	if c, i := len(save), out.NumVertices(); c != i {
		t.Errorf("should have %d vertices instead of: %d", c, i)
	}
	if c, i := 1, out.NumEdges(); c != i {
		t.Errorf("should have %d edges instead of: %d", c, i)
	}
}

func TestDisconnectedGraphs1(t *testing.T) {
	g, _ := NewGraph("g6")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	//e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v1, e3)

	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)
	//g.AddEdge(v6, v4, e6)

	graphs, err := g.DisconnectedGraphs()
	if err != nil {
		t.Errorf("failed with: %v", err)
	}

	if i := len(graphs); i != 2 {
		t.Errorf("should have 2 graphs instead of: %d", i)
	}
}

func TestDeleteVertex1(t *testing.T) {
	g, _ := NewGraph("g7")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v1, e3)

	if i := g.NumVertices(); i != 3 {
		t.Errorf("should have 3 vertices instead of: %d", i)
	}

	g.DeleteVertex(v2)

	if i := g.NumVertices(); i != 2 {
		t.Errorf("should have 2 vertices instead of: %d", i)
	}

	g.DeleteVertex(v1)

	if i := g.NumVertices(); i != 1 {
		t.Errorf("should have 1 vertices instead of: %d", i)
	}

	g.DeleteVertex(v3)

	if i := g.NumVertices(); i != 0 {
		t.Errorf("should have 0 vertices instead of: %d", i)
	}

	g.DeleteVertex(v2) // duplicate deletes don't error...

	if i := g.NumVertices(); i != 0 {
		t.Errorf("should have 0 vertices instead of: %d", i)
	}
}

func TestDeleteVertex2(t *testing.T) {
	g := &Graph{}
	v1 := NV("v1")
	g.DeleteVertex(v1) // check this doesn't panic

	if i := g.NumVertices(); i != 0 {
		t.Errorf("should have 0 vertices instead of: %d", i)
	}
}

func TestVertexContains1(t *testing.T) {
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	if VertexContains(v1, []Vertex{v1, v2, v3}) != true {
		t.Errorf("should be true instead of false.")
	}

	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	if VertexContains(v4, []Vertex{v5, v6}) != false {
		t.Errorf("should be false instead of true.")
	}

	v7 := NV("v7")
	v8 := NV("v8")
	v9 := NV("v9")
	if VertexContains(v8, []Vertex{v7, v8, v9}) != true {
		t.Errorf("should be true instead of false.")
	}

	v1b := NV("v1") // same value, different objects
	if VertexContains(v1b, []Vertex{v1, v2, v3}) != false {
		t.Errorf("should be false instead of true.")
	}
}

func TestTopoSort1(t *testing.T) {
	g, _ := NewGraph("g9")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v1, v3, e2)
	g.AddEdge(v2, v4, e3)
	g.AddEdge(v3, v4, e4)

	g.AddEdge(v4, v5, e5)
	g.AddEdge(v5, v6, e6)

	indegree := g.InDegree() // map[Vertex]int
	if i := indegree[v1]; i != 0 {
		t.Errorf("indegree of v1 should be 0 instead of: %d", i)
	}
	if i := indegree[v2]; i != 1 {
		t.Errorf("indegree of v2 should be 1 instead of: %d", i)
	}
	if i := indegree[v3]; i != 1 {
		t.Errorf("indegree of v3 should be 1 instead of: %d", i)
	}
	if i := indegree[v4]; i != 2 {
		t.Errorf("indegree of v4 should be 2 instead of: %d", i)
	}
	if i := indegree[v5]; i != 1 {
		t.Errorf("indegree of v5 should be 1 instead of: %d", i)
	}
	if i := indegree[v6]; i != 1 {
		t.Errorf("indegree of v6 should be 1 instead of: %d", i)
	}

	outdegree := g.OutDegree() // map[Vertex]int
	if i := outdegree[v1]; i != 2 {
		t.Errorf("outdegree of v1 should be 2 instead of: %d", i)
	}
	if i := outdegree[v2]; i != 1 {
		t.Errorf("outdegree of v2 should be 1 instead of: %d", i)
	}
	if i := outdegree[v3]; i != 1 {
		t.Errorf("outdegree of v3 should be 1 instead of: %d", i)
	}
	if i := outdegree[v4]; i != 1 {
		t.Errorf("outdegree of v4 should be 1 instead of: %d", i)
	}
	if i := outdegree[v5]; i != 1 {
		t.Errorf("outdegree of v5 should be 1 instead of: %d", i)
	}
	if i := outdegree[v6]; i != 0 {
		t.Errorf("outdegree of v6 should be 0 instead of: %d", i)
	}

	s, err := g.TopologicalSort()
	// either possibility is a valid toposort
	match := reflect.DeepEqual(s, []Vertex{v1, v2, v3, v4, v5, v6}) || reflect.DeepEqual(s, []Vertex{v1, v3, v2, v4, v5, v6})
	if err != nil || !match {
		t.Errorf("topological sort failed, error: %v", err)
		str := "Found:"
		for _, v := range s {
			str += " " + v.String()
		}
		t.Error(str)
	}
}

func TestTopoSort2(t *testing.T) {
	g, _ := NewGraph("g10")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v4, e3)
	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)
	g.AddEdge(v4, v2, e6) // cycle

	if _, err := g.TopologicalSort(); err == nil {
		t.Errorf("topological sort passed, but graph is cyclic")
	}
}

func TestTopoSort3(t *testing.T) {
	g, _ := NewGraph("g11")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v4, e3)
	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)
	g.AddEdge(v4, v2, e6) // cycle

	//g.ExecGraphviz(context.TODO(), "/tmp/g.dot")

	_, err := g.TopologicalSort()
	if err == nil {
		t.Errorf("topological sort passed, but graph is cyclic")
		return
	}
	errNotAcyclic, ok := err.(*ErrNotAcyclic)
	if !ok {
		t.Errorf("wrong kind of error, got: %v", err)
		return
	}
	cycle := errNotAcyclic.Cycle

	t.Logf("cycle: %v", cycle)
	if len(cycle) < 2 {
		t.Errorf("cycle is too short")
	}
	cycle1 := []Vertex{v2, v3, v4, v2}
	cycle2 := []Vertex{v3, v4, v2, v3}
	cycle3 := []Vertex{v4, v2, v3, v4}
	b1 := reflect.DeepEqual(cycle, cycle1)
	b2 := reflect.DeepEqual(cycle, cycle2)
	b3 := reflect.DeepEqual(cycle, cycle3)
	if !b1 && !b2 && !b3 {
		t.Errorf("cycle didn't match")
	}
}

// empty
func TestReachability0(t *testing.T) {
	{
		g, _ := NewGraph("g")
		result, err := g.Reachability(nil, nil)
		if err != nil {
			t.Logf("reachability failed: %+v", err)
			if result != nil {
				str := "Got:"
				for _, v := range result {
					str += " " + v.String()
				}
				t.Error(str)
			}
		}
	}
	{
		g, _ := NewGraph("g")
		v1 := NV("v1")
		v6 := NV("v6")

		result, err := g.Reachability(v1, v6)
		if err != nil {
			t.Logf("reachability failed: %+v", err)
			return
		}
		expected := []Vertex{}

		if !reflect.DeepEqual(result, expected) {
			t.Logf("reachability failed")
			str := "Got:"
			for _, v := range result {
				str += " " + v.String()
			}
			t.Error(str)
		}
	}
	{
		g, _ := NewGraph("g")
		v1 := NV("v1")
		v2 := NV("v2")
		v3 := NV("v3")
		v4 := NV("v4")
		v5 := NV("v5")
		v6 := NV("v6")
		e1 := NE("e1")
		e2 := NE("e2")
		e3 := NE("e3")
		e4 := NE("e4")
		e5 := NE("e5")
		g.AddEdge(v1, v2, e1)
		g.AddEdge(v2, v3, e2)
		g.AddEdge(v1, v4, e3)
		g.AddEdge(v3, v4, e4)
		g.AddEdge(v3, v5, e5)

		result, err := g.Reachability(v1, v6)
		if err != nil {
			t.Logf("reachability failed: %+v", err)
			return
		}
		expected := []Vertex{}

		if !reflect.DeepEqual(result, expected) {
			t.Logf("reachability failed")
			str := "Got:"
			for _, v := range result {
				str += " " + v.String()
			}
			t.Error(str)
		}
	}
}

// simple linear path
func TestReachability1(t *testing.T) {
	g, _ := NewGraph("g")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	//e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v4, e3)
	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)

	result, err := g.Reachability(v1, v6)
	if err != nil {
		t.Logf("reachability failed: %+v", err)
		return
	}
	expected := []Vertex{v1, v2, v3, v4, v5, v6}

	if !reflect.DeepEqual(result, expected) {
		t.Logf("reachability failed")
		str := "Got:"
		for _, v := range result {
			str += " " + v.String()
		}
		t.Error(str)
	}
}

// pick one of two correct paths
func TestReachability2(t *testing.T) {
	g, _ := NewGraph("g")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v1, v3, e2)
	g.AddEdge(v2, v4, e3)
	g.AddEdge(v3, v4, e4)
	g.AddEdge(v4, v5, e5)
	g.AddEdge(v5, v6, e6)

	result, err := g.Reachability(v1, v6)
	if err != nil {
		t.Logf("reachability failed: %+v", err)
		return
	}
	expected1 := []Vertex{v1, v2, v4, v5, v6}
	expected2 := []Vertex{v1, v3, v4, v5, v6}

	// !xor test
	if reflect.DeepEqual(result, expected1) == reflect.DeepEqual(result, expected2) {
		t.Logf("reachability failed")
		str := "Got:"
		for _, v := range result {
			str += " " + v.String()
		}
		t.Error(str)
	}
}

// pick shortest path
func TestReachability3(t *testing.T) {
	g, _ := NewGraph("g")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v4, e3)
	g.AddEdge(v4, v5, e4)
	g.AddEdge(v1, v5, e5)
	g.AddEdge(v5, v6, e6)

	result, err := g.Reachability(v1, v6)
	if err != nil {
		t.Logf("reachability failed: %+v", err)
		return
	}
	expected := []Vertex{v1, v5, v6}

	if !reflect.DeepEqual(result, expected) {
		t.Logf("reachability failed")
		str := "Got:"
		for _, v := range result {
			str += " " + v.String()
		}
		t.Error(str)
	}
}

// direct path
func TestReachability4(t *testing.T) {
	g, _ := NewGraph("g")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v4, e3)
	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)
	g.AddEdge(v1, v6, e6)

	result, err := g.Reachability(v1, v6)
	if err != nil {
		t.Logf("reachability failed: %+v", err)
		return
	}
	expected := []Vertex{v1, v6}

	if !reflect.DeepEqual(result, expected) {
		t.Logf("reachability failed")
		str := "Got:"
		for _, v := range result {
			str += " " + v.String()
		}
		t.Error(str)
	}
}

func TestHasPath1(t *testing.T) {
	g, _ := NewGraph("g")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	e1 := NE("e1")
	e2 := NE("e2")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddVertex(v4)

	if !g.HasPath(v1, v3) {
		t.Errorf("expected path from v1 to v3")
	}
	if g.HasPath(v3, v1) {
		t.Errorf("did not expect path from v3 to v1")
	}
	if g.HasPath(v1, v4) {
		t.Errorf("did not expect path from v1 to v4")
	}
}

func TestReverse1(t *testing.T) {
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")

	if rev := Reverse([]Vertex{}); !reflect.DeepEqual(rev, []Vertex{}) {
		t.Errorf("reverse of vertex slice failed (empty)")
	}

	if rev := Reverse([]Vertex{v1}); !reflect.DeepEqual(rev, []Vertex{v1}) {
		t.Errorf("reverse of vertex slice failed (single)")
	}

	if rev := Reverse([]Vertex{v1, v2, v3, v4, v5, v6}); !reflect.DeepEqual(rev, []Vertex{v6, v5, v4, v3, v2, v1}) {
		t.Errorf("reverse of vertex slice failed (1..6)")
	}

	if rev := Reverse([]Vertex{v6, v5, v4, v3, v2, v1}); !reflect.DeepEqual(rev, []Vertex{v1, v2, v3, v4, v5, v6}) {
		t.Errorf("reverse of vertex slice failed (6..1)")
	}
}

func TestCopy1(t *testing.T) {
	g1 := &Graph{}
	g2 := g1.Copy() // check this doesn't panic
	if !reflect.DeepEqual(g1.String(), g2.String()) {
		t.Errorf("graph copy failed")
	}
}

func TestGraphCmp1(t *testing.T) {
	g1 := &Graph{}
	g2 := &Graph{}
	g3 := &Graph{}
	g3.AddVertex(NV("v1"))
	g4 := &Graph{}
	g4.AddVertex(NV("v2"))

	if err := g1.GraphCmp(g2, strVertexCmpFn, strEdgeCmpFn); err != nil {
		t.Errorf("should have no error during GraphCmp, but got: %v", err)
	}

	if err := g1.GraphCmp(g3, strVertexCmpFn, strEdgeCmpFn); err == nil {
		t.Errorf("should have error during GraphCmp, but got nil")
	}

	if err := g3.GraphCmp(g4, strVertexCmpFn, strEdgeCmpFn); err == nil {
		t.Errorf("should have error during GraphCmp, but got nil")
	}
}

// FIXME: i think we should allow equivalent elements in the graph to compare...
// FIXME: currently this fails :(
//func TestGraphCmp2(t *testing.T) {
//	g1 := &Graph{}
//	g2 := &Graph{}
//	g1.AddVertex(NV("v1"), NV("v1"))
//	g2.AddVertex(NV("v1"), NV("v1"))
//
//	if err := g1.GraphCmp(g2, strVertexCmpFn, strEdgeCmpFn); err != nil {
//		t.Errorf("should have no error during GraphCmp, but got: %v", err)
//	}
//}

func TestSort0(t *testing.T) {
	vs := []Vertex{}
	s := Sort(vs)

	if !reflect.DeepEqual(s, []Vertex{}) {
		t.Errorf("sort failed!")
		if s == nil {
			t.Logf("output is nil!")
		} else {
			str := "Got:"
			for _, v := range s {
				str += " " + v.String()
			}
			t.Error(str)
		}
	}
}

func TestSort1(t *testing.T) {
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")

	vs := []Vertex{v3, v2, v6, v1, v5, v4}
	s := Sort(vs)

	if !reflect.DeepEqual(s, []Vertex{v1, v2, v3, v4, v5, v6}) {
		t.Errorf("sort failed!")
		str := "Got:"
		for _, v := range s {
			str += " " + v.String()
		}
		t.Error(str)
	}

	if !reflect.DeepEqual(vs, []Vertex{v3, v2, v6, v1, v5, v4}) {
		t.Errorf("sort modified input!")
		str := "Got:"
		for _, v := range vs {
			str += " " + v.String()
		}
		t.Error(str)
	}
}

func TestSprint1(t *testing.T) {
	g, _ := NewGraph("graph1")
	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v4, e3)
	g.AddEdge(v4, v5, e4)
	g.AddEdge(v5, v6, e5)

	str := g.Sprint()
	t.Logf("graph is:\n%s", str)
	count := 0
	for count < 100000 { // about one second
		x := g.Sprint()
		if str != x {
			t.Errorf("graph sprint is not consistent")
			return
		}
		count++
	}
}

func TestDeleteEdge1(t *testing.T) {
	g, _ := NewGraph("g")

	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")

	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")

	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v1, v3, e3)
	g.AddEdge(v2, v1, e4)
	g.AddEdge(v3, v2, e5)
	g.AddEdge(v3, v1, e6)

	g.DeleteEdge(e1)
	g.DeleteEdge(e2)
	g.DeleteEdge(e3)
	g.DeleteEdge(e3)

	if g.NumEdges() != 3 {
		t.Errorf("expected number of edges: 3, instead of: %d", g.NumEdges())
	}
}

func TestDeleteEdge2(t *testing.T) {
	g, _ := NewGraph("g")

	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")

	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	e7 := NE("e7")

	g.AddEdge(v1, v2, e1)
	g.AddEdge(v1, v2, e2)
	g.AddEdge(v1, v3, e3)
	g.AddEdge(v1, v4, e4)
	g.AddEdge(v2, v1, e5)
	g.AddEdge(v3, v1, e6)
	g.AddEdge(v4, v1, e7)

	g.DeleteEdge(e1)
	g.DeleteEdge(e2)
	g.DeleteEdge(e3)
	g.DeleteEdge(e5)
	g.DeleteEdge(e6)

	ie := g.IncomingGraphEdges(v1)
	oe := g.OutgoingGraphEdges(v1)

	if !reflect.DeepEqual(ie, []Edge{e7}) {
		res := ""
		for _, e := range ie {
			res += e.String() + " "
		}
		t.Errorf("expected incoming graph edges for vertex v1: e7, instead of: %s", res)
	}

	if !reflect.DeepEqual(oe, []Edge{e4}) {
		res := ""
		for _, e := range oe {
			res += e.String() + " "
		}
		t.Errorf("expected outgoing graph edges for vertex v1: e4, instead of: %s", res)
	}
}

// checkReverseIndex verifies that the internal reverse adjacency index is an
// exact mirror of the adjacency map. Any mismatch is a programming error in one
// of the graph mutation methods.
func checkReverseIndex(t *testing.T, g *Graph) {
	t.Helper()
	countAdjacency := 0
	for v1, m := range g.adjacency {
		if _, exists := g.revadjmap[v1]; !exists {
			t.Errorf("vertex %s missing from reverse index", v1)
		}
		for v2, e := range m {
			countAdjacency++
			if ex, exists := g.revadjmap[v2][v1]; !exists || ex != e {
				t.Errorf("edge %s -> %s missing or wrong in reverse index", v1, v2)
			}
		}
	}
	countReverse := 0
	for v1, m := range g.revadjmap {
		if _, exists := g.adjacency[v1]; !exists {
			t.Errorf("vertex %s in reverse index but not in adjacency", v1)
		}
		countReverse += len(m)
	}
	if countAdjacency != countReverse {
		t.Errorf("adjacency has %d edges, reverse index has %d", countAdjacency, countReverse)
	}
}

func TestReverseIndex1(t *testing.T) {
	g := &Graph{} // ensure lazy initialization works too
	checkReverseIndex(t, g)

	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")

	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")

	g.AddVertex(v1)
	checkReverseIndex(t, g)

	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v3, v1, e3)
	g.AddEdge(v3, v4, e4)
	g.AddEdge(v3, v4, e1) // overwrite an edge
	checkReverseIndex(t, g)

	g.DeleteEdgeBetween(v2, v3)
	checkReverseIndex(t, g)

	g.DeleteEdge(e1) // appears twice in the graph
	checkReverseIndex(t, g)

	g.DeleteVertex(v3)
	checkReverseIndex(t, g)
	if g.NumVertices() != 3 {
		t.Errorf("expected number of vertices: 3, instead of: %d", g.NumVertices())
	}

	cp := g.Copy()
	checkReverseIndex(t, cp)

	fn := func(v Vertex) (Vertex, error) { return NV(v.String()), nil }
	cpfn, err := g.CopyWithFn(fn)
	if err != nil {
		t.Errorf("unexpected error: %+v", err)
		return
	}
	checkReverseIndex(t, cpfn)

	g.DeleteVertex(v1, v2, v4)
	checkReverseIndex(t, g)
	if g.NumVertices() != 0 {
		t.Errorf("expected number of vertices: 0, instead of: %d", g.NumVertices())
	}
}

func TestDeleteEdgeBetween1(t *testing.T) {
	g, _ := NewGraph("g")

	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")

	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")

	g.AddEdge(v1, v2, e1)
	g.AddEdge(v2, v3, e2)
	g.AddEdge(v2, v1, e3)

	g.DeleteEdgeBetween(v1, v2)
	g.DeleteEdgeBetween(v1, v2) // duplicate delete should be a noop
	g.DeleteEdgeBetween(v1, v3) // no such edge, should be a noop

	if g.NumEdges() != 2 {
		t.Errorf("expected number of edges: 2, instead of: %d", g.NumEdges())
	}
	if e := g.FindEdge(v1, v2); e != nil {
		t.Errorf("expected edge v1 -> v2 to be deleted, got: %s", e)
	}
	if e := g.FindEdge(v2, v1); e != e3 { // reverse direction must remain
		t.Errorf("expected edge v2 -> v1 to remain")
	}
	if g.NumVertices() != 3 { // vertices must not be removed
		t.Errorf("expected number of vertices: 3, instead of: %d", g.NumVertices())
	}
}

func TestFindEdge1(t *testing.T) {
	g, _ := NewGraph("g")

	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	v7 := NV("v7")

	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	e7 := NE("e7")
	e8 := NE("e8")
	e9 := NE("e9")
	e10 := NE("e10")

	g.AddEdge(v1, v2, e1)
	g.AddEdge(v1, v4, e2)
	g.AddEdge(v1, v3, e3)
	g.AddEdge(v2, v3, e4)
	g.AddEdge(v2, v4, e5)
	g.AddEdge(v2, v6, e6)
	g.AddEdge(v2, v5, e7)
	g.AddEdge(v3, v6, e8)
	g.AddEdge(v4, v7, e9)
	g.AddEdge(v5, v7, e10)

	if !(g.HasVertex(v1) && g.HasVertex(v4) && g.HasVertex(v7)) {
		t.Errorf("graph expected to have vertices v1, v4, and v7")
	}
	if g.FindEdge(v1, v4) != e2 {
		t.Errorf("edge e2 was not returned")
	}
	if g.FindEdge(v1, v7) != nil {
		t.Errorf("an edge was found although it did not exist")
	}
}

func TestFindEdge2(t *testing.T) {
	g, _ := NewGraph("g")

	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")

	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")

	g.AddEdge(v1, v4, e1)
	g.AddEdge(v2, v5, e2)
	g.AddEdge(v3, v6, e3)

	if g.FindEdge(v1, v4) != e1 {
		t.Errorf("edge e1 was not returned")
	}
	if g.FindEdge(v2, v5) != e2 {
		t.Errorf("edge e1 was not returned")
	}
	if g.FindEdge(v3, v6) != e3 {
		t.Errorf("edge e1 was not returned")
	}
	if g.FindEdge(v2, v6) != nil {
		t.Errorf("an edge was found although it did not exist")
	}
}

func TestLookupEdge1(t *testing.T) {
	g, _ := NewGraph("g")

	v1 := NV("v1")
	v2 := NV("v2")
	v3 := NV("v3")
	v4 := NV("v4")
	v5 := NV("v5")
	v6 := NV("v6")
	v7 := NV("v7")

	e1 := NE("e1")
	e2 := NE("e2")
	e3 := NE("e3")
	e4 := NE("e4")
	e5 := NE("e5")
	e6 := NE("e6")
	e7 := NE("e7")
	e8 := NE("e8")
	e9 := NE("e9")
	e10 := NE("e10")

	g.AddEdge(v1, v2, e1)
	g.AddEdge(v1, v4, e2)
	g.AddEdge(v1, v3, e3)
	g.AddEdge(v2, v3, e4)
	g.AddEdge(v2, v4, e5)
	g.AddEdge(v2, v6, e6)
	g.AddEdge(v2, v5, e7)
	g.AddEdge(v3, v6, e8)
	g.AddEdge(v4, v7, e9)
	g.AddEdge(v5, v7, e10)

	if x, y, found := g.LookupEdge(e1); !found || x != v1 || y != v2 {
		t.Errorf("vertices v1, v2 were not returned")
	}
	if x, y, found := g.LookupEdge(e2); !found || x != v1 || y != v4 {
		t.Errorf("vertices v1, v4 were not returned")
	}
	if x, y, found := g.LookupEdge(e3); !found || x != v1 || y != v3 {
		t.Errorf("vertices v1, v3 were not returned")
	}
	if x, y, found := g.LookupEdge(e4); !found || x != v2 || y != v3 {
		t.Errorf("vertices v2, v3 were not returned")
	}
	if x, y, found := g.LookupEdge(e5); !found || x != v2 || y != v4 {
		t.Errorf("vertices v2, v4 were not returned")
	}
	if x, y, found := g.LookupEdge(e6); !found || x != v2 || y != v6 {
		t.Errorf("vertices v2, v6 were not returned")
	}
	if x, y, found := g.LookupEdge(e7); !found || x != v2 || y != v5 {
		t.Errorf("vertices v2, v5 were not returned")
	}
	if x, y, found := g.LookupEdge(e8); !found || x != v3 || y != v6 {
		t.Errorf("vertices v3, v6 were not returned")
	}
	if x, y, found := g.LookupEdge(e9); !found || x != v4 || y != v7 {
		t.Errorf("vertices v4, v7 were not returned")
	}
	if x, y, found := g.LookupEdge(e10); !found || x != v5 || y != v7 {
		t.Errorf("vertices v5, v7 were not returned")
	}

	e99 := NE("e99")
	if _, _, found := g.LookupEdge(e99); found {
		t.Errorf("unexpected vertices were found")
	}
}

func TestSetValue(t *testing.T) {
	g, _ := NewGraph("SetValue")

	key := "k1"
	value := "v1"

	g.SetValue(key, value)
	if g.kv[key] != value {
		t.Errorf("expecting value of %s at %s position, got %v", value, key, g.kv[key])
	}

	if v, ok := g.Value(key); !ok {
		t.Errorf("key %s doesn't exist", key)
	} else if v != value {
		t.Errorf("expecting value of %s at %s position, got %v", value, key, v)
	}
}
