package azalea_test

import (
	_ "embed"
	"reflect"
	"strings"
	"testing"
	"text/scanner"

	. "hakurei.app/internal/rosa/azalea"
)

//go:embed testdata/gcc.az
var sample string

func TestParse(t *testing.T) {
	t.Parallel()

	testCases := []struct {
		name string
		data string
		want []any
		err  error
	}{
		{"invalid", "}", nil, ExprError('}')},
		{"bad sep", "f{v?}", nil, TokenError{'=', '?'}},
		{"bad ident", "f{9}", nil, TokenError{scanner.Ident, scanner.Int}},
		{"share bad sep", "f { v,,v = v; }", nil, ExprError(',')},
		{"share missing sep", "f { v v }", nil, TokenError{',', scanner.Ident}},

		{"ident", `v`, []any{Ident("v")}, nil},
		{"concat", `f { v = v+"\xfd"+p{}+9; }`, []any{Func{
			Ident: "f",

			Args: []Arg{{K: []Ident{"v"}, V: Val{
				Ident("v"),
				String("\xfd"),
				Func{Ident: "p"},
				Int(9),
			}}},
		}}, nil},
		{"truncated string concat", `f { v = v+; }`, nil,
			ExprError(';')},

		{"empty pairs", `{}`, []any{[]KV(nil)}, nil},
		{"short kv", `{"\x00":v;}`, []any{[]KV{
			{K: "\x00", V: Val{Ident("v")}},
		}}, nil},
		{"truncated kv", `{"\x00"`, nil, ExprError(scanner.EOF)},
		{"ident kv", `{v="";}`, nil, ExprError(scanner.Ident)},

		{"empty array", `[]`, []any{Array(nil)}, nil},
		{"integer array", `[9]`, []any{Array{{Int(9)}}}, nil},
		{"short array", `[ "\x00" ]`, []any{
			Array{{String("\x00")}},
		}, nil},
		{"short array delim", `[ "\x00", ]`, []any{
			Array{{String("\x00")}},
		}, nil},
		{"missing array value", `[ "\x00", , v ]`, nil, ExprError(',')},
		{"missing array delimiter", `[ v0 v1 ]`, nil, TokenError{',', scanner.Ident}},
		{"truncated array", `[ "\x00"`, nil,
			ExprError(scanner.EOF)},

		{"gcc", sample, []any{Func{
			Ident:   Ident("gcc"),
			Package: true,

			Args: []Arg{
				{K: []Ident{Ident("description")}, V: Val{String("The GNU Compiler Collection")}},
				{K: []Ident{Ident("website")}, V: Val{String("https://www.gnu.org/software/gcc")}},
				{K: []Ident{Ident("anitya")}, V: Val{Int(6502)}},

				{K: []Ident{Ident("version")}, V: Val{String("16.1.0")}, R: true},
				{K: []Ident{Ident("source")}, V: Val{Func{
					Ident: Ident("remoteTar"),

					Args: []Arg{
						{K: []Ident{Ident("url")}, V: Val{
							String("https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/"),
							String("gcc-"),
							Ident("version"),
							String("/gcc-"),
							Ident("version"),
							String(".tar.gz"),
						}},
						{K: []Ident{Ident("checksum")}, V: Val{String("4ASoWbxaA2FW7PAB0zzHDPC5XnNhyaAyjtDPpGzceSLeYnEIXsNYZR3PA_Zu5P0K")}},
						{K: []Ident{Ident("compress")}, V: Val{Ident("gzip")}},
					},
				}}},
				{K: []Ident{Ident("patches")}, V: Val{Array{
					{String("musl-off64_t-loff_t.patch")},
					{String("musl-legacy-lfs.patch")},
				}}},

				{K: []Ident{Ident("exclusive")}, V: Val{Ident("true")}},

				{K: []Ident{Ident("exec")}, V: Val{Func{
					Ident: Ident("make"),

					Args: []Arg{
						{K: []Ident{Ident("configure")}, V: Val{[]KV{
							{K: String("disable-multilib")},
							{K: String("enable-default-pie")},
							{K: String("disable-nls")},
							{K: String("with-gnu-as")},
							{K: String("with-gnu-ld")},
							{K: String("with-system-zlib")},
							{K: String("enable-languages"), V: Val{String("c,c++,go")}},
							{K: String("with-native-system-header-dir"), V: Val{String("/system/include")}},
							{K: String("with-multilib-list"), V: Val{Func{
								Ident: Ident("arch"),

								Args: []Arg{
									{K: []Ident{Ident("amd64"), Ident("arm64")}, V: Val{String("''")}},
									{K: []Ident{Ident("default")}, V: Val{Ident("unset")}},
								},
							}}},
						}}},
						{K: []Ident{Ident("make")}, V: Val{Array{
							{String("BOOT_CFLAGS='-O2 -g'")},
							{
								Func{Ident: Ident("noop"), Args: []Arg{{K: []Ident{Ident("key")}, V: Val{Ident("value")}}}},
								String("\x00"),
							},
							{String("bootstrap")},
						}}},

						{K: []Ident{Ident("skip-check")}, V: Val{Ident("true")}},
					},
				}}},

				{K: []Ident{Ident("inputs")}, V: Val{Array{
					{Ident("binutils")},
					{Ident("mpc")},
					{Ident("zlib")},
					{Ident("libucontext")},
					{Ident("kernel-headers")},
				}}},
			},
		}}, nil},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			p, err := Parse(strings.NewReader(tc.data))
			if !reflect.DeepEqual(p, tc.want) {
				t.Errorf("Parse: %#v, want %#v", p, tc.want)
			}
			if !reflect.DeepEqual(err, tc.err) {
				t.Errorf("Parse: error = %v, want %v", err, tc.err)
			}
		})
	}
}

func BenchmarkParse(b *testing.B) {
	r := strings.NewReader(sample)
	for b.Loop() {
		if _, err := Parse(r); err != nil {
			b.Fatal(err)
		}

		b.StopTimer()
		r.Reset(sample)
		b.StartTimer()
	}
}
