// Copyright ©2018 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 testgraph

import (
	"math"

	"gonum.org/v1/gonum/graph"
)

// node is a graph.Node implementation that is not exported
// so that other packages will not be aware of its implementation.
type node int64

func (n node) ID() int64 { return int64(n) }

// line is an extended graph.Edge implementation that is not exported
// so that other packages will not be aware of its implementation. It
// covers all the edge types exported by graph.
type line struct {
	F, T graph.Node
	UID  int64
	W    float64
}

func (e line) From() graph.Node         { return e.F }
func (e line) To() graph.Node           { return e.T }
func (e line) ReversedEdge() graph.Edge { e.F, e.T = e.T, e.F; return e }
func (e line) ID() int64                { return e.UID }
func (e line) Weight() float64          { return e.W }

var testCases = []struct {
	// name is the name of the test.
	name string

	// nodes is the set of nodes that should be used
	// to construct the graph.
	nodes []graph.Node

	// edges is the set of edges that should be used
	// to construct the graph.
	edges []WeightedLine

	// nonexist is a set of nodes that should not be
	// found within the graph.
	nonexist []graph.Node

	// self is the weight value associated with
	// a self edge for simple graphs that do not
	// store individual self edges.
	self float64

	// absent is the weight value associated
	// with absent edges.
	absent float64
}{
	{
		name:     "empty",
		nonexist: []graph.Node{node(-1), node(0), node(1)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "one - negative",
		nodes:    []graph.Node{node(-1)},
		nonexist: []graph.Node{node(0), node(1)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "one - zero",
		nodes:    []graph.Node{node(0)},
		nonexist: []graph.Node{node(-1), node(1)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "one - positive",
		nodes:    []graph.Node{node(1)},
		nonexist: []graph.Node{node(-1), node(0)},
		self:     0,
		absent:   math.Inf(1),
	},

	{
		name:     "one - self loop",
		nodes:    []graph.Node{node(0)},
		edges:    []WeightedLine{line{F: node(0), T: node(0), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(-1), node(1)},
		self:     0,
		absent:   math.Inf(1),
	},

	{
		name:     "two - positive",
		nodes:    []graph.Node{node(1), node(2)},
		edges:    []WeightedLine{line{F: node(1), T: node(2), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(-1), node(0)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "two - negative",
		nodes:    []graph.Node{node(-1), node(-2)},
		edges:    []WeightedLine{line{F: node(-1), T: node(-2), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(0), node(-3)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "two - zero spanning",
		nodes:    []graph.Node{node(-1), node(1)},
		edges:    []WeightedLine{line{F: node(-1), T: node(1), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(0), node(2)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "two - zero contiguous",
		nodes:    []graph.Node{node(0), node(1)},
		edges:    []WeightedLine{line{F: node(0), T: node(1), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(-1), node(2)},
		self:     0,
		absent:   math.Inf(1),
	},

	{
		name:     "three - positive",
		nodes:    []graph.Node{node(1), node(2), node(3)},
		edges:    []WeightedLine{line{F: node(1), T: node(2), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(-1), node(0)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "three - negative",
		nodes:    []graph.Node{node(-1), node(-2), node(-3)},
		edges:    []WeightedLine{line{F: node(-1), T: node(-2), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(0), node(1)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "three - zero spanning",
		nodes:    []graph.Node{node(-1), node(0), node(1)},
		edges:    []WeightedLine{line{F: node(-1), T: node(1), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(-2), node(2)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:     "three - zero contiguous",
		nodes:    []graph.Node{node(0), node(1), node(2)},
		edges:    []WeightedLine{line{F: node(0), T: node(1), UID: 0, W: 0.5}},
		nonexist: []graph.Node{node(-1), node(3)},
		self:     0,
		absent:   math.Inf(1),
	},

	{
		name:  "three in only",
		nodes: []graph.Node{node(0), node(1), node(2), node(3)},
		edges: []WeightedLine{
			line{F: node(1), T: node(0), UID: 0, W: 0.5},
			line{F: node(2), T: node(0), UID: 1, W: 0.5},
			line{F: node(3), T: node(0), UID: 2, W: 0.5},
		},
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:  "three out only",
		nodes: []graph.Node{node(0), node(1), node(2), node(3)},
		edges: []WeightedLine{
			line{F: node(0), T: node(1), UID: 0, W: 0.5},
			line{F: node(0), T: node(2), UID: 1, W: 0.5},
			line{F: node(0), T: node(3), UID: 2, W: 0.5},
		},
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},

	{
		name: "4-clique - single(non-prepared)",
		edges: func() []WeightedLine {
			const n = 4
			var uid int64
			var edges []WeightedLine
			for i := 0; i < n; i++ {
				for j := i + 1; j < 4; j++ {
					edges = append(edges, line{F: node(i), T: node(j), UID: uid, W: 0.5})
					uid++
				}
			}
			return edges
		}(),
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name: "4-clique+ - single(non-prepared)",
		edges: func() []WeightedLine {
			const n = 4
			var uid int64
			var edges []WeightedLine
			for i := 0; i < n; i++ {
				for j := i; j < 4; j++ {
					edges = append(edges, line{F: node(i), T: node(j), UID: uid, W: 0.5})
					uid++
				}
			}
			return edges
		}(),
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name: "4-clique - single(prepared)",
		nodes: func() []graph.Node {
			const n = 4
			nodes := make([]graph.Node, n)
			for i := range nodes {
				nodes[i] = node(i)
			}
			return nodes
		}(),
		edges: func() []WeightedLine {
			const n = 4
			var uid int64
			var edges []WeightedLine
			for i := 0; i < n; i++ {
				for j := i + 1; j < n; j++ {
					edges = append(edges, line{F: node(i), T: node(j), UID: uid, W: 0.5})
					uid++
				}
			}
			return edges
		}(),
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name: "4-clique+ - single(prepared)",
		nodes: func() []graph.Node {
			const n = 4
			nodes := make([]graph.Node, n)
			for i := range nodes {
				nodes[i] = node(i)
			}
			return nodes
		}(),
		edges: func() []WeightedLine {
			const n = 4
			var uid int64
			var edges []WeightedLine
			for i := 0; i < n; i++ {
				for j := i; j < n; j++ {
					edges = append(edges, line{F: node(i), T: node(j), UID: uid, W: 0.5})
					uid++
				}
			}
			return edges
		}(),
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},

	{
		name: "4-clique - double(non-prepared)",
		edges: func() []WeightedLine {
			const n = 4
			var uid int64
			var edges []WeightedLine
			for i := 0; i < n; i++ {
				for j := i + 1; j < n; j++ {
					edges = append(edges, line{F: node(i), T: node(j), UID: uid, W: 0.5})
					uid++
					edges = append(edges, line{F: node(j), T: node(i), UID: uid, W: 0.5})
					uid++
				}
			}
			return edges
		}(),
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name: "4-clique+ - double(non-prepared)",
		edges: func() []WeightedLine {
			const n = 4
			var uid int64
			var edges []WeightedLine
			for i := 0; i < n; i++ {
				for j := i; j < n; j++ {
					edges = append(edges, line{F: node(i), T: node(j), UID: uid, W: 0.5})
					uid++
					edges = append(edges, line{F: node(j), T: node(i), UID: uid, W: 0.5})
					uid++
				}
			}
			return edges
		}(),
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name: "4-clique - double(prepared)",
		nodes: func() []graph.Node {
			const n = 4
			nodes := make([]graph.Node, n)
			for i := range nodes {
				nodes[i] = node(i)
			}
			return nodes
		}(),
		edges: func() []WeightedLine {
			const n = 4
			var uid int64
			var edges []WeightedLine
			for i := 0; i < n; i++ {
				for j := i + 1; j < n; j++ {
					edges = append(edges, line{F: node(i), T: node(j), UID: uid, W: 0.5})
					uid++
					edges = append(edges, line{F: node(j), T: node(i), UID: uid, W: 0.5})
					uid++
				}
			}
			return edges
		}(),
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name: "4-clique+ - double(prepared)",
		nodes: func() []graph.Node {
			const n = 4
			nodes := make([]graph.Node, n)
			for i := range nodes {
				nodes[i] = node(i)
			}
			return nodes
		}(),
		edges: func() []WeightedLine {
			const n = 4
			var uid int64
			var edges []WeightedLine
			for i := 0; i < n; i++ {
				for j := i; j < n; j++ {
					edges = append(edges, line{F: node(i), T: node(j), UID: uid, W: 0.5})
					uid++
					edges = append(edges, line{F: node(j), T: node(i), UID: uid, W: 0.5})
					uid++
				}
			}
			return edges
		}(),
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
	{
		name:  "issue 1686",
		nodes: []graph.Node{node(0), node(1), node(2)},
		edges: []WeightedLine{
			line{F: node(0), T: node(1), UID: 0, W: 0.5},
			line{F: node(1), T: node(2), UID: 0, W: 0.5},
		},
		nonexist: []graph.Node{node(-1), node(4)},
		self:     0,
		absent:   math.Inf(1),
	},
}
