/*
	SST_ThreadBarrier.c
	Author: Patrick Baggett <ptbaggett@762studios.com>
	Created: 12/20/2011

	Purpose: 

	libsst-concurrency thread barrier functions

	License:

	This program is free software. It comes without any warranty, to
	the extent permitted by applicable law. You can redistribute it
	and/or modify it under the terms of the Do What The Fuck You Want
	To Public License, Version 2, as published by Sam Hocevar. See
	http://sam.zoy.org/wtfpl/COPYING for more details.

*/

#include <SST/SST_Concurrency.h> /* SST_WAIT_INFINITE */
#include <SST/SST_ThreadBarrier.h> /* Functions being implemented */
#include <SST/SST_Atomic.h> /* Needed for atomic operations */
#include <stdlib.h>

#define WAKEUP_UNSIGNALED (-1)
#define WAKEUP_CANCELED 0
#define WAKEUP_SUCCESS 1

typedef struct SST_ThreadBarrierImpl
{
	SST_Event event;
	uint32_t count;
	volatile int wakeup_reason;
	uint32_t threshold;
} SST_ThreadBarrierImpl;

/*************************************************************************/

SST_ThreadBarrier SST_Concurrency_CreateThreadBarrier(uint32_t threshold)
{
	SST_ThreadBarrierImpl* barrier;
	
	/* Allocate barrier */
	barrier = (SST_ThreadBarrierImpl*)malloc(sizeof(SST_ThreadBarrierImpl));
	if(barrier == NULL)
		return NULL;
	
	barrier->event = SST_Concurrency_CreateEvent();
	if(barrier->event == NULL) /* Out of memory */
	{
		free(barrier);
		return NULL;
	}
	
	barrier->count = 0;
	barrier->wakeup_reason = WAKEUP_UNSIGNALED; /* == error condition */
	barrier->threshold = threshold;
	
	/* Ensure all memory operations are visible by the time this returns */
	SST_Concurrency_MemoryBarrier();

	return (SST_ThreadBarrier)barrier;
}

/*************************************************************************/

int SST_Concurrency_WaitThreadBarrier(SST_ThreadBarrier _barrier)
{
	SST_ThreadBarrierImpl* barrier = (SST_ThreadBarrierImpl*)_barrier;
	uint32_t newValue;
	const int reason = barrier->wakeup_reason;
	int retval;

	/* Already canceled? */
	if(reason == WAKEUP_CANCELED)
		return 0;
		
	/* Already successful? */
	if(reason == WAKEUP_SUCCESS)
		return (int)barrier->threshold;
	
	/* At this point, we believe the barrier has not yet been canceled/finished. Due to
	multithreading, it could have been though. */
	
	/* Increment barrier count, see if we've exceeded the threshold */
	newValue = (uint32_t)SST_Atomic_AddReturn((volatile int*)&barrier->count, 1);
	if(newValue == barrier->threshold)
	{
		/* Yes -- but is it already canceled/finished? */
		const int state = SST_Atomic_CAS(&barrier->wakeup_reason, WAKEUP_UNSIGNALED, WAKEUP_SUCCESS);
		
		/*
			'state' can only be WAKEUP_SUCCESS or WAKEUP_CANCELED at this point. Don't
			(redundantly) signal the event if it was canceled.
		*/
		
		if(state == WAKEUP_SUCCESS)
			SST_Concurrency_SignalEvent(barrier->event);

		retval = SST_BARRIER_SINGLE;
	}
	else /* either newValue < threshold newValue > threshold or */
	{
		/* No not yet. Wait. */
		SST_Concurrency_WaitEvent(barrier->event, SST_WAIT_INFINITE);
		retval = 1;
	}
	
	/* Threads could have been woken up due to a cancel or due to the count being exceeded. */
	if(barrier->wakeup_reason == WAKEUP_UNSIGNALED) /* error? */
		return -1;
	if(barrier->wakeup_reason == WAKEUP_CANCELED) /* canceled */
		return 0;

	return retval; 
}

/*************************************************************************/

void SST_Concurrency_ResetThreadBarrier(SST_ThreadBarrier _barrier, uint32_t newThreshold)
{
	SST_ThreadBarrierImpl* barrier = (SST_ThreadBarrierImpl*)_barrier;
	barrier->count = 0;
	barrier->threshold = newThreshold;
	barrier->wakeup_reason = WAKEUP_UNSIGNALED; /* == error condition */

	SST_Concurrency_ResetEvent(barrier->event);
}

/*************************************************************************/

void SST_Concurrency_CancelThreadBarrier(SST_ThreadBarrier _barrier)
{
	SST_ThreadBarrierImpl* barrier = (SST_ThreadBarrierImpl*)_barrier;
	barrier->wakeup_reason = WAKEUP_CANCELED; /* == barrier canceled */
	SST_Concurrency_SignalEvent(barrier->event);
	
	/* NOTE: SignalEvent() provides a memory barrier so that barrier->wakeup_reason is globally
	visible before the event is signaled. */	
}

/*************************************************************************/

void SST_Concurrency_DestroyThreadBarrier(SST_ThreadBarrier _barrier)
{
	SST_ThreadBarrierImpl* barrier = (SST_ThreadBarrierImpl*)_barrier;
	SST_Concurrency_DestroyEvent(barrier->event);
	free(barrier);
}

