/*
    PIP - Platform Independent Primitives

    Stephan Fomenko

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "piplatform.h"
#ifdef WINDOWS
#  undef _WIN32_WINNT
#  define _WIN32_WINNT 0x0600
#endif

#include "piconditionvar.h"
#include "piincludes_p.h"
#ifdef WINDOWS
#  include <synchapi.h>
#  include <winbase.h>
#  include <windef.h>
#endif
#ifdef FREERTOS
#  include <event_groups.h>
#endif


PRIVATE_DEFINITION_START(PIConditionVariable)
#if defined(WINDOWS)
	CONDITION_VARIABLE
#elif defined(FREERTOS)
	EventGroupHandle_t
#else
	pthread_cond_t
#endif
	nativeHandle;
PRIVATE_DEFINITION_END(PIConditionVariable)


PIConditionVariable::PIConditionVariable() {
#if defined(WINDOWS)
	InitializeConditionVariable(&PRIVATE->nativeHandle);
#elif defined(FREERTOS)
	PRIVATE->nativeHandle = xEventGroupCreate();
#else
	pthread_condattr_t condattr;
	pthread_condattr_init(&condattr);
#  if !defined(MAC_OS)
	pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);
#  endif
	piZeroMemory(PRIVATE->nativeHandle);
	pthread_cond_init(&PRIVATE->nativeHandle, &condattr);
#endif
}


PIConditionVariable::~PIConditionVariable() {
#if defined(WINDOWS)
#elif defined(FREERTOS)
	vEventGroupDelete(PRIVATE->nativeHandle);
#else
	pthread_cond_destroy(&PRIVATE->nativeHandle);
#endif
}


void PIConditionVariable::wait(PIMutex & lk) {
#if defined(WINDOWS)
	SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), INFINITE);
#elif defined(FREERTOS)
	xEventGroupClearBits(PRIVATE->nativeHandle, 1);
	xEventGroupWaitBits(PRIVATE->nativeHandle, 1, pdTRUE, pdTRUE, portMAX_DELAY);
#else
	pthread_cond_wait(&PRIVATE->nativeHandle, (pthread_mutex_t *)lk.handle());
#endif
}


void PIConditionVariable::wait(PIMutex & lk, std::function<bool()> condition) {
	while (true) {
		if (condition()) break;
#if defined(WINDOWS)
		SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), INFINITE);
#elif defined(FREERTOS)
		xEventGroupClearBits(PRIVATE->nativeHandle, 1);
		xEventGroupWaitBits(PRIVATE->nativeHandle, 1, pdTRUE, pdTRUE, portMAX_DELAY);
#else
		pthread_cond_wait(&PRIVATE->nativeHandle, (pthread_mutex_t *)lk.handle());
#endif
	}
}


bool PIConditionVariable::waitFor(PIMutex & lk, PISystemTime timeout) {
	bool isNotTimeout;
#if defined(WINDOWS)
	isNotTimeout = SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), timeout.toMilliseconds()) != 0;
#elif defined(FREERTOS)
	xEventGroupClearBits(PRIVATE->nativeHandle, 1);
	EventBits_t uxBits;
	uxBits          = xEventGroupWaitBits(PRIVATE->nativeHandle, 1, pdTRUE, pdTRUE, timeout.toMilliseconds() / portTICK_PERIOD_MS);
	isNotTimeout    = (uxBits & 1) != 0;
#else
	PISystemTime st = PISystemTime::current(true) + timeout;
	timespec expire_ts;
	st.toTimespec(&expire_ts);
	isNotTimeout = pthread_cond_timedwait(&PRIVATE->nativeHandle, (pthread_mutex_t *)lk.handle(), &expire_ts) == 0;
#endif
	return isNotTimeout;
}


bool PIConditionVariable::waitFor(PIMutex & lk, PISystemTime timeout, std::function<bool()> condition) {
#if defined(WINDOWS) || defined(FREERTOS)
	PITimeMeasurer measurer;
#else
	PISystemTime st = PISystemTime::current(true) + timeout;
	timespec expire_ts;
	st.toTimespec(&expire_ts);
#endif
#ifdef FREERTOS
	xEventGroupClearBits(PRIVATE->nativeHandle, 1);
#endif
	while (true) {
		if (condition()) break;
		bool isTimeout;
#if defined(WINDOWS)
		{
			int remain = (int)(timeout.toMilliseconds() - (int)measurer.elapsed_m());
			if (remain <= 0) return false;
			isTimeout = SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), remain) == 0;
		}
#elif defined(FREERTOS)
		{
			int remain = (int)(timeout.toMilliseconds() - (int)measurer.elapsed_m());
			if (remain <= 0) return false;
			EventBits_t uxBits;
			uxBits    = xEventGroupWaitBits(PRIVATE->nativeHandle, 1, pdTRUE, pdTRUE, remain / portTICK_PERIOD_MS);
			isTimeout = (uxBits & 1) == 0;
		}
#else
		isTimeout = pthread_cond_timedwait(&PRIVATE->nativeHandle, (pthread_mutex_t *)lk.handle(), &expire_ts) != 0;
#endif
		if (isTimeout) return false;
	}
	return true;
}


void PIConditionVariable::notifyOne() {
#if defined(WINDOWS)
	WakeConditionVariable(&PRIVATE->nativeHandle);
#elif defined(FREERTOS)
	xEventGroupSetBits(PRIVATE->nativeHandle, 1);
#else
	pthread_cond_signal(&PRIVATE->nativeHandle);
#endif
}


void PIConditionVariable::notifyAll() {
#if defined(WINDOWS)
	WakeAllConditionVariable(&PRIVATE->nativeHandle);
#elif defined(FREERTOS)
	xEventGroupSetBits(PRIVATE->nativeHandle, 1);
#else
	pthread_cond_broadcast(&PRIVATE->nativeHandle);
#endif
}
