// Copyright 2023 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 main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/build/constraint"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"golang.org/x/tools/internal/bisect"
	"golang.org/x/tools/internal/diffp"
	"golang.org/x/tools/txtar"
)

var update = flag.Bool("update", false, "update testdata with new stdout/stderr")

func Test(t *testing.T) {
	files, err := filepath.Glob("testdata/*.txt")
	if err != nil {
		t.Fatal(err)
	}
	for _, file := range files {
		t.Run(strings.TrimSuffix(filepath.Base(file), ".txt"), func(t *testing.T) {
			data, err := os.ReadFile(file)
			if err != nil {
				t.Fatal(err)
			}
			a := txtar.Parse(data)
			var wantStdout, wantStderr []byte
			files := a.Files
			if len(files) > 0 && files[0].Name == "stdout" {
				wantStdout = files[0].Data
				files = files[1:]
			}
			if len(files) > 0 && files[0].Name == "stderr" {
				wantStderr = files[0].Data
				files = files[1:]
			}
			if len(files) > 0 {
				t.Fatalf("unexpected txtar entry: %s", files[0].Name)
			}

			var tt struct {
				Fail   string
				Bisect Bisect
			}
			if err := json.Unmarshal(a.Comment, &tt); err != nil {
				t.Fatal(err)
			}

			expr, err := constraint.Parse("//go:build " + tt.Fail)
			if err != nil {
				t.Fatalf("invalid Cmd: %v", err)
			}

			rnd := rand.New(rand.NewSource(1))
			b := &tt.Bisect
			b.Cmd = "test"
			b.Args = []string{"PATTERN"}
			var stdout, stderr bytes.Buffer
			b.Stdout = &stdout
			b.Stderr = &stderr
			b.TestRun = func(env []string, cmd string, args []string) (out []byte, err error) {
				pattern := args[0]
				m, err := bisect.New(pattern)
				if err != nil {
					t.Fatal(err)
				}
				have := make(map[string]bool)
				for i, color := range colors {
					if m.ShouldEnable(uint64(i)) {
						have[color] = true
					}
					if m.ShouldReport(uint64(i)) {
						out = fmt.Appendf(out, "%s %s\n", color, bisect.Marker(uint64(i)))
					}
				}
				err = nil
				if eval(rnd, expr, have) {
					err = fmt.Errorf("failed")
				}
				return out, err
			}

			if !b.Search() {
				stderr.WriteString("<bisect failed>\n")
			}
			rewrite := false
			if !bytes.Equal(stdout.Bytes(), wantStdout) {
				if *update {
					rewrite = true
				} else {
					t.Errorf("incorrect stdout: %s", diffp.Diff("have", stdout.Bytes(), "want", wantStdout))
				}
			}
			if !bytes.Equal(stderr.Bytes(), wantStderr) {
				if *update {
					rewrite = true
				} else {
					t.Errorf("incorrect stderr: %s", diffp.Diff("have", stderr.Bytes(), "want", wantStderr))
				}
			}
			if rewrite {
				a.Files = []txtar.File{{Name: "stdout", Data: stdout.Bytes()}, {Name: "stderr", Data: stderr.Bytes()}}
				err := os.WriteFile(file, txtar.Format(a), 0666)
				if err != nil {
					t.Fatal(err)
				}
				t.Logf("updated %s", file)
			}
		})
	}
}

func eval(rnd *rand.Rand, z constraint.Expr, have map[string]bool) bool {
	switch z := z.(type) {
	default:
		panic(fmt.Sprintf("unexpected type %T", z))
	case *constraint.NotExpr:
		return !eval(rnd, z.X, have)
	case *constraint.AndExpr:
		return eval(rnd, z.X, have) && eval(rnd, z.Y, have)
	case *constraint.OrExpr:
		return eval(rnd, z.X, have) || eval(rnd, z.Y, have)
	case *constraint.TagExpr:
		if z.Tag == "random" {
			return rnd.Intn(2) == 1
		}
		return have[z.Tag]
	}
}

var colors = strings.Fields(`
	aliceblue
	amaranth
	amber
	amethyst
	applegreen
	applered
	apricot
	aquamarine
	azure
	babyblue
	beige
	brickred
	black
	blue
	bluegreen
	blueviolet
	blush
	bronze
	brown
	burgundy
	byzantium
	carmine
	cerise
	cerulean
	champagne
	chartreusegreen
	chocolate
	cobaltblue
	coffee
	copper
	coral
	crimson
	cyan
	desertsand
	electricblue
	emerald
	erin
	gold
	gray
	green
	harlequin
	indigo
	ivory
	jade
	junglegreen
	lavender
	lemon
	lilac
	lime
	magenta
	magentarose
	maroon
	mauve
	navyblue
	ochre
	olive
	orange
	orangered
	orchid
	peach
	pear
	periwinkle
	persianblue
	pink
	plum
	prussianblue
	puce
	purple
	raspberry
	red
	redviolet
	rose
	ruby
	salmon
	sangria
	sapphire
	scarlet
	silver
	slategray
	springbud
	springgreen
	tan
	taupe
	teal
	turquoise
	ultramarine
	violet
	viridian
	white
	yellow
`)
