#include "sema.h"

#include "util/asm.hpp"

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

		const u32 value = m_value.load();

		if (value & c_value_mask && m_value.compare_and_swap_test(value, value - c_value))
		{
			return;
		}
	}

	bool waits = false;

	while (true)
	{
		// Try hard way
		const u32 value = m_value.fetch_op([&](u32& value)
		{
			ensure(value != c_waiter_mask); // "semaphore_base: overflow"

			if (value & c_value_mask)
			{
				// Obtain signal
				value -= c_value;

				if (waits)
				{
					// Remove waiter
					value -= c_waiter;
				}
			}
			else if (!waits)
			{
				// Add waiter
				value += c_waiter;
			}
			else
			{
				return false;
			}

			return true;
		}).first;

		if (value & c_value_mask)
		{
			break;
		}

		m_value.wait(value + (waits ? 0 : c_waiter));
		waits = true;
	}
}

void semaphore_base::imp_post(u32 _old)
{
	ensure(~_old & c_value_mask); // "semaphore_base: overflow"

	if ((_old & c_waiter_mask) / c_waiter > (_old & c_value_mask) / c_value)
	{
		m_value.notify_one();
	}
}

bool semaphore_base::try_post(u32 _max)
{
	// Conditional increment
	const auto [value, ok] = m_value.fetch_op([&](u32& value)
	{
		if ((value & c_value_mask) <= _max)
		{
			value += c_value;
			return true;
		}

		return false;
	});

	if (!ok)
	{
		return false;
	}

	if (value & c_waiter_mask)
	{
		imp_post(value);
	}

	return true;
}
