// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package misc

import (
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"golang.org/x/tools/gopls/internal/lsp"
	"golang.org/x/tools/gopls/internal/lsp/protocol"
	. "golang.org/x/tools/gopls/internal/lsp/regtest"
	"golang.org/x/tools/internal/typeparams"
)

func TestBadURICrash_VSCodeIssue1498(t *testing.T) {
	const src = `
-- go.mod --
module example.com

go 1.12

-- main.go --
package main

func main() {}

`
	WithOptions(
		Modes(Default),
		Settings{"allExperiments": true},
	).Run(t, src, func(t *testing.T, env *Env) {
		params := &protocol.SemanticTokensParams{}
		const badURI = "http://foo"
		params.TextDocument.URI = badURI
		// This call panicked in the past: golang/vscode-go#1498.
		if _, err := env.Editor.Server.SemanticTokensFull(env.Ctx, params); err != nil {
			// Requests to an invalid URI scheme shouldn't result in an error, we
			// simply don't support this so return empty result. This could be
			// changed, but for now assert on the current behavior.
			t.Errorf("SemanticTokensFull(%q): %v", badURI, err)
		}
	})
}

// fix bug involving type parameters and regular parameters
// (golang/vscode-go#2527)
func TestSemantic_2527(t *testing.T) {
	if !typeparams.Enabled {
		t.Skip("type parameters are needed for this test")
	}
	// these are the expected types of identifiers in text order
	want := []result{
		{"package", "keyword", ""},
		{"foo", "namespace", ""},
		{"func", "keyword", ""},
		{"Add", "function", "definition deprecated"},
		{"T", "typeParameter", "definition"},
		{"int", "type", "defaultLibrary"},
		{"target", "parameter", "definition"},
		{"T", "typeParameter", ""},
		{"l", "parameter", "definition"},
		{"T", "typeParameter", ""},
		{"T", "typeParameter", ""},
		{"return", "keyword", ""},
		{"append", "function", "defaultLibrary"},
		{"l", "parameter", ""},
		{"target", "parameter", ""},
		{"for", "keyword", ""},
		{"range", "keyword", ""},
		{"l", "parameter", ""},
		{"return", "keyword", ""},
		{"nil", "variable", "readonly defaultLibrary"},
	}
	src := `
-- go.mod --
module example.com

go 1.19
-- main.go --
package foo
// Deprecated (for testing)
func Add[T int](target T, l []T) []T {
	return append(l, target)
	for range l {} // test coverage
	return nil
}
`
	WithOptions(
		Modes(Default),
		Settings{"semanticTokens": true},
	).Run(t, src, func(t *testing.T, env *Env) {
		env.OpenFile("main.go")
		env.AfterChange(
			Diagnostics(env.AtRegexp("main.go", "for range")),
		)
		p := &protocol.SemanticTokensParams{
			TextDocument: protocol.TextDocumentIdentifier{
				URI: env.Sandbox.Workdir.URI("main.go"),
			},
		}
		v, err := env.Editor.Server.SemanticTokensFull(env.Ctx, p)
		if err != nil {
			t.Fatal(err)
		}
		seen := interpret(v.Data, env.BufferText("main.go"))
		if x := cmp.Diff(want, seen); x != "" {
			t.Errorf("Semantic tokens do not match (-want +got):\n%s", x)
		}
	})

}

// fix inconsistency in TypeParameters
// https://github.com/golang/go/issues/57619
func TestSemantic_57619(t *testing.T) {
	if !typeparams.Enabled {
		t.Skip("type parameters are needed for this test")
	}
	src := `
-- go.mod --
module example.com

go 1.19
-- main.go --
package foo
type Smap[K int, V any] struct {
	Store map[K]V
}
func (s *Smap[K, V]) Get(k K) (V, bool) {
	v, ok := s.Store[k]
	return v, ok
}
func New[K int, V any]() Smap[K, V] {
	return Smap[K, V]{Store: make(map[K]V)}
}
`
	WithOptions(
		Modes(Default),
		Settings{"semanticTokens": true},
	).Run(t, src, func(t *testing.T, env *Env) {
		env.OpenFile("main.go")
		p := &protocol.SemanticTokensParams{
			TextDocument: protocol.TextDocumentIdentifier{
				URI: env.Sandbox.Workdir.URI("main.go"),
			},
		}
		v, err := env.Editor.Server.SemanticTokensFull(env.Ctx, p)
		if err != nil {
			t.Fatal(err)
		}
		seen := interpret(v.Data, env.BufferText("main.go"))
		for i, s := range seen {
			if (s.Token == "K" || s.Token == "V") && s.TokenType != "typeParameter" {
				t.Errorf("%d: expected K and V to be type parameters, but got %v", i, s)
			}
		}
	})
}

type result struct {
	Token     string
	TokenType string
	Mod       string
}

// human-readable version of the semantic tokens
// comment, string, number are elided
// (and in the future, maybe elide other things, like operators)
func interpret(x []uint32, contents string) []result {
	lines := strings.Split(contents, "\n")
	ans := []result{}
	line, col := 1, 1
	for i := 0; i < len(x); i += 5 {
		line += int(x[i])
		col += int(x[i+1])
		if x[i] != 0 { // new line
			col = int(x[i+1]) + 1 // 1-based column numbers
		}
		sz := x[i+2]
		t := semanticTypes[x[i+3]]
		if t == "comment" || t == "string" || t == "number" {
			continue
		}
		l := x[i+4]
		var mods []string
		for i, mod := range semanticModifiers {
			if l&(1<<i) != 0 {
				mods = append(mods, mod)
			}
		}
		// col is a utf-8 offset
		tok := lines[line-1][col-1 : col-1+int(sz)]
		ans = append(ans, result{tok, t, strings.Join(mods, " ")})
	}
	return ans
}

var (
	semanticTypes     = lsp.SemanticTypes()
	semanticModifiers = lsp.SemanticModifiers()
)
