package util_test

import (
	"fmt"
	"math"
	"testing"

	"galaxy/calc"
	"galaxy/util"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestShortDistance(t *testing.T) {
	for i, tc := range []struct {
		w, h              uint32
		x1, y1, x2, y2, d float64
	}{
		{10, 10, 0, 0, 5, 5, 7.071},
		{10, 10, 0, 0, 5.01, 5.01, 7.057},
		{10, 10, 2, 2, 8, 2, 4.},
		{10, 10, 8, 7, 1, 7, 3.},
	} {
		t.Run(fmt.Sprint(i), func(t *testing.T) {
			d := calc.ShortDistance(tc.w, tc.h, tc.x1, tc.y1, tc.x2, tc.y2)
			assert.Equal(t, tc.d, util.Fixed3(d))
		})
	}
}

func TestNextTravelCoord(t *testing.T) {
	for i, tc := range []struct {
		w, h                  uint32
		ox, oy, dx, dy, delta float64
		tx, ty                float64
		arrived               bool
	}{
		{w: 10, h: 10, ox: 0.0, oy: 0.0, dx: 2.0, dy: 0.0, delta: 1.0, tx: 1.0, ty: 0.0, arrived: false},
		{w: 10, h: 10, ox: 0.0, oy: 0.0, dx: 0.0, dy: 2.0, delta: 1.0, tx: 0.0, ty: 1.0, arrived: false},

		{w: 10, h: 10, ox: 1.0, oy: 1.0, dx: 9.0, dy: 1.0, delta: 1.0, tx: 0.0, ty: 1.0, arrived: false},
		{w: 10, h: 10, ox: 1.0, oy: 9.5, dx: 1.0, dy: 1.0, delta: 1.0, tx: 1.0, ty: 0.5, arrived: false},

		{w: 10, h: 10, ox: 1.0, oy: 1.0, dx: 5.0, dy: 5.0, delta: 2.0, tx: 2.414, ty: 2.414, arrived: false},
		{w: 10, h: 10, ox: 1.0, oy: 1.0, dx: 9.0, dy: 9.0, delta: 2.0, tx: 9.586, ty: 9.586, arrived: false},

		{w: 10, h: 10, ox: 5.0, oy: 5.0, dx: 9.0, dy: 9.0, delta: 6.0, tx: 9.0, ty: 9.0, arrived: true},
		{w: 10, h: 10, ox: 6.0, oy: 6.0, dx: 10.0, dy: 10.0, delta: 6.0, tx: 10.0, ty: 10.0, arrived: true},
		{w: 10, h: 10, ox: 1.0, oy: 2.0, dx: 7.0, dy: 8.0, delta: 6.0, tx: 7.0, ty: 8.0, arrived: true},
	} {
		t.Run(fmt.Sprint(i), func(t *testing.T) {
			tx, ty, arrived := util.NextTravelCoord(tc.w, tc.h, tc.ox, tc.oy, tc.dx, tc.dy, tc.delta)
			assert.Equal(t, tc.arrived, arrived)
			assert.Equal(t, tc.tx, util.Fixed3(tx))
			assert.Equal(t, tc.ty, util.Fixed3(ty))
		})
	}
}

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

	tests := []struct {
		name  string
		value float64
		size  int
		want  float64
	}{
		{
			name:  "already in range integer",
			value: 3,
			size:  10,
			want:  3,
		},
		{
			name:  "already in range fractional",
			value: 3.25,
			size:  10,
			want:  3.25,
		},
		{
			name:  "positive overflow integer",
			value: 12,
			size:  10,
			want:  2,
		},
		{
			name:  "positive overflow fractional",
			value: 12.75,
			size:  10,
			want:  2.75,
		},
		{
			name:  "negative small fractional",
			value: -0.25,
			size:  10,
			want:  9.75,
		},
		{
			name:  "negative overflow integer",
			value: -12,
			size:  10,
			want:  8,
		},
		{
			name:  "negative overflow fractional",
			value: -12.75,
			size:  10,
			want:  7.25,
		},
		{
			name:  "exact multiple positive",
			value: 20,
			size:  10,
			want:  0,
		},
		{
			name:  "exact multiple negative",
			value: -20,
			size:  10,
			want:  0,
		},
		{
			name:  "size one wraps into [0,1)",
			value: 123.456,
			size:  1,
			want:  math.Mod(123.456, 1),
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			got := util.WrapF(tt.value, tt.size)
			require.InDelta(t, tt.want, got, 1e-12)
			require.GreaterOrEqual(t, got, 0.0)
			require.Less(t, got, float64(tt.size))
		})
	}
}

// TestWrapF_NaNAndInf verifies IEEE 754 behavior inherited from math.Mod.
func TestWrapF_NaNAndInf(t *testing.T) {
	t.Parallel()

	require.True(t, math.IsNaN(util.WrapF(math.NaN(), 10)))
	require.True(t, math.IsNaN(util.WrapF(math.Inf(1), 10)))
	require.True(t, math.IsNaN(util.WrapF(math.Inf(-1), 10)))
}
