package searcher

import (
	"strings"
	"testing"
)

func TestSearcher(t *testing.T) {
	testCases := []struct {
		name     string
		str      string
		cursor   int64
		pattern  string
		forward  bool
		expected int64
		err      error
	}{
		{
			name:     "search forward",
			str:      "abcde",
			cursor:   0,
			pattern:  "cd",
			forward:  true,
			expected: 2,
		},
		{
			name:    "search forward but not found",
			str:     "abcde",
			cursor:  2,
			pattern: "cd",
			forward: true,
			err:     errNotFound("cd"),
		},
		{
			name:     "search backward",
			str:      "abcde",
			cursor:   4,
			pattern:  "bc",
			forward:  false,
			expected: 1,
		},
		{
			name:    "search backward but not found",
			str:     "abcde",
			cursor:  0,
			pattern: "ba",
			forward: true,
			err:     errNotFound("ba"),
		},
		{
			name:     "search large target forward",
			str:      strings.Repeat(" ", 10*1024*1024+100) + "abcde",
			cursor:   102,
			pattern:  "bcd",
			forward:  true,
			expected: 10*1024*1024 + 101,
		},
		{
			name:    "search large target forward but not found",
			str:     strings.Repeat(" ", 10*1024*1024+100) + "abcde",
			cursor:  102,
			pattern: "cba",
			forward: true,
			err:     errNotFound("cba"),
		},
		{
			name:     "search large target backward",
			str:      "abcde" + strings.Repeat(" ", 10*1024*1024),
			cursor:   10*1024*1024 + 2,
			pattern:  "bcd",
			forward:  false,
			expected: 1,
		},
		{
			name:    "search large target backward but not found",
			str:     "abcde" + strings.Repeat(" ", 10*1024*1024),
			cursor:  10*1024*1024 + 2,
			pattern: "cba",
			forward: false,
			err:     errNotFound("cba"),
		},
		{
			name:     "search hex",
			str:      "\x13\x24\x35\x46\x57\x68",
			cursor:   0,
			pattern:  `\x35\x46\x57`,
			forward:  true,
			expected: 2,
		},
		{
			name:     "search nul",
			str:      "\x06\x07\x08\x00\x09\x10\x11",
			cursor:   0,
			pattern:  `\0`,
			forward:  true,
			expected: 3,
		},
		{
			name:     "search bell and bs",
			str:      "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x09\x0a",
			cursor:   0,
			pattern:  `\a\b\v`,
			forward:  true,
			expected: 7,
		},
		{
			name:     "search tab",
			str:      "\x06\x07\x08\x09\x10\x11",
			cursor:   0,
			pattern:  `\t`,
			forward:  true,
			expected: 3,
		},
		{
			name:     "search escape character",
			str:      `ab\cd\\e`,
			cursor:   0,
			pattern:  `\\\`,
			forward:  true,
			expected: 5,
		},
		{
			name:     "search unicode",
			str:      "\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf",
			cursor:   0,
			pattern:  `\u3061\u306F`,
			forward:  true,
			expected: 9,
		},
		{
			name:     "search unicode in supplementary multilingual plane",
			str:      "\U0001F604\U0001F606\U0001F60E\U0001F60D\U0001F642",
			cursor:   0,
			pattern:  `\U0001F60E\U0001F60D`,
			forward:  true,
			expected: 8,
		},
		{
			name:     "search hex literal",
			str:      "\x16\x27\x38\x49\x50\x61",
			cursor:   0,
			pattern:  `0x38495`,
			forward:  true,
			expected: 2,
		},
		{
			name:     "search bin literal",
			str:      "\x16\x27\x38\x48\x50\x61",
			cursor:   0,
			pattern:  `0b0011100001001`,
			forward:  true,
			expected: 2,
		},
		{
			name:     "search text starting with 0",
			str:      "432101234",
			cursor:   0,
			pattern:  `0123`,
			forward:  true,
			expected: 4,
		},
	}
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			s := NewSearcher(strings.NewReader(testCase.str))
			ch := s.Search(testCase.cursor, testCase.pattern, testCase.forward)
			switch x := (<-ch).(type) {
			case error:
				if testCase.err == nil {
					t.Error(x)
				} else if x != testCase.err {
					t.Errorf("Error should be %v but got %v", testCase.err, x)
				}
			case int64:
				if x != testCase.expected {
					t.Errorf("Search result should be %d but got %d", testCase.expected, x)
				}
			}
		})
	}
}
