// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package hcl

import (
	"bufio"
	"reflect"
	"testing"

	"github.com/davecgh/go-spew/spew"
)

func TestPosScanner(t *testing.T) {
	tests := map[string]struct {
		Input    string
		Want     []Range
		WantToks [][]byte
	}{
		"empty": {
			"",
			[]Range{},
			[][]byte{},
		},
		"single line": {
			"hello",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 5, Line: 1, Column: 6},
				},
			},
			[][]byte{
				[]byte("hello"),
			},
		},
		"single line with trailing UNIX newline": {
			"hello\n",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 5, Line: 1, Column: 6},
				},
			},
			[][]byte{
				[]byte("hello"),
			},
		},
		"single line with trailing Windows newline": {
			"hello\r\n",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 5, Line: 1, Column: 6},
				},
			},
			[][]byte{
				[]byte("hello"),
			},
		},
		"two lines with UNIX newline": {
			"hello\nworld",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 5, Line: 1, Column: 6},
				},
				{
					Start: Pos{Byte: 6, Line: 2, Column: 1},
					End:   Pos{Byte: 11, Line: 2, Column: 6},
				},
			},
			[][]byte{
				[]byte("hello"),
				[]byte("world"),
			},
		},
		"two lines with Windows newline": {
			"hello\r\nworld",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 5, Line: 1, Column: 6},
				},
				{
					Start: Pos{Byte: 7, Line: 2, Column: 1},
					End:   Pos{Byte: 12, Line: 2, Column: 6},
				},
			},
			[][]byte{
				[]byte("hello"),
				[]byte("world"),
			},
		},
		"blank line with UNIX newlines": {
			"hello\n\nworld",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 5, Line: 1, Column: 6},
				},
				{
					Start: Pos{Byte: 6, Line: 2, Column: 1},
					End:   Pos{Byte: 6, Line: 2, Column: 1},
				},
				{
					Start: Pos{Byte: 7, Line: 3, Column: 1},
					End:   Pos{Byte: 12, Line: 3, Column: 6},
				},
			},
			[][]byte{
				[]byte("hello"),
				[]byte(""),
				[]byte("world"),
			},
		},
		"blank line with Windows newlines": {
			"hello\r\n\r\nworld",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 5, Line: 1, Column: 6},
				},
				{
					Start: Pos{Byte: 7, Line: 2, Column: 1},
					End:   Pos{Byte: 7, Line: 2, Column: 1},
				},
				{
					Start: Pos{Byte: 9, Line: 3, Column: 1},
					End:   Pos{Byte: 14, Line: 3, Column: 6},
				},
			},
			[][]byte{
				[]byte("hello"),
				[]byte(""),
				[]byte("world"),
			},
		},
		"two lines with combiner and UNIX newline": {
			"foo \U0001f469\U0001f3ff bar\nbaz",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 16, Line: 1, Column: 10},
				},
				{
					Start: Pos{Byte: 17, Line: 2, Column: 1},
					End:   Pos{Byte: 20, Line: 2, Column: 4},
				},
			},
			[][]byte{
				[]byte("foo \U0001f469\U0001f3ff bar"),
				[]byte("baz"),
			},
		},
		"two lines with combiner and Windows newline": {
			"foo \U0001f469\U0001f3ff bar\r\nbaz",
			[]Range{
				{
					Start: Pos{Byte: 0, Line: 1, Column: 1},
					End:   Pos{Byte: 16, Line: 1, Column: 10},
				},
				{
					Start: Pos{Byte: 18, Line: 2, Column: 1},
					End:   Pos{Byte: 21, Line: 2, Column: 4},
				},
			},
			[][]byte{
				[]byte("foo \U0001f469\U0001f3ff bar"),
				[]byte("baz"),
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			src := []byte(test.Input)
			sc := NewRangeScanner(src, "", bufio.ScanLines)
			got := make([]Range, 0)
			gotToks := make([][]byte, 0)
			for sc.Scan() {
				got = append(got, sc.Range())
				gotToks = append(gotToks, sc.Bytes())
			}
			if sc.Err() != nil {
				t.Fatalf("unexpected error: %s", sc.Err())
			}
			if !reflect.DeepEqual(got, test.Want) {
				t.Errorf("incorrect ranges\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(test.Want))
			}
			if !reflect.DeepEqual(gotToks, test.WantToks) {
				t.Errorf("incorrect tokens\ngot: %swant: %s", spew.Sdump(gotToks), spew.Sdump(test.WantToks))
			}
		})
	}
}
