package parser

import (
	"strings"
	"testing"

	"github.com/protocolbuffers/txtpbfmt/ast"
)

func findNode(name string, nodes []*ast.Node) *ast.Node {
	for _, n := range nodes {
		if n.Name == name {
			return n
		}
		if found := findNode(name, n.Children); found != nil {
			return found
		}
	}
	return nil
}

func mkString(lines ...string) string {
	return strings.Join(lines, "\n")
}

func TestParsePositions(t *testing.T) {
	for i, tc := range []struct {
		in                 string
		startByte, endByte int
		start, end         ast.Position
	}{
		{
			in:        `trackme: "foo"` + "\n",
			startByte: 0,
		},
		{
			in: mkString(
				"# top",      // 5 bytes + newline
				"trackme: 4", // 10 bytes + newline
			),
			startByte: 0,
		},
		{
			in: mkString(
				"# top",        // 5 bytes + newline
				"unrelated: 1", // 12 bytes + newline
				"trackme: 4",
			),
			startByte: 19,
		},
		{
			in:        "trackme: {\n}",
			startByte: 0, endByte: 11,
			start: ast.Position{Line: 1, Column: 1},
			end:   ast.Position{Line: 2},
		},
		{
			in: mkString(
				"# top",        // 5 bytes + newline
				"unrelated: 1", // 12 bytes + newline
				"",             // this already gets accounted for trackme
				"trackme: 4",
			),
			startByte: 19,
		},
		{
			in: mkString(
				"# top",        // 5 bytes + newline
				"unrelated: 1", // 12 bytes + newline
				"# boo",        // this already gets accounted for trackme
				"trackme: 4",
			),
			startByte: 19,
		},
		{
			in: mkString(
				"# top",        // 5 bytes + newline
				"unrelated: 1", // 12 bytes + newline
				"",             // this already gets accounted for trackme
				"# boo",
				"trackme: 4",
			),
			startByte: 19,
		},
		{
			in: mkString(
				"outer: {", // 8 bytes + newline
				"  foo: 1", // 8 bytes + newline
				"  trackme: 4",
				"}",
			),
			startByte: 18,
		},
		{
			in: mkString(
				"outer: {", // 8 bytes + newline
				"  foo: 1", // 8 bytes + newline
				"",         // acounted already for trackme
				"  # multiline desc",
				"  # for trackme",
				"  trackme: 4",
				"}",
			),
			startByte: 18,
		},
		{
			in: mkString(
				"trackme: {",   // 10 bytes + newline
				"  content: 1", // 12 bytes + newline
				"}",
			),
			startByte: 0, endByte: 24,
			start: ast.Position{Line: 1, Column: 1},
			end:   ast.Position{Line: 3},
		},
		{
			in: mkString(
				"outer: {",          // 8 bytes + newline
				"  a: 1",            // 6 bytes + newline
				"  trackme: {",      // 12 bytes + newline
				"    b: 2",          // 8 bytes + newline
				"    # end comment", // 17 bytes + newline
				"  }",
				"}",
			),
			startByte: 9 + 7, endByte: 9 + 7 + 13 + 9 + 18,
			start: ast.Position{Line: 3, Column: 1},
			end:   ast.Position{Line: 6},
		},
	} {
		nodes, err := Parse([]byte(tc.in))
		if err != nil {
			t.Fatal(err)
		}
		found := findNode("trackme", nodes)
		if found == nil {
			t.Fatalf("%d. TestParsePositions no node 'trackme' found", i)
		}
		if (uint32(tc.startByte) != found.Start.Byte) ||
			(uint32(tc.endByte) != found.End.Byte) ||
			(tc.start.Line > 0 && found.Start.Line != tc.start.Line) ||
			(tc.start.Column > 0 && found.Start.Column != tc.start.Column) ||
			(tc.end.Line > 0 && found.End.Line != tc.end.Line) {
			t.Errorf("%d. TestParsePositions got = %+v, want = (%d, %d), (%d, %d, %d); input:\n%s",
				i,
				found,
				tc.startByte, tc.endByte,
				tc.start.Line, tc.start.Column, tc.end.Line,
				tc.in)
		}

	}
}
