package nodedegree

import (
	"fmt"
	"testing"
)

var smallGraph = [][2]int{
	{1, 2},
	{1, 3},
}

var bigGraph = [][2]int{
	{1, 2},
	{1, 3},
	{2, 3},
	{1, 4},
	{3, 4},
	{1, 5},
	{2, 5},
	{1, 6},
	{2, 6},
	{3, 6},
	{3, 7},
	{5, 7},
	{6, 7},
	{3, 8},
	{4, 8},
	{6, 8},
	{7, 8},
	{2, 9},
	{5, 9},
	{6, 9},
	{2, 10},
	{9, 10},
	{6, 11},
	{7, 11},
	{8, 11},
	{9, 11},
	{10, 11},
	{1, 12},
	{6, 12},
	{7, 12},
	{8, 12},
	{11, 12},
	{6, 13},
	{7, 13},
	{9, 13},
	{10, 13},
	{11, 13},
	{5, 14},
	{8, 14},
	{12, 14},
	{13, 14},
	{1, 15},
	{2, 15},
	{5, 15},
	{9, 15},
	{10, 15},
	{11, 15},
	{12, 15},
	{13, 15},
	{1, 16},
	{2, 16},
	{5, 16},
	{6, 16},
	{11, 16},
	{12, 16},
	{13, 16},
	{14, 16},
	{15, 16},
}

func TestDegree(t *testing.T) {
	tests := []struct {
		nodes      int
		graph      [][2]int
		node       int
		wantDegree int
		wantErrMsg string
	}{
		{3, smallGraph, 1, 2, ""},
		{3, smallGraph, 2, 1, ""},
		{3, smallGraph, 3, 1, ""},
		{3, smallGraph, 4, 0, "node 4 not found in the graph"},
	}

	for _, tt := range tests {
		t.Run(fmt.Sprintf("%d", tt.nodes), func(t *testing.T) {
			got, err := Degree(tt.nodes, tt.graph, tt.node)
			if got != tt.wantDegree {
				t.Fatalf("got %d, expecting %d", got, tt.wantDegree)
			}
			var errMsg string
			if err != nil {
				errMsg = err.Error()
			}
			if errMsg != tt.wantErrMsg {
				t.Fatalf("got '%s' error message, expecting '%s' error message", errMsg, tt.wantErrMsg)
			}
		})
	}
}

func BenchmarkDegree(b *testing.B) {
	for b.Loop() {
		_, err := Degree(16, bigGraph, 6)
		if err != nil {
			fmt.Printf("error: %v\n", err)
		}
	}
}

func TestDegreeBig(t *testing.T) {
	tests := []struct {
		degree func(int, [][2]int, int) (int, error)
	}{
		{DegreeLinear},
		{DegreeLinearCopy},
		{DegreeLinearReverse},
		{DegreeStepReverse},
		{DegreeInterpol},
	}

	for i, test := range tests {
		degree, err := test.degree(16, bigGraph, 6)
		if err != nil {
			t.Error(err)
		}
		if degree != 10 {
			t.Errorf("%d: degree 10 expected, was %d", i, degree)
		}
	}
}

func BenchmarkBig(b *testing.B) {
	b.Run("Linear", bench(DegreeLinear, 16, bigGraph, 6))
	b.Run("LinearCopy", bench(DegreeLinearCopy, 16, bigGraph, 6))
	b.Run("LinearReverse", bench(DegreeLinearReverse, 16, bigGraph, 6))
	b.Run("StepReverse", bench(DegreeStepReverse, 16, bigGraph, 6))
	b.Run("Interpol", bench(DegreeInterpol, 16, bigGraph, 6))
}

func BenchmarkHuge(b *testing.B) {
	initHugeGraph()

	b.Run("Linear", bench(DegreeLinear, N, hugeGraph, int(N*P)))
	b.Run("LinearCopy", bench(DegreeLinearCopy, N, hugeGraph, int(N*P)))
	b.Run("LinearReverse", bench(DegreeLinearReverse, N, hugeGraph, int(N*P)))
	b.Run("StepReverse", bench(DegreeStepReverse, N, hugeGraph, int(N*P)))
	b.Run("Interpol", bench(DegreeInterpol, N, hugeGraph, int(N*P)))
}

func bench(
	degreeImpl func(int, [][2]int, int) (int, error),
	nodes int, graph [][2]int, node int,
) func(*testing.B) {
	return func(b *testing.B) {
		for b.Loop() {
			_, err := degreeImpl(nodes, graph, node)
			if err != nil {
				fmt.Printf("error: %v\n", err)
			}
		}
	}
}

const (
	N = 100000
	P = .5
)

var hugeGraph [][2]int

func initHugeGraph() {
	if len(hugeGraph) > 0 {
		return
	}

	s := 0
	sparce := 100
	for j := 2; j <= N; j++ {
		for i := 1; i < j; i++ {
			if s%sparce == 0 {
				hugeGraph = append(hugeGraph, [2]int{i, j})
			}
			s++
		}
	}
}
