package handler

import (
	"reflect"
	"strings"
	"testing"

	lsp "github.com/sourcegraph/go-lsp"
)

func TestCreateSourceMaps(t *testing.T) {
	input := `#include <Arduino.h>
#line 1 "sketch_july2a.ino"
#line 1 "sketch_july2a.ino"

#line 2 "sketch_july2a.ino"
void setup();
#line 7 "sketch_july2a.ino"
void loop();
#line 2 "sketch_july2a.ino"
void setup() {
	// put your setup code here, to run once:
	
}

void loop() {
	// put your main code here, to run repeatedly:
	
}
`
	sourceLineMap, targetLineMap := createSourceMaps(strings.NewReader(input))
	if !reflect.DeepEqual(sourceLineMap, map[int]int{
		3: 0, 5: 1, 7: 6, 9: 1, 10: 2, 11: 3, 12: 4, 13: 5, 14: 6, 15: 7, 16: 8, 17: 9, 18: 10,
	}) {
		t.Error(sourceLineMap)
	}
	if !reflect.DeepEqual(targetLineMap, map[int]int{
		0: 3, 1: 9, 2: 10, 3: 11, 4: 12, 5: 13, 6: 14, 7: 15, 8: 16, 9: 17, 10: 18,
	}) {
		t.Error(targetLineMap)
	}
}

func TestUpdateSourceMaps1(t *testing.T) {
	targetLineMap := map[int]int{0: 1, 1: 2, 2: 0, 3: 5, 4: 3, 5: 4}
	sourceLineMap := make(map[int]int)
	for s, t := range targetLineMap {
		sourceLineMap[t] = s
	}
	updateSourceMaps(sourceLineMap, targetLineMap, 0, 1, "foo\nbar\nbaz")
	if !reflect.DeepEqual(targetLineMap, map[int]int{0: 1, 1: 2, 2: 3, 3: 4, 4: 0, 5: 7, 6: 5, 7: 6}) {
		t.Error(targetLineMap)
	}
	if !reflect.DeepEqual(sourceLineMap, map[int]int{0: 4, 1: 0, 2: 1, 3: 2, 4: 3, 5: 6, 6: 7, 7: 5}) {
		t.Error(sourceLineMap)
	}
}

func TestUpdateSourceMaps2(t *testing.T) {
	targetLineMap := map[int]int{0: 1, 1: 2, 2: 0, 3: 5, 4: 3, 5: 4}
	sourceLineMap := make(map[int]int)
	for s, t := range targetLineMap {
		sourceLineMap[t] = s
	}
	updateSourceMaps(sourceLineMap, targetLineMap, 2, 1, "foo")
	if !reflect.DeepEqual(targetLineMap, map[int]int{0: 0, 1: 1, 2: 2, 3: 3}) {
		t.Error(targetLineMap)
	}
	if !reflect.DeepEqual(sourceLineMap, map[int]int{0: 0, 1: 1, 2: 2, 3: 3}) {
		t.Error(sourceLineMap)
	}
}

func TestApplyTextChange(t *testing.T) {
	tests := []struct {
		InitialText string
		Range       lsp.Range
		Insertion   string
		Expectation string
		Err         error
	}{
		{
			"foo\nbar\nbaz\n!",
			lsp.Range{
				Start: lsp.Position{Line: 1, Character: 1},
				End:   lsp.Position{Line: 2, Character: 2},
			},
			"i",
			"foo\nbiz\n!",
			nil,
		},
		{
			"foo\nbar\nbaz\n!",
			lsp.Range{
				Start: lsp.Position{Line: 1, Character: 1},
				End:   lsp.Position{Line: 1, Character: 2},
			},
			"ee",
			"foo\nbeer\nbaz\n!",
			nil,
		},
		{
			"foo\nbar\nbaz\n!",
			lsp.Range{
				Start: lsp.Position{Line: 1, Character: 1},
				End:   lsp.Position{Line: 1, Character: 1},
			},
			"eer from the st",
			"foo\nbeer from the star\nbaz\n!",
			nil,
		},
		{
			"foo\nbar\nbaz\n!",
			lsp.Range{
				Start: lsp.Position{Line: 0, Character: 10},
				End:   lsp.Position{Line: 2, Character: 20},
			},
			"i",
			"fooi\n!",
			nil,
		},
		{
			"foo\nbar\nbaz\n!",
			lsp.Range{
				// out of range start offset
				Start: lsp.Position{Line: 0, Character: 100},
				End:   lsp.Position{Line: 2, Character: 0},
			},
			"i",
			"fooibaz\n!",
			nil,
		},
		{
			"foo\nbar\nbaz\n!",
			lsp.Range{
				// out of range start offset
				Start: lsp.Position{Line: 20, Character: 0},
				End:   lsp.Position{Line: 2, Character: 0},
			},
			"i",
			"",
			OutOfRangeError{13, lsp.Position{Line: 20, Character: 0}},
		},
		{
			"foo\nbar\nbaz\n!",
			lsp.Range{
				// out of range start offset
				Start: lsp.Position{Line: 0, Character: 0},
				End:   lsp.Position{Line: 20, Character: 0},
			},
			"i",
			"",
			OutOfRangeError{13, lsp.Position{Line: 20, Character: 0}},
		},
	}

	for _, test := range tests {
		initial := strings.ReplaceAll(test.InitialText, "\n", "\\n")
		insertion := strings.ReplaceAll(test.Insertion, "\n", "\\n")
		expectation := strings.ReplaceAll(test.Expectation, "\n", "\\n")

		t.Logf("applyTextChange(\"%s\", %v, \"%s\") == \"%s\"", initial, test.Range, insertion, expectation)
		act, err := applyTextChange(test.InitialText, test.Range, test.Insertion)
		if act != test.Expectation {
			t.Errorf("applyTextChange(\"%s\", %v, \"%s\") != \"%s\", got \"%s\"", initial, test.Range, insertion, expectation, strings.ReplaceAll(act, "\n", "\\n"))
		}
		if err != test.Err {
			t.Errorf("applyTextChange(\"%s\", %v, \"%s\") error != %v, got %v instead", initial, test.Range, insertion, test.Err, err)
		}
	}
}

func TestGetOffset(t *testing.T) {
	tests := []struct {
		Text string
		Line int
		Char int
		Exp  int
		Err  error
	}{
		{"foo\nfoobar\nbaz", 0, 0, 0, nil},
		{"foo\nfoobar\nbaz", 1, 0, 4, nil},
		{"foo\nfoobar\nbaz", 1, 3, 7, nil},
		{"foo\nba\nr\nbaz\n!", 3, 0, 9, nil},
		{"foo\nba\nr\nbaz\n!", 1, 10, 6, nil},
		{"foo\nba\nr\nbaz\n!", -1, 0, -1, OutOfRangeError{14, lsp.Position{Line: -1, Character: 0}}},
		{"foo\nba\nr\nbaz\n!", 4, 20, -1, OutOfRangeError{14, lsp.Position{Line: 4, Character: 20}}},
	}

	for _, test := range tests {
		st := strings.Replace(test.Text, "\n", "\\n", -1)

		t.Logf("getOffset(\"%s\", {Line: %d, Character: %d}) == %d", st, test.Line, test.Char, test.Exp)
		act, err := getOffset(test.Text, lsp.Position{Line: test.Line, Character: test.Char})
		if act != test.Exp {
			t.Errorf("getOffset(\"%s\", {Line: %d, Character: %d}) != %d, got %d instead", st, test.Line, test.Char, test.Exp, act)
		}
		if err != test.Err {
			t.Errorf("getOffset(\"%s\", {Line: %d, Character: %d}) error != %v, got %v instead", st, test.Line, test.Char, test.Err, err)
		}
	}
}

func TestGetLineOffset(t *testing.T) {
	tests := []struct {
		Text   string
		Line   int
		Offset int
	}{
		{"foo\nfoobar\nbaz", 0, 0},
		{"foo\nfoobar\nbaz", 1, 4},
		{"foo\nfoobar\nbaz", 2, 11},
		{"foo\nfoobar\nbaz", 3, -1},
		{"foo\nba\nr\nbaz\n!", 3, 9},
		{"foo\nba\nr\nbaz\n!", -1, -1},
		{"foo\nba\nr\nbaz\n!", 20, -1},
	}

	for _, test := range tests {
		st := strings.Replace(test.Text, "\n", "\\n", -1)

		t.Logf("getLineOffset(\"%s\", %d) == %d", st, test.Line, test.Offset)
		act := getLineOffset(test.Text, test.Line)
		if act != test.Offset {
			t.Errorf("getLineOffset(\"%s\", %d) != %d, got %d instead", st, test.Line, test.Offset, act)
		}
	}
}