// run -gcflags=-d=converthash=qy

// Copyright 2020 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.

//go:build !wasm && !386 && !arm && !mips && !mipsle

// TODO fix this to work for wasm and 32-bit architectures.
// Doing more than this, however, expands the change.

package main

import (
	"fmt"
	"runtime"
)

// This test checks that conversion from floats to (unsigned) 32 and 64-bit
// integers has the same sensible behavior for corner cases, and that the
// conversions to smaller integers agree.  Because outliers are platform-
// independent, the "golden test" for smaller integers is more like of
// a "gold-ish test" and subject to change.

//go:noinline
func id[T any](x T) T {
	return x
}

//go:noinline
func want[T comparable](name string, x, y T) {
	if x != y {
		_, _, line, _ := runtime.Caller(1)
		fmt.Println("FAIL at line", line, "var =", name, "got =", x, "want =", y)
	}
}

//go:noinline
func log[T comparable](name string, x T) {
	fmt.Println(name, x)
}

const (
	// pX = max positive signed X bit
	// nX = min negative signed X bit
	// uX = max unsigned X bit
	// tX = two to the X
	p32 = 2147483647
	n32 = -2147483648
	u32 = 4294967295
	p64 = 9223372036854775807
	n64 = -9223372036854775808
	u64 = 18446744073709551615
	t44 = 1 << 44
)

func main() {
	one := 1.0
	minus1_32 := id(float32(-1.0))
	minus1_64 := id(float64(-1.0))
	p32_plus4k_plus1 := id(float32(p32 + 4096 + 1)) // want this to be precise and fit in 24 bits mantissa
	p64_plus4k_plus1 := id(float64(p64 + 4096 + 1)) // want this to be precise and fit in 53 bits mantissa
	n32_minus4k := id(float32(n32 - 4096))
	n64_minus4k := id(float64(n64 - 4096))
	inf_32 := id(float32(one / 0))
	inf_64 := id(float64(one / 0))
	ninf_32 := id(float32(-one / 0))
	ninf_64 := id(float64(-one / 0))

	// int32 conversions
	int32Tests := []struct {
		name     string
		input    any // Use any to handle both float32 and float64
		expected int32
	}{
		{"minus1_32", minus1_32, -1},
		{"minus1_64", minus1_64, -1},
		{"p32_plus4k_plus1", p32_plus4k_plus1, p32},
		{"p64_plus4k_plus1", p64_plus4k_plus1, p32},
		{"n32_minus4k", n32_minus4k, n32},
		{"n64_minus4k", n64_minus4k, n32},
		{"inf_32", inf_32, p32},
		{"inf_64", inf_64, p32},
		{"ninf_32", ninf_32, n32},
		{"ninf_64", ninf_64, n32},
	}

	for _, test := range int32Tests {
		var converted int32
		switch v := test.input.(type) {
		case float32:
			converted = int32(v)
		case float64:
			converted = int32(v)
		}
		want(test.name, converted, test.expected)
	}

	// int64 conversions
	int64Tests := []struct {
		name     string
		input    any
		expected int64
	}{
		{"minus1_32", minus1_32, -1},
		{"minus1_64", minus1_64, -1},
		{"p32_plus4k_plus1", p32_plus4k_plus1, p32 + 4096 + 1},
		{"p64_plus4k_plus1", p64_plus4k_plus1, p64},
		{"n32_minus4k", n32_minus4k, n32 - 4096},
		{"n64_minus4k", n64_minus4k, n64},
		{"inf_32", inf_32, p64},
		{"inf_64", inf_64, p64},
		{"ninf_32", ninf_32, n64},
		{"ninf_64", ninf_64, n64},
	}

	for _, test := range int64Tests {
		var converted int64
		switch v := test.input.(type) {
		case float32:
			converted = int64(v)
		case float64:
			converted = int64(v)
		}
		want(test.name, converted, test.expected)
	}

	// uint32 conversions
	uint32Tests := []struct {
		name     string
		input    any
		expected uint32
	}{
		{"minus1_32", minus1_32, 0},
		{"minus1_64", minus1_64, 0},
		{"p32_plus4k_plus1", p32_plus4k_plus1, p32 + 4096 + 1},
		{"p64_plus4k_plus1", p64_plus4k_plus1, u32},
		{"n32_minus4k", n32_minus4k, 0},
		{"n64_minus4k", n64_minus4k, 0},
		{"inf_32", inf_32, u32},
		{"inf_64", inf_64, u32},
		{"ninf_32", ninf_32, 0},
		{"ninf_64", ninf_64, 0},
	}

	for _, test := range uint32Tests {
		var converted uint32
		switch v := test.input.(type) {
		case float32:
			converted = uint32(v)
		case float64:
			converted = uint32(v)
		}
		want(test.name, converted, test.expected)
	}

	u64_plus4k_plus1_64 := id(float64(u64 + 4096 + 1))
	u64_plust44_plus1_32 := id(float32(u64 + t44 + 1))

	// uint64 conversions
	uint64Tests := []struct {
		name     string
		input    any
		expected uint64
	}{
		{"minus1_32", minus1_32, 0},
		{"minus1_64", minus1_64, 0},
		{"p32_plus4k_plus1", p32_plus4k_plus1, p32 + 4096 + 1},
		{"p64_plus4k_plus1", p64_plus4k_plus1, p64 + 4096 + 1},
		{"n32_minus4k", n32_minus4k, 0},
		{"n64_minus4k", n64_minus4k, 0},
		{"inf_32", inf_32, u64},
		{"inf_64", inf_64, u64},
		{"ninf_32", ninf_32, 0},
		{"ninf_64", ninf_64, 0},
		{"u64_plus4k_plus1_64", u64_plus4k_plus1_64, u64},
		{"u64_plust44_plus1_32", u64_plust44_plus1_32, u64},
	}

	for _, test := range uint64Tests {
		var converted uint64
		switch v := test.input.(type) {
		case float32:
			converted = uint64(v)
		case float64:
			converted = uint64(v)
		}
		want(test.name, converted, test.expected)
	}

	// for smaller integer types
	// TODO the overflow behavior is dubious, maybe we should fix it to be more sensible, e.g. saturating.
	fmt.Println("Below this are 'golden' results to check for consistency across platforms.  Overflow behavior is not necessarily what we want")

	u8plus2 := id(float64(257))
	p8minus1 := id(float32(126))
	n8plus2 := id(float64(-126))
	n8minusone := id(float32(-129))

	fmt.Println("\nuint8 conversions")
	uint8Tests := []struct {
		name  string
		input any
	}{
		{"minus1_32", minus1_32},
		{"minus1_64", minus1_64},
		{"p32_plus4k_plus1", p32_plus4k_plus1},
		{"p64_plus4k_plus1", p64_plus4k_plus1},
		{"n32_minus4k", n32_minus4k},
		{"n64_minus4k", n64_minus4k},
		{"inf_32", inf_32},
		{"inf_64", inf_64},
		{"ninf_32", ninf_32},
		{"ninf_64", ninf_64},
		{"u64_plus4k_plus1_64", u64_plus4k_plus1_64},
		{"u64_plust44_plus1_32", u64_plust44_plus1_32},
		{"u8plus2", u8plus2},
		{"p8minus1", p8minus1},
		{"n8plus2", n8plus2},
		{"n8minusone", n8minusone},
	}

	for _, test := range uint8Tests {
		var converted uint8
		switch v := test.input.(type) {
		case float32:
			converted = uint8(v)
		case float64:
			converted = uint8(v)
		}
		log(test.name, converted)
	}

	fmt.Println("\nint8 conversions")
	int8Tests := []struct {
		name  string
		input any
	}{
		{"minus1_32", minus1_32},
		{"minus1_64", minus1_64},
		{"p32_plus4k_plus1", p32_plus4k_plus1},
		{"p64_plus4k_plus1", p64_plus4k_plus1},
		{"n32_minus4k", n32_minus4k},
		{"n64_minus4k", n64_minus4k},
		{"inf_32", inf_32},
		{"inf_64", inf_64},
		{"ninf_32", ninf_32},
		{"ninf_64", ninf_64},
		{"u64_plus4k_plus1_64", u64_plus4k_plus1_64},
		{"u64_plust44_plus1_32", u64_plust44_plus1_32},
		{"u8plus2", u8plus2},
		{"p8minus1", p8minus1},
		{"n8plus2", n8plus2},
		{"n8minusone", n8minusone},
	}

	for _, test := range int8Tests {
		var converted int8
		switch v := test.input.(type) {
		case float32:
			converted = int8(v)
		case float64:
			converted = int8(v)
		}
		log(test.name, converted)
	}

}
