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

//go:build linux && (amd64 || arm64 || loong64 || ppc64le || s390x || riscv64 || 386 || arm)

package libc // import "modernc.org/libc"

import (
	"bytes"
	"math"
	"os"
	"os/exec"
	"testing"
	"unsafe"
)

// TestNaN checks the musl code returning NAN, which musl's math.h defines as
// __builtin_nanf(""): nan, nanf and nanl, and __floatscan, which parses "nan"
// and "inf" for the strtod and the scanf families.
//
// libc v1.77.0 was generated by ccgo v4.36.0, which linked __builtin_nanf to
// musl's nanf, so Xnanf called itself. The stack overflow is a fatal error
// that takes down the test binary, so the checks run in a child process.
func TestNaN(t *testing.T) {
	const env = "LIBC_TEST_NAN_CHILD"
	if os.Getenv(env) == "" {
		cmd := exec.Command(os.Args[0], "-test.run=^"+t.Name()+"$", "-test.v")
		cmd.Env = append(os.Environ(), env+"=1")
		out, err := cmd.CombinedOutput()
		if err != nil || !bytes.Contains(out, []byte("--- PASS: "+t.Name())) {
			if len(out) > 4000 {
				out = out[:4000]
			}
			t.Fatalf("child: %v\n%s", err, out)
		}
		return
	}

	tls := NewTLS()
	defer tls.Close()

	var allocs []uintptr
	defer func() {
		for _, p := range allocs {
			Xfree(tls, p)
		}
	}()

	cstr := func(s string) uintptr {
		p, err := CString(s)
		if err != nil {
			t.Fatal(err)
		}

		allocs = append(allocs, p)
		return p
	}

	empty := cstr("")
	if g := Xnan(tls, empty); !math.IsNaN(g) {
		t.Errorf("nan: got %v", g)
	}
	if g := Xnanf(tls, empty); !math.IsNaN(float64(g)) {
		t.Errorf("nanf: got %v", g)
	}
	if g := Xnanl(tls, empty); !math.IsNaN(g) {
		t.Errorf("nanl: got %v", g)
	}

	buf := Xmalloc(tls, 64)
	allocs = append(allocs, buf)
	end, va, out := buf, buf+16, buf+32
	for _, v := range []struct {
		s    string
		sign int // 0 for NaN, else the sign of the infinity
		n    int // bytes parsed
	}{
		{"nan", 0, 3},
		{"-nan", 0, 4},
		{"NAN(123)", 0, 8},
		{"inf", 1, 3},
		{"-inf", -1, 4},
		{"infinity", 1, 8},
	} {
		s := cstr(v.s)
		check := func(name string, g float64) {
			t.Helper()
			if v.sign == 0 && !math.IsNaN(g) || v.sign != 0 && !math.IsInf(g, v.sign) {
				t.Errorf("%s(%q): got %v", name, v.s, g)
			}
		}
		for _, f := range []struct {
			name string
			fn   func() float64
		}{
			{"strtod", func() float64 { return Xstrtod(tls, s, end) }},
			{"strtof", func() float64 { return float64(Xstrtof(tls, s, end)) }},
			{"strtold", func() float64 { return Xstrtold(tls, s, end) }},
		} {
			*(*uintptr)(unsafe.Pointer(end)) = 0
			check(f.name, f.fn())
			if g := *(*uintptr)(unsafe.Pointer(end)) - s; g != uintptr(v.n) {
				t.Errorf("%s(%q): parsed %d bytes, expected %d", f.name, v.s, g, v.n)
			}
		}

		if g := Xsscanf(tls, s, cstr("%f"), VaList(va, out)); g != 1 {
			t.Errorf("sscanf(%q, \"%%f\"): got %d conversions, expected 1", v.s, g)
		}
		check("sscanf %f", float64(*(*float32)(unsafe.Pointer(out))))
		if g := Xsscanf(tls, s, cstr("%lf"), VaList(va, out)); g != 1 {
			t.Errorf("sscanf(%q, \"%%lf\"): got %d conversions, expected 1", v.s, g)
		}
		check("sscanf %lf", *(*float64)(unsafe.Pointer(out)))
	}
}
