package ring

import "base:runtime"
import "core:fmt"

@(private)
ODIN_BOUNDS_CHECK :: !ODIN_NO_BOUNDS_CHECK

Ring :: struct($E: typeid) {
	data:                  []E,
	next_write_index, len: int,
}

Ring_Soa :: struct($E: typeid) {
	data:                  #soa[]E,
	next_write_index, len: int,
}

destroy_aos :: #force_inline proc(
	ring: ^Ring($E),
	allocator := context.allocator,
) -> runtime.Allocator_Error {
	return delete(ring.data)
}

destroy_soa :: #force_inline proc(
	ring: ^Ring_Soa($E),
	allocator := context.allocator,
) -> runtime.Allocator_Error {
	return delete(ring.data)
}

destroy :: proc {
	destroy_aos,
	destroy_soa,
}

create_aos :: #force_inline proc(
	$E: typeid,
	capacity: int,
	allocator := context.allocator,
) -> (
	ring: Ring(E),
	err: runtime.Allocator_Error,
) #optional_allocator_error {
	ring.data, err = make([]E, capacity, allocator)
	return ring, err
}

create_soa :: #force_inline proc(
	$E: typeid,
	capacity: int,
	allocator := context.allocator,
) -> (
	ring: Ring_Soa(E),
	err: runtime.Allocator_Error,
) #optional_allocator_error {
	ring.data, err = make(#soa[]E, capacity, allocator)
	return ring, err
}

// All contents of `data` will be completely ignored, `data` is treated as an empty slice.
init_from_slice_aos :: #force_inline proc(ring: ^Ring($E), data: $T/[]E) {
	ring.data = data
	ring.len = 0
	ring.next_write_index = 0
	return
}

// All contents of `data` will be completely ignored, `data` is treated as an empty slice.
init_from_slice_soa :: #force_inline proc(ring: ^Ring_Soa($E), data: $T/#soa[]E) {
	ring.data = data
	ring.len = 0
	ring.next_write_index = 0
	return
}

init_from_slice :: proc {
	init_from_slice_aos,
	init_from_slice_soa,
}

// Internal
// Index in the backing array where the ring starts
start_index_aos :: #force_inline proc(ring: Ring($E)) -> int {
	return ring.len < len(ring.data) ? 0 : ring.next_write_index
}

// Internal
// Index in the backing array where the ring starts
start_index_soa :: #force_inline proc(ring: Ring_Soa($E)) -> int {
	return ring.len < len(ring.data) ? 0 : ring.next_write_index
}

advance_aos :: #force_inline proc(ring: ^Ring($E)) {
	// Length
	if ring.len != len(ring.data) do ring.len += 1
	// Write index
	ring.next_write_index += 1
	if ring.next_write_index == len(ring.data) do ring.next_write_index = 0
}

advance_soa :: #force_inline proc(ring: ^Ring_Soa($E)) {
	// Length
	if ring.len != len(ring.data) do ring.len += 1
	// Write index
	ring.next_write_index += 1
	if ring.next_write_index == len(ring.data) do ring.next_write_index = 0
}

advance :: proc {
	advance_aos,
	advance_soa,
}

append_aos :: #force_inline proc(ring: ^Ring($E), element: E) {
	ring.data[ring.next_write_index] = element
	advance(ring)
}

append_soa :: #force_inline proc(ring: ^Ring_Soa($E), element: E) {
	ring.data[ring.next_write_index] = element
	advance(ring)
}

append :: proc {
	append_aos,
	append_soa,
}

get_aos :: #force_inline proc(ring: Ring($E), index: int) -> ^E {
	when ODIN_BOUNDS_CHECK {
		fmt.assertf(index < ring.len, "Ring index %i out of bounds for length %i", index, ring.len)
	}

	array_index := start_index_aos(ring) + index
	if array_index < len(ring.data) {
		return &ring.data[array_index]
	} else {
		array_index = array_index - len(ring.data)
		return &ring.data[array_index]
	}
}

// SOA can't return soa pointer to parapoly T.
get_soa :: #force_inline proc(ring: Ring_Soa($E), index: int) -> E {
	when ODIN_BOUNDS_CHECK {
		fmt.assertf(index < ring.len, "Ring index %i out of bounds for length %i", index, ring.len)
	}

	array_index := start_index_soa(ring) + index
	if array_index < len(ring.data) {
		return ring.data[array_index]
	} else {
		array_index = array_index - len(ring.data)
		return ring.data[array_index]
	}
}

get :: proc {
	get_aos,
	get_soa,
}

get_last_aos :: #force_inline proc(ring: Ring($E)) -> ^E {
	return get(ring, ring.len - 1)
}

get_last_soa :: #force_inline proc(ring: Ring_Soa($E)) -> E {
	return get(ring, ring.len - 1)
}

get_last :: proc {
	get_last_aos,
	get_last_soa,
}

clear_aos :: #force_inline proc "contextless" (ring: ^Ring($E)) {
	ring.len = 0
	ring.next_write_index = 0
}

clear_soa :: #force_inline proc "contextless" (ring: ^Ring_Soa($E)) {
	ring.len = 0
	ring.next_write_index = 0
}

clear :: proc {
	clear_aos,
	clear_soa,
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Tests ------------------------
// ---------------------------------------------------------------------------------------------------------------------
import "core:log"
import "core:testing"

@(test)
test_ring_aos :: proc(t: ^testing.T) {
	ring := create_aos(int, 10)
	defer destroy(&ring)

	for i in 1 ..= 5 {
		append(&ring, i)
		log.debug("Length:", ring.len)
		log.debug("Start index:", start_index_aos(ring))
		log.debug("Next write index:", ring.next_write_index)
		log.debug(ring.data)
	}
	testing.expect_value(t, get(ring, 0)^, 1)
	testing.expect_value(t, get(ring, 4)^, 5)
	testing.expect_value(t, ring.len, 5)
	testing.expect_value(t, ring.next_write_index, 5)
	testing.expect_value(t, start_index_aos(ring), 0)

	for i in 6 ..= 15 {
		append(&ring, i)
		log.debug("Length:", ring.len)
		log.debug("Start index:", start_index_aos(ring))
		log.debug("Next write index:", ring.next_write_index)
		log.debug(ring.data)
	}
	testing.expect_value(t, get(ring, 0)^, 6)
	testing.expect_value(t, get(ring, 4)^, 10)
	testing.expect_value(t, get(ring, 9)^, 15)
	testing.expect_value(t, get_last(ring)^, 15)
	testing.expect_value(t, ring.len, 10)
	testing.expect_value(t, ring.next_write_index, 5)
	testing.expect_value(t, start_index_aos(ring), 5)

	for i in 15 ..= 25 {
		append(&ring, i)
		log.debug("Length:", ring.len)
		log.debug("Start index:", start_index_aos(ring))
		log.debug("Next write index:", ring.next_write_index)
		log.debug(ring.data)
	}
	testing.expect_value(t, get(ring, 0)^, 16)
	testing.expect_value(t, ring.next_write_index, 6)
	testing.expect_value(t, get_last(ring)^, 25)

	clear(&ring)
	append(&ring, 1)
	testing.expect_value(t, ring.len, 1)
	testing.expect_value(t, get(ring, 0)^, 1)
}

@(test)
test_ring_soa :: proc(t: ^testing.T) {
	Ints :: struct {
		x, y: int,
	}

	ring := create_soa(Ints, 10)
	defer destroy(&ring)

	for i in 1 ..= 5 {
		append(&ring, Ints{i, i})
		log.debug("Length:", ring.len)
		log.debug("Start index:", start_index_soa(ring))
		log.debug("Next write index:", ring.next_write_index)
		log.debug(ring.data)
	}
	testing.expect_value(t, get(ring, 0), Ints{1, 1})
	testing.expect_value(t, get(ring, 4), Ints{5, 5})
	testing.expect_value(t, ring.len, 5)
	testing.expect_value(t, ring.next_write_index, 5)
	testing.expect_value(t, start_index_soa(ring), 0)

	for i in 6 ..= 15 {
		append(&ring, Ints{i, i})
		log.debug("Length:", ring.len)
		log.debug("Start index:", start_index_soa(ring))
		log.debug("Next write index:", ring.next_write_index)
		log.debug(ring.data)
	}
	testing.expect_value(t, get(ring, 0), Ints{6, 6})
	testing.expect_value(t, get(ring, 4), Ints{10, 10})
	testing.expect_value(t, get(ring, 9), Ints{15, 15})
	testing.expect_value(t, get_last(ring), Ints{15, 15})
	testing.expect_value(t, ring.len, 10)
	testing.expect_value(t, ring.next_write_index, 5)
	testing.expect_value(t, start_index_soa(ring), 5)

	for i in 15 ..= 25 {
		append(&ring, Ints{i, i})
		log.debug("Length:", ring.len)
		log.debug("Start index:", start_index_soa(ring))
		log.debug("Next write index:", ring.next_write_index)
		log.debug(ring.data)
	}
	testing.expect_value(t, get(ring, 0), Ints{16, 16})
	testing.expect_value(t, ring.next_write_index, 6)
	testing.expect_value(t, get_last(ring), Ints{25, 25})

	clear(&ring)
	append(&ring, Ints{1, 1})
	testing.expect_value(t, ring.len, 1)
	testing.expect_value(t, get(ring, 0), Ints{1, 1})
}

@(test)
test_ring_aos_init_from_slice :: proc(t: ^testing.T) {
	// Stack-allocated backing with pre-existing garbage and odd capacity.
	backing: [7]int = {99, 99, 99, 99, 99, 99, 99}

	ring: Ring(int)
	init_from_slice(&ring, backing[:])

	// Empty ring invariants after init_from_slice.
	testing.expect_value(t, ring.len, 0)
	testing.expect_value(t, ring.next_write_index, 0)
	testing.expect_value(t, start_index_aos(ring), 0)

	// Partial fill (3 / 7).
	for i in 1 ..= 3 do append(&ring, i)
	testing.expect_value(t, ring.len, 3)
	testing.expect_value(t, ring.next_write_index, 3)
	testing.expect_value(t, start_index_aos(ring), 0)
	testing.expect_value(t, get(ring, 0)^, 1)
	testing.expect_value(t, get(ring, 2)^, 3)
	testing.expect_value(t, get_last(ring)^, 3)

	// Fill exactly to capacity. Pushing element 7 must make len == cap
	// AND wrap next_write_index from 6 back to 0 in the same step.
	for i in 4 ..= 7 do append(&ring, i)
	testing.expect_value(t, ring.len, 7)
	testing.expect_value(t, ring.next_write_index, 0)
	testing.expect_value(t, start_index_aos(ring), 0)
	testing.expect_value(t, get(ring, 0)^, 1)
	testing.expect_value(t, get(ring, 6)^, 7)
	testing.expect_value(t, get_last(ring)^, 7)

	// First overwrite — oldest element shifts by one.
	append(&ring, 8)
	testing.expect_value(t, ring.len, 7)
	testing.expect_value(t, ring.next_write_index, 1)
	testing.expect_value(t, start_index_aos(ring), 1)
	testing.expect_value(t, get(ring, 0)^, 2)
	testing.expect_value(t, get(ring, 6)^, 8)
	testing.expect_value(t, get_last(ring)^, 8)

	// Stress: 3 more complete wrap cycles (21 more pushes).
	// After 29 total pushes, ring contains the last 7 (23..=29),
	// and next_write_index = 29 mod 7 = 1.
	for i in 9 ..= 29 do append(&ring, i)
	testing.expect_value(t, ring.len, 7)
	testing.expect_value(t, ring.next_write_index, 1)
	testing.expect_value(t, start_index_aos(ring), 1)
	testing.expect_value(t, get(ring, 0)^, 23)
	testing.expect_value(t, get(ring, 3)^, 26)
	testing.expect_value(t, get(ring, 6)^, 29)
	testing.expect_value(t, get_last(ring)^, 29)

	// Clear returns ring to empty-equivalent state.
	clear(&ring)
	testing.expect_value(t, ring.len, 0)
	testing.expect_value(t, ring.next_write_index, 0)
	testing.expect_value(t, start_index_aos(ring), 0)

	// Single-element edge case: get_last(len==1) routes through get(ring, 0).
	append(&ring, 42)
	testing.expect_value(t, ring.len, 1)
	testing.expect_value(t, ring.next_write_index, 1)
	testing.expect_value(t, get(ring, 0)^, 42)
	testing.expect_value(t, get_last(ring)^, 42)
}

@(test)
test_ring_soa_init_from_slice :: proc(t: ^testing.T) {
	Ints :: struct {
		x, y: int,
	}

	// Stack-allocated backing with pre-existing garbage and odd capacity.
	backing: #soa[7]Ints = {{99, 99}, {99, 99}, {99, 99}, {99, 99}, {99, 99}, {99, 99}, {99, 99}}

	ring: Ring_Soa(Ints)
	init_from_slice(&ring, backing[:])

	// Empty ring invariants after init_from_slice.
	testing.expect_value(t, ring.len, 0)
	testing.expect_value(t, ring.next_write_index, 0)
	testing.expect_value(t, start_index_soa(ring), 0)

	// Partial fill (3 / 7).
	for i in 1 ..= 3 do append(&ring, Ints{i, i})
	testing.expect_value(t, ring.len, 3)
	testing.expect_value(t, ring.next_write_index, 3)
	testing.expect_value(t, start_index_soa(ring), 0)
	testing.expect_value(t, get(ring, 0), Ints{1, 1})
	testing.expect_value(t, get(ring, 2), Ints{3, 3})
	testing.expect_value(t, get_last(ring), Ints{3, 3})

	// Fill exactly to capacity. Pushing element 7 must make len == cap
	// AND wrap next_write_index from 6 back to 0 in the same step.
	for i in 4 ..= 7 do append(&ring, Ints{i, i})
	testing.expect_value(t, ring.len, 7)
	testing.expect_value(t, ring.next_write_index, 0)
	testing.expect_value(t, start_index_soa(ring), 0)
	testing.expect_value(t, get(ring, 0), Ints{1, 1})
	testing.expect_value(t, get(ring, 6), Ints{7, 7})
	testing.expect_value(t, get_last(ring), Ints{7, 7})

	// First overwrite — oldest element shifts by one.
	append(&ring, Ints{8, 8})
	testing.expect_value(t, ring.len, 7)
	testing.expect_value(t, ring.next_write_index, 1)
	testing.expect_value(t, start_index_soa(ring), 1)
	testing.expect_value(t, get(ring, 0), Ints{2, 2})
	testing.expect_value(t, get(ring, 6), Ints{8, 8})
	testing.expect_value(t, get_last(ring), Ints{8, 8})

	// Stress: 3 more complete wrap cycles (21 more pushes).
	// After 29 total pushes, ring contains the last 7 (23..=29),
	// and next_write_index = 29 mod 7 = 1.
	for i in 9 ..= 29 do append(&ring, Ints{i, i})
	testing.expect_value(t, ring.len, 7)
	testing.expect_value(t, ring.next_write_index, 1)
	testing.expect_value(t, start_index_soa(ring), 1)
	testing.expect_value(t, get(ring, 0), Ints{23, 23})
	testing.expect_value(t, get(ring, 3), Ints{26, 26})
	testing.expect_value(t, get(ring, 6), Ints{29, 29})
	testing.expect_value(t, get_last(ring), Ints{29, 29})

	// Clear returns ring to empty-equivalent state.
	clear(&ring)
	testing.expect_value(t, ring.len, 0)
	testing.expect_value(t, ring.next_write_index, 0)
	testing.expect_value(t, start_index_soa(ring), 0)

	// Single-element edge case: get_last(len==1) routes through get(ring, 0).
	append(&ring, Ints{42, 42})
	testing.expect_value(t, ring.len, 1)
	testing.expect_value(t, ring.next_write_index, 1)
	testing.expect_value(t, get(ring, 0), Ints{42, 42})
	testing.expect_value(t, get_last(ring), Ints{42, 42})
}
