#include "sema.h"
#include "sync.h"

void semaphore_base::imp_wait()
{
	for (int i = 0; i < 10; i++)
	{
		busy_wait();

		const s32 value = m_value.load();

		if (value > 0 && m_value.compare_and_swap_test(value, value - 1))
		{
			return;
		}
	}

#ifdef _WIN32
	const s32 value = m_value.fetch_sub(1);

	if (value <= 0)
	{
		NtWaitForKeyedEvent(nullptr, &m_value, false, nullptr);
	}
#else
	while (true)
	{
		// Try hard way
		const s32 value = m_value.op_fetch([](s32& value)
		{
			// Use sign bit to acknowledge waiter presence
			if (value && value > INT32_MIN)
			{
				value--;

				if (value < 0)
				{
					// Remove sign bit
					value -= INT32_MIN;
				}
			}
			else
			{
				// Set sign bit
				value = INT32_MIN;
			}
		});

		if (value >= 0)
		{
			// Signal other waiter to wake up or to restore sign bit
			futex(&m_value.raw(), FUTEX_WAKE_PRIVATE, 1, nullptr, nullptr, 0);
			return;
		}

		futex(&m_value.raw(), FUTEX_WAIT_PRIVATE, value, nullptr, nullptr, 0);
	}
#endif
}

void semaphore_base::imp_post(s32 _old)
{
	verify("semaphore_base: overflow" HERE), _old < 0;

#ifdef _WIN32
	NtReleaseKeyedEvent(nullptr, &m_value, false, nullptr);
#else
	futex(&m_value.raw(), FUTEX_WAKE_PRIVATE, 1, nullptr, nullptr, 0);
#endif
}

bool semaphore_base::try_wait()
{
	// Conditional decrement
	const s32 value = m_value.fetch_op([](s32& value)
	{
		if (value > 0)
		{
			value -= 1;
		}
	});

	return value > 0;
}

bool semaphore_base::try_post(s32 _max)
{
	// Conditional increment
	const s32 value = m_value.fetch_op([&](s32& value)
	{
		if (value < _max)
		{
			value += 1;
		}
	});

	if (value < 0)
	{
		imp_post(value);
	}

	return value < _max;
}
