package tea

import "testing"

func TestMouseEvent_String(t *testing.T) {
	tt := []struct {
		name     string
		event    MouseEvent
		expected string
	}{
		{
			name:     "unknown",
			event:    MouseEvent{Type: MouseUnknown},
			expected: "unknown",
		},
		{
			name:     "left",
			event:    MouseEvent{Type: MouseLeft},
			expected: "left",
		},
		{
			name:     "right",
			event:    MouseEvent{Type: MouseRight},
			expected: "right",
		},
		{
			name:     "middle",
			event:    MouseEvent{Type: MouseMiddle},
			expected: "middle",
		},
		{
			name:     "release",
			event:    MouseEvent{Type: MouseRelease},
			expected: "release",
		},
		{
			name:     "wheel up",
			event:    MouseEvent{Type: MouseWheelUp},
			expected: "wheel up",
		},
		{
			name:     "wheel down",
			event:    MouseEvent{Type: MouseWheelDown},
			expected: "wheel down",
		},
		{
			name:     "motion",
			event:    MouseEvent{Type: MouseMotion},
			expected: "motion",
		},
		{
			name: "alt+left",
			event: MouseEvent{
				Type: MouseLeft,
				Alt:  true,
			},
			expected: "alt+left",
		},
		{
			name: "ctrl+left",
			event: MouseEvent{
				Type: MouseLeft,
				Ctrl: true,
			},
			expected: "ctrl+left",
		},
		{
			name: "ctrl+alt+left",
			event: MouseEvent{
				Type: MouseLeft,
				Alt:  true,
				Ctrl: true,
			},
			expected: "ctrl+alt+left",
		},
		{
			name: "ignore coordinates",
			event: MouseEvent{
				X:    100,
				Y:    200,
				Type: MouseLeft,
			},
			expected: "left",
		},
		{
			name: "broken type",
			event: MouseEvent{
				Type: MouseEventType(-1000),
			},
			expected: "",
		},
	}

	for i := range tt {
		tc := tt[i]

		t.Run(tc.name, func(t *testing.T) {
			actual := tc.event.String()

			if tc.expected != actual {
				t.Fatalf("expected %q but got %q",
					tc.expected,
					actual,
				)
			}
		})
	}
}

func TestParseX10MouseEvent(t *testing.T) {
	encode := func(b byte, x, y int) []byte {
		return []byte{
			'\x1b',
			'[',
			'M',
			byte(32) + b,
			byte(x + 32 + 1),
			byte(y + 32 + 1),
		}
	}

	tt := []struct {
		name     string
		buf      []byte
		expected MouseEvent
	}{
		// Position.
		{
			name: "zero position",
			buf:  encode(0b0010_0000, 0, 0),
			expected: MouseEvent{
				X:    0,
				Y:    0,
				Type: MouseLeft,
			},
		},
		{
			name: "max position",
			buf:  encode(0b0010_0000, 222, 222), // Because 255 (max int8) - 32 - 1.
			expected: MouseEvent{
				X:    222,
				Y:    222,
				Type: MouseLeft,
			},
		},
		// Simple.
		{
			name: "left",
			buf:  encode(0b0000_0000, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseLeft,
			},
		},
		{
			name: "left in motion",
			buf:  encode(0b0010_0000, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseLeft,
			},
		},
		{
			name: "middle",
			buf:  encode(0b0000_0001, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseMiddle,
			},
		},
		{
			name: "middle in motion",
			buf:  encode(0b0010_0001, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseMiddle,
			},
		},
		{
			name: "right",
			buf:  encode(0b0000_0010, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseRight,
			},
		},
		{
			name: "right in motion",
			buf:  encode(0b0010_0010, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseRight,
			},
		},
		{
			name: "motion",
			buf:  encode(0b0010_0011, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseMotion,
			},
		},
		{
			name: "wheel up",
			buf:  encode(0b0100_0000, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseWheelUp,
			},
		},
		{
			name: "wheel down",
			buf:  encode(0b0100_0001, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseWheelDown,
			},
		},
		{
			name: "release",
			buf:  encode(0b0000_0011, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseRelease,
			},
		},
		// Combinations.
		{
			name: "alt+right",
			buf:  encode(0b0010_1010, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseRight,
				Alt:  true,
			},
		},
		{
			name: "ctrl+right",
			buf:  encode(0b0011_0010, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseRight,
				Ctrl: true,
			},
		},
		{
			name: "ctrl+alt+right",
			buf:  encode(0b0011_1010, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseRight,
				Alt:  true,
				Ctrl: true,
			},
		},
		{
			name: "alt+wheel down",
			buf:  encode(0b0100_1001, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseWheelDown,
				Alt:  true,
			},
		},
		{
			name: "ctrl+wheel down",
			buf:  encode(0b0101_0001, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseWheelDown,
				Ctrl: true,
			},
		},
		{
			name: "ctrl+alt+wheel down",
			buf:  encode(0b0101_1001, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseWheelDown,
				Alt:  true,
				Ctrl: true,
			},
		},
		// Unknown.
		{
			name: "wheel with unknown bit",
			buf:  encode(0b0100_0010, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseUnknown,
			},
		},
		{
			name: "unknown with modifier",
			buf:  encode(0b0100_1010, 32, 16),
			expected: MouseEvent{
				X:    32,
				Y:    16,
				Type: MouseUnknown,
				Alt:  true,
			},
		},
		// Overflow position.
		{
			name: "overflow position",
			buf:  encode(0b0010_0000, 250, 223), // Because 255 (max int8) - 32 - 1.
			expected: MouseEvent{
				X:    -6,
				Y:    -33,
				Type: MouseLeft,
			},
		},
	}

	for i := range tt {
		tc := tt[i]

		t.Run(tc.name, func(t *testing.T) {
			actual, err := parseX10MouseEvent(tc.buf)
			if err != nil {
				t.Fatalf("unexpected error: %v",
					err,
				)
			}

			if tc.expected != actual {
				t.Fatalf("expected %#v but got %#v",
					tc.expected,
					actual,
				)
			}
		})
	}
}

func TestParseX10MouseEvent_error(t *testing.T) {
	tt := []struct {
		name string
		buf  []byte
	}{
		{
			name: "empty buf",
			buf:  nil,
		},
		{
			name: "wrong high bit",
			buf:  []byte("\x1a[M@A1"),
		},
		{
			name: "short buf",
			buf:  []byte("\x1b[M@A"),
		},
		{
			name: "long buf",
			buf:  []byte("\x1b[M@A11"),
		},
	}

	for i := range tt {
		tc := tt[i]

		t.Run(tc.name, func(t *testing.T) {
			_, err := parseX10MouseEvent(tc.buf)

			if err == nil {
				t.Fatalf("expected error but got nil")
			}
		})
	}
}
