package main

import (
	"os"
	"os/exec"
	"strings"
	"testing"
)

func must[T any](t T, err error) T {
	if err != nil {
		panic(err)
	}
	return t
}

var defaultOptions options = options{
	tongue:    "  ",
	eyes:      "oo",
	leftEye:   "o",
	rightEye:  "o",
	wrapWidth: 40,
}

func TestGenerateCow(t *testing.T) {
	type args struct {
		cow string
		say string
		opt options
	}
	tests := []struct {
		name     string
		args     args
		expected string
	}{
		{
			name: "basic cow",
			args: args{
				cow: string(must(os.ReadFile("./cows/default.cow"))),
				say: "test text",
				opt: defaultOptions,
			},
			expected: string(must(os.ReadFile("./testdata/basic.cow"))),
		},
		{
			name: "multiline test",
			args: args{
				cow: string(must(os.ReadFile("./cows/default.cow"))),
				say: "test\ntext",
				opt: defaultOptions,
			},
			expected: string(must(os.ReadFile("./testdata/multiline.cow"))),
		},
		{
			name: "multiline with wordwrap test",
			args: args{
				cow: string(must(os.ReadFile("./cows/default.cow"))),
				say: "this is a long block of text.\n\n\nIt goes over many lines! It'll even word-wrap for us.",
				opt: defaultOptions,
			},
			expected: string(must(os.ReadFile("./testdata/longer_multiline.cow"))),
		},
		{
			name: "offset bubble",
			args: args{
				cow: string(must(os.ReadFile("./cows/dragon-and-cow.cow"))),
				say: "this is a long block of text.\n\n\nIt goes over many lines! It'll even word-wrap for us.",
				opt: defaultOptions,
			},
			expected: string(must(os.ReadFile("./testdata/dragon.cow"))),
		},
		{
			name: "leading space cow",
			args: args{
				cow: string(must(os.ReadFile("./cows/default.cow"))),
				say: " text",
				opt: defaultOptions,
			},
			expected: string(must(os.ReadFile("./testdata/leading_space.cow"))),
		},
		{
			name: "extra spaces cow",
			args: args{
				cow: string(must(os.ReadFile("./cows/default.cow"))),
				say: "some    text",
				opt: defaultOptions,
			},
			expected: string(must(os.ReadFile("./testdata/extra_spaces.cow"))),
		},
		{
			name: "vertical tab cow",
			args: args{
				cow: string(must(os.ReadFile("./cows/default.cow"))),
				say: "some\vtext",
				opt: defaultOptions,
			},
			expected: string(must(os.ReadFile("./testdata/extra_spaces.cow"))),
		},
		{
			name: "a word that's too long cow",
			args: args{
				cow: string(must(os.ReadFile("./cows/default.cow"))),
				say: "00000000000000000000000000000000000000000000000000 11",
				opt: defaultOptions,
			},
			expected: string(must(os.ReadFile("./testdata/too_long.cow"))),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			out := generateCow(tt.args.cow, tt.args.say, tt.args.opt)
			if out != tt.expected {
				t.Logf("expected: \n%v", tt.expected)
				t.Logf("got: \n%v", out)
				t.Fatal()
			}
		})
	}
}

// fuzz testing requires perl to be installed and in the PATH
func FuzzCow(f *testing.F) {
	cowString := string(must(os.ReadFile("./cows/default.cow")))
	f.Add("phrase")
	f.Add("phrase with many many words in it so that we get a word-wrap situation")
	f.Fuzz(func(t *testing.T, phrase string) {
		if strings.TrimSpace(phrase) != phrase || phrase == "" {
			// uninteresting degenerate cases where there's a bug in the perl
			t.Skip()
		}
		res := generateCow(cowString, phrase, options{tongue: "  ", eyes: "oo", wrapWidth: 40})
		cmd := exec.Command("perl", "./perl/cowsay", "-f", "./perl/cows/default.cow")
		cmd.Stdin = strings.NewReader(phrase)
		var out strings.Builder
		cmd.Stdout = &out
		err := cmd.Run()
		if err != nil {
			t.Error(err)
		}
		if res != out.String() {
			t.Errorf("mismatch between perl and go.\nPerl:\n%s\ngo:\n%s", out.String(), res)
		}
	})
}
