package slice

import "base:runtime"

// An in-place permutation iterator.
Permutation_Iterator :: struct($T: typeid) {
	index: int,
	slice: []T,
	counters: []int,
}

/*
Make an iterator to permute a slice in-place.

*Allocates Using Provided Allocator*

This procedure allocates some state to assist in permutation and does not make
a copy of the underlying slice. If you want to permute a slice without altering
the underlying data, use `clone` to create a copy, then permute that instead.

Inputs:
- slice: The slice to permute.
- allocator: (default is context.allocator)

Returns:
- iter: The iterator, to be passed to `permute`.
- error: An `Allocator_Error`, if allocation failed.
*/
make_permutation_iterator :: proc(
	slice: []$T,
	allocator := context.allocator,
	loc := #caller_location,
) -> (
	iter: Permutation_Iterator(T),
	error: runtime.Allocator_Error,
) #optional_allocator_error {
	iter.slice = slice
	iter.counters = make([]int, len(iter.slice), allocator, loc) or_return

	return
}
/*
Free the state allocated by `make_permutation_iterator`.

Inputs:
- iter: The iterator created by `make_permutation_iterator`.
- allocator: The allocator used to create the iterator. (default is context.allocator)
*/
destroy_permutation_iterator :: proc(
	iter: Permutation_Iterator($T),
	allocator := context.allocator,
) {
	delete(iter.counters, allocator = allocator)
}
/*
Permute a slice in-place.

Note that the first iteration will always be the original, unpermuted slice.

Inputs:
- iter: The iterator created by `make_permutation_iterator`.

Returns:
- ok: True if the permutation succeeded, false if the iteration is complete.
*/
permute :: proc(iter: ^Permutation_Iterator($T)) -> (ok: bool) {
	// This is an iterative, resumable implementation of Heap's algorithm.
	//
	// The original algorithm was described by B. R. Heap as "Permutations by
	// interchanges" in The Computer Journal, 1963.
	//
	// This implementation is based on the nonrecursive version described by
	// Robert Sedgewick in "Permutation Generation Methods" which was published
	// in ACM Computing Surveys in 1977.

	i := iter.index

	if i == 0 {
		iter.index = 1
		return true
	}

	n := len(iter.counters)
	#no_bounds_check for i < n {
		if iter.counters[i] < i {
			if i & 1 == 0 {
				iter.slice[0], iter.slice[i] = iter.slice[i], iter.slice[0]
			} else {
				iter.slice[iter.counters[i]], iter.slice[i] = iter.slice[i], iter.slice[iter.counters[i]]
			}

			iter.counters[i] += 1
			i = 1

			break
		} else {
			iter.counters[i] = 0
			i += 1
		}
	}
	if i == n {
		return false
	}
	iter.index = i
	return true
}
