package slices

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestPartitionFlatten(t *testing.T) {
	tests := map[string]struct {
		input    []int
		n        int
		expected [][]int
	}{
		"3 elements 1 partition": {
			input:    []int{1, 2, 3},
			n:        1,
			expected: [][]int{{1, 2, 3}},
		},
		"3 elements 2 partitions": {
			input:    []int{1, 2, 3},
			n:        2,
			expected: [][]int{{1, 2}, {3}},
		},
		"3 elements 3 partitions": {
			input:    []int{1, 2, 3},
			n:        3,
			expected: [][]int{{1}, {2}, {3}},
		},
		"3 elements 4 partitions": {
			input:    []int{1, 2, 3},
			n:        4,
			expected: [][]int{{1}, {2}, {3}, {}},
		},
		"empty input 3 partitions": {
			input:    []int{},
			n:        3,
			expected: [][]int{{}, {}, {}},
		},
		"nil input 3 partitions": {
			input:    nil,
			n:        3,
			expected: [][]int{nil, nil, nil},
		},
	}
	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			partitions := Partition(tc.input, tc.n)
			assert.Equal(t, tc.expected, partitions)
			flattened := Flatten(partitions)
			assert.Equal(t, tc.input, flattened)
		})
	}
}

func TestMap(t *testing.T) {
	tests := map[string]struct {
		input     []int
		transform func(int) int
		expected  []int
	}{
		"identity": {
			input:     []int{1, 2, 3},
			transform: func(i int) int { return i },
			expected:  []int{1, 2, 3},
		},
		"simple function": {
			input:     []int{1, 2, 3},
			transform: func(i int) int { return i + 1 },
			expected:  []int{2, 3, 4},
		},
		"empty input": {
			input:     []int{},
			transform: func(i int) int { return i + 1 },
			expected:  []int{},
		},
		"nil input": {
			input:     nil,
			transform: func(i int) int { return i + 1 },
			expected:  nil,
		},
	}
	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			output := Map(tc.input, tc.transform)
			assert.Equal(t, tc.expected, output)
		})
	}
}

func TestAllFunc(t *testing.T) {
	tests := map[string]struct {
		input     []int
		transform func(int) bool
		expected  bool
	}{
		"returns constant false": {
			input:     []int{1, 2, 3},
			transform: func(i int) bool { return false },
			expected:  false,
		},
		"returns constant true": {
			input:     []int{1, 2, 3},
			transform: func(i int) bool { return true },
			expected:  true,
		},
		"simple function returns true": {
			input:     []int{1, 2, 3},
			transform: func(i int) bool { return i < 4 },
			expected:  true,
		},
		"simple function returns false": {
			input:     []int{1, 2, 3},
			transform: func(i int) bool { return i > 3 },
			expected:  false,
		},
		"simple function returns mixed": {
			input:     []int{1, 2, 3},
			transform: func(i int) bool { return i < 3 },
			expected:  false,
		},
		"empty input": {
			input:     []int{},
			transform: func(i int) bool { return false },
			expected:  true,
		},
		"nil input": {
			input:     nil,
			transform: func(i int) bool { return false },
			expected:  true,
		},
	}
	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			output := AllFunc(tc.input, tc.transform)
			assert.Equal(t, tc.expected, output)
		})
	}
}

func TestApply(t *testing.T) {
	type testType struct {
		val int
	}
	tests := map[string]struct {
		input     []*testType
		transform func(*testType)
		expected  []*testType
	}{
		"identity": {
			input:     []*testType{{1}, {2}, {3}},
			transform: func(i *testType) { return },
			expected:  []*testType{{1}, {2}, {3}},
		},
		"simple function": {
			input:     []*testType{{1}, {2}, {3}},
			transform: func(i *testType) { i.val += 1 },
			expected:  []*testType{{2}, {3}, {4}},
		},
		"empty input": {
			input:     []*testType{},
			transform: func(i *testType) { i.val += 1 },
			expected:  []*testType{},
		},
		"nil input": {
			input:     nil,
			transform: func(i *testType) { i.val += 1 },
			expected:  nil,
		},
	}
	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			Apply(tc.input, tc.transform)
			output := tc.input
			assert.Equal(t, tc.expected, output)
		})
	}
}

func TestPartitionToMaxLen(t *testing.T) {
	tests := map[string]struct {
		input  []int
		maxLen int
	}{
		"3 elements maxLen 1": {
			input:  []int{1, 2, 3},
			maxLen: 1,
		},
		"3 elements maxLen 2": {
			input:  []int{1, 2, 3},
			maxLen: 2,
		},
		"3 elements maxLen 3": {
			input:  []int{1, 2, 3},
			maxLen: 3,
		},
		"3 elements maxLen 4": {
			input:  []int{1, 2, 3},
			maxLen: 4,
		},
		"empty input maxLen 3": {
			input:  []int{},
			maxLen: 3,
		},
		"nil input maxLen 3": {
			input:  nil,
			maxLen: 3,
		},
	}
	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			partitions := PartitionToMaxLen(tc.input, tc.maxLen)
			for _, partition := range partitions {
				assert.LessOrEqual(t, len(partition), tc.maxLen)
			}
			flattened := Flatten(partitions)
			assert.Equal(t, tc.input, flattened)
		})
	}
}

func TestGroupByFunc(t *testing.T) {
	s := []int{1, 2, 3, 4, 5}
	actual := GroupByFunc(s, func(e int) int { return e / 3 })
	expected := map[int][]int{
		0: {1, 2},
		1: {3, 4, 5},
	}
	assert.Equal(t, expected, actual)
}

func TestMapAndGroupByFuncs(t *testing.T) {
	s := []int{1, 2, 3, 4, 5}
	actual := MapAndGroupByFuncs(s, func(e int) int { return e / 3 }, func(e int) float64 { return float64(e) })
	expected := map[int][]float64{
		0: {1.0, 2.0},
		1: {3.0, 4.0, 5.0},
	}
	assert.Equal(t, expected, actual)
}

func TestSubtract(t *testing.T) {
	tests := map[string]struct {
		input          []int
		toSubtract     []int
		expectedOutput []int
	}{
		"RemovesExpectedElements": {
			input:          []int{1, 3, 5},
			toSubtract:     []int{1, 5},
			expectedOutput: []int{3},
		},
		"ToRemoveAndInputAreEqual": {
			input:          []int{1, 3, 5, 7, 9},
			toSubtract:     []int{1, 3, 5, 7, 9},
			expectedOutput: []int{},
		},
		"ToRemoveIsLargerThanInput": {
			input:          []int{1, 5, 9},
			toSubtract:     []int{1, 3, 5, 7, 9},
			expectedOutput: []int{},
		},
		"InputIsNil": {
			input:          nil,
			toSubtract:     []int{1, 3, 5, 7, 9},
			expectedOutput: nil,
		},
		"ToRemoveIsNil": {
			input:          []int{1, 3, 5, 7, 9},
			toSubtract:     nil,
			expectedOutput: []int{1, 3, 5, 7, 9},
		},
		"AllInputsAreNil": {
			input:          nil,
			toSubtract:     nil,
			expectedOutput: nil,
		},
		"AllInputsAreEmpty": {
			input:          []int{},
			toSubtract:     []int{},
			expectedOutput: []int{},
		},
	}
	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			output := Subtract(tc.input, tc.toSubtract)
			assert.Equal(t, output, tc.expectedOutput)
		})
	}
}

func TestUnique(t *testing.T) {
	tests := map[string]struct {
		s        []int
		expected []int
	}{
		"nil": {
			s:        nil,
			expected: nil,
		},
		"empty": {
			s:        make([]int, 0),
			expected: make([]int, 0),
		},
		"no duplicates": {
			s:        []int{1, 2, 3},
			expected: []int{1, 2, 3},
		},
		"consecutive duplicates": {
			s:        []int{1, 2, 3, 3},
			expected: []int{1, 2, 3},
		},
		"non-consecutive duplicates": {
			s:        []int{3, 1, 2, 3},
			expected: []int{3, 1, 2},
		},
	}
	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			assert.Equal(t, tc.expected, Unique(tc.s))
		})
	}
}

func TestFilter(t *testing.T) {
	includeOver5 := func(val int) bool { return val > 5 }
	input := []int{1, 3, 5, 7, 9}
	expectedOutput := []int{7, 9}

	output := Filter(input, includeOver5)
	assert.Equal(t, expectedOutput, output)
}

func TestFilter_ExcludeAllFilter(t *testing.T) {
	excludeAll := func(val int) bool { return false }
	input := []int{1, 3, 5, 7, 9}
	expectedOutput := []int{}
	output := Filter(input, excludeAll)
	assert.Equal(t, expectedOutput, output)
}

func TestFilter_IncludeAllFilter(t *testing.T) {
	includeAll := func(val int) bool { return true }
	input := []int{1, 3, 5, 7, 9}
	expectedOutput := input

	output := Filter(input, includeAll)
	assert.Equal(t, expectedOutput, output)
}

func TestFilter_Empty(t *testing.T) {
	includeAll := func(val int) bool { return true }
	input := []int{}
	expectedOutput := input

	output := Filter(input, includeAll)
	assert.Equal(t, expectedOutput, output)
}

func TestFilter_Nil(t *testing.T) {
	includeAll := func(val int) bool { return true }
	var input []int = nil
	expectedOutput := input

	output := Filter(input, includeAll)
	assert.Equal(t, expectedOutput, output)
}

func TestPop(t *testing.T) {
	s := []int{1, 2, 3}
	expected := []int{3, 2, 1}
	var actual []int
	for len(s) > 0 {
		actual = append(actual, Pop(&s))
	}
	assert.Equal(t, expected, actual)
	assert.Panics(
		t,
		func() {
			Pop(&s)
		},
	)
	assert.Panics(
		t,
		func() {
			var s []int
			Pop(&s)
		},
	)
}

func TestRepeat(t *testing.T) {
	tests := map[string]struct {
		n        int
		vs       []int
		expected []int
	}{
		"n: 0": {
			n:        0,
			vs:       []int{1, 2, 3},
			expected: []int{},
		},
		"vs emptyy": {
			n:        3,
			vs:       []int{},
			expected: []int{},
		},
		"one entry": {
			n:        3,
			vs:       []int{0},
			expected: []int{0, 0, 0},
		},
		"multiple entries": {
			n:        3,
			vs:       []int{0, 1},
			expected: []int{0, 1, 0, 1, 0, 1},
		},
	}
	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			assert.Equal(t, tc.expected, Repeat(tc.n, tc.vs...))
		})
	}
}

func TestAny(t *testing.T) {
	var nilSlice []int
	assert.Equal(
		t,
		false,
		AnyFunc(nilSlice, func(v int) bool { return true }),
	)

	emptySlice := make([]int, 0)
	assert.Equal(
		t,
		false,
		AnyFunc(emptySlice, func(v int) bool { return true }),
	)

	assert.Equal(
		t,
		true,
		AnyFunc([]int{1, 2, 3}, func(v int) bool { return v > 2 }),
	)

	assert.Equal(
		t,
		false,
		AnyFunc([]int{1, 2, 3}, func(v int) bool { return v > 3 }),
	)
}
