// Package bunny owns the CPU bunny population and its retained SoA storage.
// No Godot/Gododin/Raylib imports; stepping and packing allocate nothing.
//
// RenderingServer's TRANSFORM_2D buffer uses eight floats without color/custom
// data: xx,yx,0,ox,xy,yy,0,oy. The pinned dev5 mesh_storage.cpp lines
// 1607–1610, 1963–1970 and 2081–2086 establish the stride, writer and getter.
package bunny

import "core:math"
import "core:mem"

STRIDE :: 8
TRANSFORM_2D_FLOATS :: 6
MAX_COUNT :: 1_000_000
CANONICAL_BOUNDS_X :: f32(1280)
CANONICAL_BOUNDS_Y :: f32(720)
CANONICAL_SPEED_SCALE :: f32(1)
CANONICAL_GRAVITY :: f32(900)
CANONICAL_DT :: f32(1.0 / 60.0)

Step_Profile :: struct {dt, bounds_x, bounds_y, speed_scale, gravity: f32}
canonical_profile :: proc() -> Step_Profile {
	return {CANONICAL_DT, CANONICAL_BOUNDS_X, CANONICAL_BOUNDS_Y, CANONICAL_SPEED_SCALE, CANONICAL_GRAVITY}
}
profile_equal :: proc(a, b: Step_Profile) -> bool {
	return a.dt == b.dt && a.bounds_x == b.bounds_x && a.bounds_y == b.bounds_y && a.speed_scale == b.speed_scale && a.gravity == b.gravity
}
// Admit dt in (0,1], bounds in (0,1000000], speed in [0,10], gravity in
// [0,10000]. These bounds prevent finite-but-overflowing arithmetic.
valid_profile :: proc(p: Step_Profile) -> bool {
	for v in ([5]f32{p.dt, p.bounds_x, p.bounds_y, p.speed_scale, p.gravity}) {
		if math.is_nan(v) || math.is_inf(v) do return false
	}
	return p.dt > 0 && p.dt <= 1 && p.bounds_x > 0 && p.bounds_x <= 1000000 && p.bounds_y > 0 && p.bounds_y <= 1000000 && p.speed_scale >= 0 && p.speed_scale <= 10 && p.gravity >= 0 && p.gravity <= 10000
}

Bunny_Parameter :: struct {x0, y0, vx0, vy0, scale: f32}

// bunny_parameter is mirrored by the GDScript baseline and checked by runtime
// replay. Integer coordinates/velocities and dyadic scale avoid trig/PRNG drift
// across languages. All products fit signed i64 before the GDScript u32 mask.
bunny_parameter :: proc(index: i64, population_seed: u32) -> Bunny_Parameter {
	x := population_seed ~ (u32(index) * 2654435761)
	x = x * 1664525 + 1013904223
	px := 40 + (x >> 16) % 1200
	x = x * 1664525 + 1013904223
	py := 40 + (x >> 16) % 640
	x = x * 1664525 + 1013904223
	vx := i32((x >> 16) % 481) - 240
	x = x * 1664525 + 1013904223
	vy := i32((x >> 16) % 481) - 240
	x = x * 1664525 + 1013904223
	return {f32(px), f32(py), f32(vx), f32(vy), 0.5 + f32(x >> 24)/256.0}
}

// Field owns one allocation for five SoA rows and one for the owner itself.
// No pointers into the arena survive destroy. Canonical history records whether
// the seed/frame replay oracle applies; custom profiles still simulate normally.
Field :: struct {
	allocator: mem.Allocator,
	count: i64,
	population_seed: u32,
	frame: i64,
	arena, x, y, vx, vy, scale: []f32,
	profile: Step_Profile,
	profile_uniform: bool,
}
field_create :: proc(count: i64, seed: u32) -> ^Field {
	return field_create_with_allocator(count, seed, context.allocator)
}
field_create_with_allocator :: proc(count: i64, seed: u32, allocator: mem.Allocator) -> ^Field {
	if count <= 0 || count > MAX_COUNT || allocator.procedure == nil do return nil
	arena, err := make([]f32, 5*int(count), allocator)
	if err != nil do return nil
	f, owner_err := new(Field, allocator)
	if owner_err != nil {delete(arena, allocator); return nil}
	n := int(count)
	f^ = {allocator=allocator, count=count, population_seed=seed, arena=arena,
		x=arena[:n], y=arena[n:2*n], vx=arena[2*n:3*n], vy=arena[3*n:4*n], scale=arena[4*n:],
		profile=canonical_profile(), profile_uniform=true}
	for i in 0..<n {
		p := bunny_parameter(i64(i), seed)
		f.x[i], f.y[i], f.vx[i], f.vy[i], f.scale[i] = p.x0, p.y0, p.vx0, p.vy0, p.scale
	}
	return f
}

// field_destroy consumes and clears the owner's pointer. Repeated cleanup is
// safe through that same owner; copied pointers/slices expire on the first call.
field_destroy :: proc(owner: ^^Field) -> bool {
	if owner == nil || owner^ == nil do return true
	f := owner^
	allocator := f.allocator
	if f.arena != nil {
		if delete(f.arena, allocator) != nil do return false
		f.arena = nil
	}
	if mem.free(f, allocator) != nil do return false
	owner^ = nil
	return true
}

// bounce_axis is the common semi-implicit Euler wall rule: clamp an overshoot
// and invert that component's velocity. This intentionally discards overshoot;
// it is a discrete bunnymark workload, not an exact collision solver.
bounce_axis :: proc(pos_in, vel_in, size: f32) -> (f32, f32) {
	if pos_in > size do return size, -vel_in
	if pos_in < 0 do return 0, -vel_in
	return pos_in, vel_in
}

// integrate uses explicit f32 temporaries and operation order shared with
// GDScript Vector2 arithmetic. Speed scales both axes' displacement, while
// gravity changes velocity. Changing settings continues from current state.
integrate :: proc(x, y, vx, vy: f32, profile: Step_Profile) -> (f32, f32, f32, f32) {
	gravity_step := profile.gravity * profile.dt
	new_vy := vy + gravity_step
	travel_dt := profile.dt * profile.speed_scale
	dx := vx * travel_dt
	dy := new_vy * travel_dt
	px, out_vx := bounce_axis(x + dx, vx, profile.bounds_x)
	py, out_vy := bounce_axis(y + dy, new_vy, profile.bounds_y)
	return px, py, out_vx, out_vy
}
field_step :: proc(f: ^Field, dt, bounds_x, bounds_y, speed_scale, gravity: f32) -> bool {
	p := Step_Profile{dt, bounds_x, bounds_y, speed_scale, gravity}
	if f == nil || !valid_profile(p) do return false
	if f.frame == 0 {f.profile = p} else if !profile_equal(f.profile, p) {
		f.profile = p
		f.profile_uniform = false
	}
	f.frame += 1
	for i in 0..<int(f.count) {
		f.x[i], f.y[i], f.vx[i], f.vy[i] = integrate(f.x[i], f.y[i], f.vx[i], f.vy[i], p)
	}
	return true
}

// field_write_layout only packs live state. It neither advances nor replays it.
field_write_layout :: proc(f: ^Field, sink: []f32) -> bool {
	if f == nil || i64(len(sink)) != f.count*STRIDE do return false
	for i in 0..<int(f.count) {
		sink[i*STRIDE+0] = f.scale[i]
		sink[i*STRIDE+1] = 0
		sink[i*STRIDE+2] = 0
		sink[i*STRIDE+3] = f.x[i]
		sink[i*STRIDE+4] = 0
		sink[i*STRIDE+5] = f.scale[i]
		sink[i*STRIDE+6] = 0
		sink[i*STRIDE+7] = f.y[i]
	}
	return true
}

// reference_state independently replays one canonical bunny in O(frames).
// It intentionally calls neither field_step nor integrate/bounce_axis nor the
// layout writer. Shared seed identity is checked with fixed vectors in tests.
reference_state :: proc(index, frames: i64, seed: u32) -> Bunny_Parameter {
	p := bunny_parameter(index, seed)
	for _ in 0..<frames {
		gravity_step := f32(900) * CANONICAL_DT
		p.vy0 = p.vy0 + gravity_step
		dx := p.vx0 * CANONICAL_DT
		dy := p.vy0 * CANONICAL_DT
		p.x0 = p.x0 + dx
		p.y0 = p.y0 + dy
		if p.x0 > 1280 {p.x0=1280; p.vx0=-p.vx0} else if p.x0 < 0 {p.x0=0; p.vx0=-p.vx0}
		if p.y0 > 720 {p.y0=720; p.vy0=-p.vy0} else if p.y0 < 0 {p.y0=0; p.vy0=-p.vy0}
	}
	return p
}

// oracle_instance compares exact float bits, including signed zero. It accepts
// either padded server storage or the engine getter's six-float Transform2D.
oracle_instance :: proc(sink: []f32, index, frames: i64, seed: u32) -> bool {
	if frames < 0 || index < 0 do return false
	p := reference_state(index, frames, seed)
	expected: [STRIDE]f32
	if len(sink) == STRIDE {expected = {p.scale, 0, 0, p.x0, 0, p.scale, 0, p.y0}}
	else if len(sink) == TRANSFORM_2D_FLOATS {expected = {p.scale, 0, 0, p.scale, p.x0, p.y0, 0, 0}}
	else {return false}
	for v, i in sink {if transmute(u32)v != transmute(u32)expected[i] do return false}
	return true
}

digest :: proc(buffer: []f32) -> u64 {
	hash := u64(0xcbf29ce484222325)
	for value in buffer {
		bits := transmute(u32)value
		for shift in 0..<4 {hash = (hash ~ u64((bits >> (u32(shift)*8)) & 0xFF))*0x100000001b3}
	}
	return hash
}
