#include "rundown_protection.hpp"

#define FZ_RUNDOWNPROTECTION_DEBUG 0

#if FZ_RUNDOWNPROTECTION_DEBUG
#	include <cassert>
#	define FZ_RUNDOWNPROTECTION_ASSERT assert
#else
#	define FZ_RUNDOWNPROTECTION_ASSERT(...) do {} while(0)
#endif

namespace fz {

bool rundown_protection::acquire() noexcept
{
	auto oldv = state_.load(std::memory_order_relaxed);

	while (!(oldv & DISABLED_BIT)) {
		const auto newv = oldv + 1;

		// Overflow guard: shouldn’t hit unless you have 2^(state_size-1) borrows
		if (newv & DISABLED_BIT) {
			return false;
		}

		if (state_.compare_exchange_weak(oldv, newv, std::memory_order_acquire, std::memory_order_relaxed)) {
			return true; // success
		}

		// CAS failed; old has been updated to current value; retry
	}

	return false; // already closing
}

void rundown_protection::release() noexcept
{
	const auto oldv = state_.fetch_sub(1, std::memory_order_release);

	FZ_RUNDOWNPROTECTION_ASSERT((oldv & COUNT_MASK) != 0);

	const auto newv = oldv - 1;

	// Was it the last borrow and already closing?
	if ((oldv & DISABLED_BIT) && ((newv & COUNT_MASK) == 0)) {
		fz::scoped_lock l(m_);

		// Fixme: this signals only one thread. We need to add a broadcast() method to fz::condition, or simply use std::condition_variable instead?
		cv_.signal(l);
	}
}

bool rundown_protection::acquire_enabled() const noexcept
{
	auto s = state_.load(std::memory_order_acquire);

	return !(s & DISABLED_BIT);
}

void rundown_protection::disable_acquire() noexcept
{
	auto oldv = state_.load(std::memory_order_relaxed);

	while (!(oldv & DISABLED_BIT)) {
		const auto newv = oldv | DISABLED_BIT;

		if (state_.compare_exchange_weak(oldv, newv, std::memory_order_acq_rel, std::memory_order_relaxed)) {
			break; // closing bit set
		}

		// CAS failed: old updated, retry
	}
}

void rundown_protection::wait_until_all_acquires_released() noexcept
{
	auto s = state_.load(std::memory_order_acquire);

	// Fast path: already drained
	if ((s & DISABLED_BIT) && ((s & COUNT_MASK) == 0)) {
		return;
	}

	fz::scoped_lock l(m_);

	while (true) {
		s = state_.load(std::memory_order_acquire);
		if ((s & DISABLED_BIT) && ((s & COUNT_MASK) == 0)) {
			break;
		}

		cv_.wait(l);
	}
}

rundown_protection::state_type rundown_protection::acquired_count() const noexcept
{
	return state_.load(std::memory_order_acquire) & COUNT_MASK;
}

}
