package mutant

import (
	"testing"
)

var (
	human = []string{"ATGCGA", "AAGTGC", "ATATTT", "AGACGG", "GCGTCA", "TCACTG"}
	mutantBase = []string{"ATGCGA", "CAGTGC", "TTATGT", "AGAAGG", "CCCCTA", "TCACTG"}
	mutantFastDetection = []string{"ATGCGA", "AAGTGC", "ATAGTC", "AGGAGC", "GGCTCC", "TCACTG"}
	mutantHorizontal = []string{"AAAAGA", "CAGTGC", "TTTTGT", "AGAAGG", "CCCCTA", "TCACTG"}
	mutantVertical = []string{"ATGCGA", "AAGTGC", "ATATTC", "AGACGC", "GCGTCC", "TCACTG"}
	mutantDiagonal = []string{"ATGCGA", "AAGTGC", "ATAGTC", "GGGAGC", "GGCTCC", "TCACTG"}
	mutantBigMatrix = []string{"ATGCGAA", "CAGTGCC", "TTATGTT", "AGAAGGG", "CCCCTAA", "TCACTGG", "ATGCGAA"}
	invalidMatrix = []string{"AGA", "GCF"}
)

func BenchmarkIsMutant(b *testing.B) {
	for n := 0; n < b.N; n++ {
		IsMutant(mutantBigMatrix)
	}
}

func TestIsMutant(t *testing.T) {
	testCases := []struct {
		dna []string
		isMutant bool
	}{
		{human, false},
		{mutantBase, true},
		{mutantFastDetection, true},
		{mutantHorizontal, true},
		{mutantVertical, true},
		{mutantDiagonal, true},
		{mutantBigMatrix, true},
	}

	for _, testCase := range testCases {

		if result := IsMutant(testCase.dna); result != testCase.isMutant {
			t.Errorf("DNA %+v expected to be mutant? %t, got: %t",
				testCase.dna,
				testCase.isMutant,
				result,
			)
		}
	}
}

func BenchmarkIsSquareMatrix(b *testing.B) {
	for i := 0; i < b.N; i++ {
		IsSquareMatrix(mutantBigMatrix)
	}
}

func TestIsSquareMatrix(t *testing.T) {
	testCases := []struct {
		dna []string
		isValid bool
	}{
		{human, true},
		{invalidMatrix, false},
	}
	for _, testCase := range testCases {
		result := IsSquareMatrix(testCase.dna)
		if result != testCase.isValid {
			t.Errorf("In DNA %+v should be valid? %t got: %t",
				testCase.dna,
				testCase.isValid,
				result,
			)
		}
	}
}

func BenchmarkHasInvalidCharacters(b *testing.B) {
	for i := 0; i < b.N; i++ {
		HasInvalidCharacters(mutantBigMatrix)
	}
}

func TestHasInvalidCharacters(t *testing.T) {
	testCases := []struct {
		dna []string
		result bool
	}{
		{human, false},
		{invalidMatrix, true},
	}
	for _, testCase := range testCases {
		result := HasInvalidCharacters(testCase.dna)
		if result != testCase.result {
			t.Errorf("DNA %+v should have invalid characters? %t got: %t",
				testCase.dna,
				testCase.result,
				result,
			)
		}
	}
}

func BenchmarkBuildUniqueId(b *testing.B) {
	for i := 0; i < b.N; i++ {
		BuildUniqueId(mutantBigMatrix)
	}
}

func TestBuildUniqueId(t *testing.T) {
	testCases := []struct {
		dna []string
		sha1 string
	}{
		{human, "4a3e32ccec6f3babc1a00f8a61fd451833ec3d2c"},
		{mutantBigMatrix, "836b48609a33810587595ea1eb5dc7e1cff85c2d"},
	}
	for _, testCase := range testCases {
		result := BuildUniqueId(testCase.dna)
		if result != testCase.sha1 {
			t.Errorf("DNA %+v should have an UID %s, got: %s",
				testCase.dna,
				testCase.sha1,
				result,
			)
		}
	}
}

func TestSearchMutantSubSequence(t *testing.T) {
	testCases := []struct {
		dna []string
		startPosition Point
		found int
	}{
		{human, Point{0, 0}, 1},
		{mutantBase, Point{0, 0}, 1},
		{mutantFastDetection, Point{0, 0}, 2},
	}

	for _, testCase := range testCases {
		// reset subsequences
		mutantSubsequences = 0
		searchMutantSubSequence(testCase.dna, testCase.startPosition)
		if mutantSubsequences != testCase.found {
			t.Errorf("In DNA %+v from position (%d, %d) found: %d, expected: %d",
				testCase.dna,
				testCase.startPosition.x,
				testCase.startPosition.y,
				mutantSubsequences,
				testCase.found,
			)
		}
	}
}

func TestGetCharsLeftCount(t *testing.T) {
	testCases := []struct {
		limit    int
		position Point
		dir      Point
		result   int
	}{
		// test direction 1,0
		{5, Point{0, 0}, Point{1, 0}, 6},
		// test direction 1,1
		{5, Point{1, 1}, Point{1, 1}, 5},
		// test direction 0,1
		{5, Point{0, 3}, Point{0, 1}, 3},
		// test direction -1, 1
		{5, Point{2, 0}, Point{-1, 1}, 3},
	}

	for _, testCase := range testCases {
		result := getCharsLeftCount(testCase.limit, testCase.position, testCase.dir)
		if result != testCase.result {
			t.Errorf("Direction (%d, %d) with limit %d, starting from: (%d, %d), expect %d characters left, has %d",
				testCase.position.x,
				testCase.position.y,
				testCase.limit,
				testCase.dir.x,
				testCase.dir.y,
				testCase.result,
				result,
			)
		}
	}
}

func TestOutOfBound(t *testing.T) {
	testCases := []struct {
		point  Point
		limit  int
		result bool
	}{
		// valid case
		{Point{5, 1}, 5, false},
		// bellow boundaries X
		{Point{-4, 1}, 5, true},
		// exceed boundaries X
		{Point{7, 1}, 5, true},
		// bellow boundaries Y
		{Point{0, -2}, 5, true},
		// exceed boundaries Y
		{Point{0, 7}, 5, true},
	}

	for _, testCase := range testCases {
		result := outOfBound(testCase.point, testCase.limit)
		if result != testCase.result {
			var msg string
			if testCase.result {
				msg = "should"
			} else {
				msg = "should not"
			}
			t.Errorf("With Limit %d, Case: (%d, %d) %s exceed boundaries", testCase.limit, testCase.point.x, testCase.point.y, msg)
		}
	}
}