// Copyright ©2015 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 plotter

import (
	"fmt"
	"reflect"
	"sort"
	"testing"
)

func linksTo(i ...int) set {
	if len(i) == 0 {
		return nil
	}
	s := make(set)
	for _, v := range i {
		s[v] = struct{}{}
	}
	return s
}

func (s set) String() string {
	a := make([]int, 0, len(s))
	for v := range s {
		a = append(a, v)
	}
	sort.Ints(a)
	return fmt.Sprint(a)
}

var graphTests = []struct {
	path path
	g    graph

	// Tarjan tests.
	orderIsAmbiguous bool
	wantSCCs         [][]int
	wantAdj          graph

	// Johnson tests.
	wantCycles     [][]int
	wantCyclePaths []path
}{
	{
		path: path{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}, {8, 8}, {9, 9}},

		wantSCCs: [][]int{{9}, {8}, {7}, {6}, {5}, {4}, {3}, {2}, {1}, {0}},
		wantAdj:  nil,

		wantCycles:     nil,
		wantCyclePaths: nil,
	},
	{
		path: path{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {4, 4}, {7, 7}, {8, 8}, {9, 9}},

		wantSCCs: [][]int{
			{10}, {9}, {8},
			{4, 5, 6},
			{3}, {2}, {1}, {0},
			{7 /*second point{4, 4}*/},
		},
		wantAdj: graph{
			4:  linksTo(5),
			5:  linksTo(6),
			6:  linksTo(4),
			10: nil,
		},

		wantCycles: [][]int{
			{4, 5, 6, 4},
		},
		wantCyclePaths: []path{
			{{4, 4}, {5, 5}, {6, 6}, {4, 4}},
		},
	},
	{
		path: path{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {4, 4}, {7, 7}, {2, 2}, {9, 9}},

		wantSCCs: [][]int{
			{10},
			{2, 3, 4, 5, 6, 8},
			{1}, {0},
			{7 /*second point{4, 4}*/},
			{9 /*second point{2, 2}*/},
		},
		wantAdj: graph{
			2:  linksTo(3),
			3:  linksTo(4),
			4:  linksTo(5, 8),
			5:  linksTo(6),
			6:  linksTo(4),
			8:  linksTo(2),
			10: nil,
		},

		wantCycles: [][]int{
			{4, 5, 6, 4},
			{2, 3, 4, 8, 2},
		},
		wantCyclePaths: []path{
			{{4, 4}, {5, 5}, {6, 6}, {4, 4}},
			{{2, 2}, {3, 3}, {4, 4}, {7, 7}, {2, 2}},
		},
	},
	{
		path: path{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {2, 2}, {7, 7}, {2, 2}, {9, 9}},

		wantSCCs: [][]int{{9},
			{2, 3, 4, 5, 7},
			{1}, {0},
			{6 /*second point{2, 2}*/},
			{8 /*third point{2, 2}*/},
		},
		wantAdj: graph{
			2: linksTo(3, 7),
			3: linksTo(4),
			4: linksTo(5),
			5: linksTo(2),
			7: linksTo(2),
			9: nil,
		},

		wantCycles: [][]int{
			{2, 7, 2},
			{2, 3, 4, 5, 2},
		},
		wantCyclePaths: []path{
			{{2, 2}, {7, 7}, {2, 2}},
			{{2, 2}, {3, 3}, {4, 4}, {5, 5}, {2, 2}},
		},
	},
	{
		path: path{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {3, 3}, {8, 8}, {9, 9}, {5, 5}, {10, 10}},

		wantSCCs: [][]int{
			{11},
			{3, 4, 5, 6, 8, 9},
			{2}, {1}, {0},
			{7 /*second point{4, 4}*/},
			{10 /*second point{2, 2}*/},
		},
		wantAdj: graph{
			3:  linksTo(4, 8),
			4:  linksTo(5),
			5:  linksTo(6),
			6:  linksTo(3),
			8:  linksTo(9),
			9:  linksTo(5),
			11: nil,
		},

		wantCycles: [][]int{
			{3, 4, 5, 6, 3},
			{3, 8, 9, 5, 6, 3},
		},
		wantCyclePaths: []path{
			{{3, 3}, {4, 4}, {5, 5}, {6, 6}, {3, 3}},
			{{3, 3}, {8, 8}, {9, 9}, {5, 5}, {6, 6}, {3, 3}},
		},
	},
	{
		path: path{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {0, 0}, {5, 5}, {6, 6}, {7, 7}, {5, 5}, {9, 9}, {10, 10}, {9, 9}},

		wantSCCs: [][]int{
			{9, 10},
			{5, 6, 7},
			{0, 1, 2, 3},
			{4 /*second point{0, 0}*/},
			{8 /*second point{5, 5}*/},
			{11 /*second point{9, 9}*/},
		},
		wantAdj: graph{
			0:  linksTo(1),
			1:  linksTo(2),
			2:  linksTo(3),
			3:  linksTo(0),
			5:  linksTo(6),
			6:  linksTo(7),
			7:  linksTo(5),
			9:  linksTo(10),
			10: linksTo(9),
			11: nil,
		},

		wantCycles: [][]int{
			{9, 10, 9},
			{5, 6, 7, 5},
			{0, 1, 2, 3, 0},
		},
		wantCyclePaths: []path{
			{{9, 9}, {10, 10}, {9, 9}},
			{{5, 5}, {6, 6}, {7, 7}, {5, 5}},
			{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {0, 0}},
		},
	},

	{
		g: graph{
			0: linksTo(1),
			1: linksTo(2, 7),
			2: linksTo(3, 6),
			3: linksTo(4),
			4: linksTo(2, 5),
			6: linksTo(3, 5),
			7: linksTo(0, 6),
		},

		wantSCCs: [][]int{
			{5},
			{2, 3, 4, 6},
			{0, 1, 7},
		},
		wantAdj: graph{
			0: linksTo(1),
			1: linksTo(7),
			2: linksTo(3, 6),
			3: linksTo(4),
			4: linksTo(2),
			6: linksTo(3),
			7: linksTo(0),
		},

		wantCycles: [][]int{
			{0, 1, 7, 0},
			{2, 3, 4, 2},
			{2, 6, 3, 4, 2},
		},
	},
	{
		g: graph{
			0: linksTo(1, 2, 3),
			1: linksTo(2),
			2: linksTo(3),
			3: linksTo(1),
		},

		wantSCCs: [][]int{
			{1, 2, 3},
			{0},
		},
		wantAdj: graph{
			1: linksTo(2),
			2: linksTo(3),
			3: linksTo(1),
		},

		wantCycles: [][]int{
			{1, 2, 3, 1},
		},
	},
	{
		g: graph{
			0: linksTo(1),
			1: linksTo(0, 2),
			2: linksTo(1),
		},

		wantSCCs: [][]int{
			{0, 1, 2},
		},
		wantAdj: graph{
			0: linksTo(1),
			1: linksTo(0, 2),
			2: linksTo(1),
		},

		wantCycles: [][]int{
			{0, 1, 0},
			{1, 2, 1},
		},
	},
	{
		g: graph{
			0: linksTo(1),
			1: linksTo(2, 3),
			2: linksTo(4, 5),
			3: linksTo(4, 5),
			4: linksTo(6),
			5: nil,
			6: nil,
		},

		orderIsAmbiguous: true,
		wantSCCs: [][]int{
			// Node pairs (2, 3) and (4, 5) are not
			// relatively orderable within each pair.
			{6}, {5}, {4}, {3}, {2}, {1}, {0},
		},
		wantAdj: nil,

		wantCycles: nil,
	},
	{
		g: graph{
			0: linksTo(1),
			1: linksTo(2, 3, 4),
			2: linksTo(0, 3),
			3: linksTo(4),
			4: linksTo(3),
		},

		orderIsAmbiguous: true,
		wantSCCs: [][]int{
			// SCCs are not relatively ordable.
			{3, 4}, {0, 1, 2},
		},
		wantAdj: graph{
			0: linksTo(1),
			1: linksTo(2),
			2: linksTo(0),
			3: linksTo(4),
			4: linksTo(3),
		},

		wantCycles: [][]int{
			{3, 4, 3},
			{0, 1, 2, 0},
		},
	},
}

func TestTarjan(t *testing.T) {
	for i, test := range graphTests {
		var g graph
		if test.path != nil {
			g = graphFrom(test.path)
		} else {
			g = test.g
		}
		tar := newTarjan(g)
		gotSCCs := tar.sccs
		if test.orderIsAmbiguous {
			// We lose topological order here, but that
			// is not important for this use case.
			sort.Sort(byComponentLengthOrStart(test.wantSCCs))
			sort.Sort(byComponentLengthOrStart(gotSCCs))
		}
		// tarjan.strongconnect does range iteration over maps,
		// so sort SCC members to ensure consistent ordering.
		for _, scc := range gotSCCs {
			sort.Ints(scc)
		}
		if !reflect.DeepEqual(gotSCCs, test.wantSCCs) {
			t.Errorf("unexpected tarjan scc result for %d:\n\tgot:%v\n\twant:%v", i, gotSCCs, test.wantSCCs)
		}
		gotAdj := tar.sccSubGraph(2)
		if !reflect.DeepEqual(gotAdj, test.wantAdj) {
			t.Errorf("unexpected tarjan sccSubGraph(2) result for %d:\n\tgot:%#v\n\twant:%#v", i, gotAdj, test.wantAdj)
		}
	}
}

func TestJohnson(t *testing.T) {
	for i, test := range graphTests {
		var g graph
		if test.path != nil {
			g = graphFrom(test.path)
		} else {
			g = test.g
		}
		gotCycles := cyclesIn(g)
		// johnson.circuit does range iteration over maps,
		// so sort to ensure consistent ordering.
		sort.Sort(byComponentLengthOrStart(gotCycles))
		if !reflect.DeepEqual(gotCycles, test.wantCycles) {
			t.Errorf("unexpected johnson result for %d:\n\tgot:%#v\n\twant:%#v", i, gotCycles, test.wantCycles)
		}

		// Don't do path reconstruction tests without a path definition.
		if test.path == nil {
			continue
		}

		// Test we reconstruct paths correctly from cycles.
		var gotPaths []path
		for _, pi := range gotCycles {
			gotPaths = append(gotPaths, test.path.subpath(pi))
		}
		if !reflect.DeepEqual(gotPaths, test.wantCyclePaths) {
			t.Errorf("unexpected johnson path result for %d:\n\tgot:%#v\n\twant:%#v", i, gotPaths, test.wantCyclePaths)
		}
	}
}

type byComponentLengthOrStart [][]int

func (c byComponentLengthOrStart) Len() int { return len(c) }
func (c byComponentLengthOrStart) Less(i, j int) bool {
	return len(c[i]) < len(c[j]) || (len(c[i]) == len(c[j]) && c[i][0] < c[j][0])
}
func (c byComponentLengthOrStart) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
