// Copyright 2016 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 syntax

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"testing"
)

func TestPrint(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}

	ast, _ := ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics)

	if ast != nil {
		Fprint(testOut(), ast, LineForm)
		fmt.Println()
	}
}

type shortBuffer struct {
	buf []byte
}

func (w *shortBuffer) Write(data []byte) (n int, err error) {
	w.buf = append(w.buf, data...)
	n = len(data)
	if len(w.buf) > 10 {
		err = io.ErrShortBuffer
	}
	return
}

func TestPrintError(t *testing.T) {
	const src = "package p; var x int"
	ast, err := Parse(nil, strings.NewReader(src), nil, nil, 0)
	if err != nil {
		t.Fatal(err)
	}

	var buf shortBuffer
	_, err = Fprint(&buf, ast, 0)
	if err == nil || err != io.ErrShortBuffer {
		t.Errorf("got err = %s, want %s", err, io.ErrShortBuffer)
	}
}

var stringTests = [][2]string{
	dup("package p"),
	dup("package p; type _ int; type T1 = struct{}; type ( _ *struct{}; T2 = float32 )"),

	// generic type declarations
	dup("package p; type _[T any] struct{}"),
	dup("package p; type _[A, B, C interface{m()}] struct{}"),
	dup("package p; type _[T any, A, B, C interface{m()}, X, Y, Z interface{~int}] struct{}"),

	dup("package p; type _[P *T,] struct{}"),
	dup("package p; type _[P *T, _ any] struct{}"),
	{"package p; type _[P (*T),] struct{}", "package p; type _[P *T,] struct{}"},
	{"package p; type _[P (*T), _ any] struct{}", "package p; type _[P *T, _ any] struct{}"},
	{"package p; type _[P (T),] struct{}", "package p; type _[P T] struct{}"},
	{"package p; type _[P (T), _ any] struct{}", "package p; type _[P T, _ any] struct{}"},

	dup("package p; type _[P *struct{}] struct{}"),
	{"package p; type _[P (*struct{})] struct{}", "package p; type _[P *struct{}] struct{}"},
	{"package p; type _[P ([]int)] struct{}", "package p; type _[P []int] struct{}"},

	dup("package p; type _ [P(T)]struct{}"),
	dup("package p; type _ [P((T))]struct{}"),
	dup("package p; type _ [P * *T]struct{}"),
	dup("package p; type _ [P * T]struct{}"),
	dup("package p; type _ [P(*T)]struct{}"),
	dup("package p; type _ [P(**T)]struct{}"),
	dup("package p; type _ [P * T - T]struct{}"),

	// array type declarations
	dup("package p; type _ [P * T]struct{}"),
	dup("package p; type _ [P * T - T]struct{}"),

	// generic function declarations
	dup("package p; func _[T any]()"),
	dup("package p; func _[A, B, C interface{m()}]()"),
	dup("package p; func _[T any, A, B, C interface{m()}, X, Y, Z interface{~int}]()"),

	// methods with generic receiver types
	dup("package p; func (R[T]) _()"),
	dup("package p; func (*R[A, B, C]) _()"),
	dup("package p; func (_ *R[A, B, C]) _()"),

	// type constraint literals with elided interfaces
	dup("package p; func _[P ~int, Q int | string]() {}"),
	dup("package p; func _[P struct{f int}, Q *P]() {}"),

	// channels
	dup("package p; type _ chan chan int"),
	dup("package p; type _ chan (<-chan int)"),
	dup("package p; type _ chan chan<- int"),

	dup("package p; type _ <-chan chan int"),
	dup("package p; type _ <-chan <-chan int"),
	dup("package p; type _ <-chan chan<- int"),

	dup("package p; type _ chan<- chan int"),
	dup("package p; type _ chan<- <-chan int"),
	dup("package p; type _ chan<- chan<- int"),

	// TODO(gri) expand
}

func TestPrintString(t *testing.T) {
	for _, test := range stringTests {
		ast, err := Parse(nil, strings.NewReader(test[0]), nil, nil, AllowGenerics)
		if err != nil {
			t.Error(err)
			continue
		}
		if got := String(ast); got != test[1] {
			t.Errorf("%q: got %q", test[1], got)
		}
	}
}

func testOut() io.Writer {
	if testing.Verbose() {
		return os.Stdout
	}
	return ioutil.Discard
}

func dup(s string) [2]string { return [2]string{s, s} }

var exprTests = [][2]string{
	// basic type literals
	dup("x"),
	dup("true"),
	dup("42"),
	dup("3.1415"),
	dup("2.71828i"),
	dup(`'a'`),
	dup(`"foo"`),
	dup("`bar`"),

	// func and composite literals
	dup("func() {}"),
	dup("[]int{}"),
	{"func(x int) complex128 { return 0 }", "func(x int) complex128 {…}"},
	{"[]int{1, 2, 3}", "[]int{…}"},

	// type expressions
	dup("[1 << 10]byte"),
	dup("[]int"),
	dup("*int"),
	dup("struct{x int}"),
	dup("func()"),
	dup("func(int, float32) string"),
	dup("interface{m()}"),
	dup("interface{m() string; n(x int)}"),
	dup("interface{~int}"),
	dup("interface{~int | ~float64 | ~string}"),
	dup("interface{~int; m()}"),
	dup("interface{~int | ~float64 | ~string; m() string; n(x int)}"),
	dup("map[string]int"),
	dup("chan E"),
	dup("<-chan E"),
	dup("chan<- E"),

	// new interfaces
	dup("interface{int}"),
	dup("interface{~int}"),
	dup("interface{~int}"),
	dup("interface{int | string}"),
	dup("interface{~int | ~string; float64; m()}"),
	dup("interface{~a | ~b | ~c; ~int | ~string; float64; m()}"),
	dup("interface{~T[int, string] | string}"),

	// non-type expressions
	dup("(x)"),
	dup("x.f"),
	dup("a[i]"),

	dup("s[:]"),
	dup("s[i:]"),
	dup("s[:j]"),
	dup("s[i:j]"),
	dup("s[:j:k]"),
	dup("s[i:j:k]"),

	dup("x.(T)"),

	dup("x.([10]int)"),
	dup("x.([...]int)"),

	dup("x.(struct{})"),
	dup("x.(struct{x int; y, z float32; E})"),

	dup("x.(func())"),
	dup("x.(func(x int))"),
	dup("x.(func() int)"),
	dup("x.(func(x, y int, z float32) (r int))"),
	dup("x.(func(a, b, c int))"),
	dup("x.(func(x ...T))"),

	dup("x.(interface{})"),
	dup("x.(interface{m(); n(x int); E})"),
	dup("x.(interface{m(); n(x int) T; E; F})"),

	dup("x.(map[K]V)"),

	dup("x.(chan E)"),
	dup("x.(<-chan E)"),
	dup("x.(chan<- chan int)"),
	dup("x.(chan<- <-chan int)"),
	dup("x.(<-chan chan int)"),
	dup("x.(chan (<-chan int))"),

	dup("f()"),
	dup("f(x)"),
	dup("int(x)"),
	dup("f(x, x + y)"),
	dup("f(s...)"),
	dup("f(a, s...)"),

	dup("*x"),
	dup("&x"),
	dup("x + y"),
	dup("x + y << (2 * s)"),
}

func TestShortString(t *testing.T) {
	for _, test := range exprTests {
		src := "package p; var _ = " + test[0]
		ast, err := Parse(nil, strings.NewReader(src), nil, nil, AllowGenerics)
		if err != nil {
			t.Errorf("%s: %s", test[0], err)
			continue
		}
		x := ast.DeclList[0].(*VarDecl).Values
		if got := String(x); got != test[1] {
			t.Errorf("%s: got %s, want %s", test[0], got, test[1])
		}
	}
}
