/*
	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 "piconditionvar.h"
#include "pithread.h"
#include "pitime.h"

#ifdef WINDOWS
#  undef _WIN32_WINNT
#  define _WIN32_WINNT 0x0600
#  include <synchapi.h>
#  include <windef.h>
#  include <winbase.h>
#endif


PRIVATE_DEFINITION_START(PIConditionVariable)
#ifdef WINDOWS
	CONDITION_VARIABLE nativeHandle;
#else
	pthread_cond_t nativeHandle;
#endif
	bool isDestroying;
PRIVATE_DEFINITION_END(PIConditionVariable)


PIConditionVariable::PIConditionVariable() {
#ifdef WINDOWS
	InitializeConditionVariable(&PRIVATE->nativeHandle);
#else
	PRIVATE->isDestroying = false;

	pthread_condattr_t condattr;
	pthread_condattr_init(&condattr);
#  ifndef MAC_OS
	pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);
#  endif
	memset(&(PRIVATE->nativeHandle), 0, sizeof(PRIVATE->nativeHandle));
	pthread_cond_init(&PRIVATE->nativeHandle, &condattr);
#endif
}


PIConditionVariable::~PIConditionVariable() {
#ifdef WINDOWS
#else
	pthread_cond_destroy(&PRIVATE->nativeHandle);
#endif
}


void PIConditionVariable::wait(PIMutex& lk) {
#ifdef WINDOWS
	SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), INFINITE);
#else
	pthread_cond_wait(&PRIVATE->nativeHandle, (pthread_mutex_t*)lk.handle());
#endif
}


void PIConditionVariable::wait(PIMutex& lk, const std::function<bool()>& condition) {
	bool isCondition;
	while (true) {
		isCondition = condition();
		if (isCondition) break;
#ifdef WINDOWS
		SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), INFINITE);
#else
		pthread_cond_wait(&PRIVATE->nativeHandle, (pthread_mutex_t*)lk.handle());
#endif
		if (PRIVATE->isDestroying) return;
	}
}


bool PIConditionVariable::waitFor(PIMutex &lk, int timeoutMs) {
	bool isNotTimeout;
#ifdef WINDOWS
	isNotTimeout = SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), timeoutMs) != 0;
#else
	timespec expire_ts;
	PISystemTime st = PISystemTime::current(true);
	st.addMilliseconds(timeoutMs);
	st.toTimespec(&expire_ts);
	isNotTimeout = pthread_cond_timedwait(&PRIVATE->nativeHandle, (pthread_mutex_t*)lk.handle(), &expire_ts) == 0;
#endif
	if (PRIVATE->isDestroying) return false;
	return isNotTimeout;
}


bool PIConditionVariable::waitFor(PIMutex& lk, int timeoutMs, const std::function<bool()> &condition) {
	bool isCondition;
#ifdef WINDOWS
	PITimeMeasurer measurer;
#else
	timespec expire_ts;
	PISystemTime st = PISystemTime::current(true);
	st.addMilliseconds(timeoutMs);
	st.toTimespec(&expire_ts);
#endif
	while (true) {
		isCondition = condition();
		if (isCondition) break;
#ifdef WINDOWS
		bool isTimeout = SleepConditionVariableCS(
					&PRIVATE->nativeHandle,
					(PCRITICAL_SECTION)lk.handle(),
					timeoutMs - (int)measurer.elapsed_m()) == 0;
#else
		bool isTimeout = pthread_cond_timedwait(&PRIVATE->nativeHandle, (pthread_mutex_t*)lk.handle(), &expire_ts) != 0;
#endif
		if (isTimeout) return false;
		if (PRIVATE->isDestroying) return false;
	}
	return true;
}


void PIConditionVariable::notifyOne() {
#ifdef WINDOWS
	WakeConditionVariable(&PRIVATE->nativeHandle);
#else
	pthread_cond_signal(&PRIVATE->nativeHandle);
#endif
}


void PIConditionVariable::notifyAll() {
#ifdef WINDOWS
	WakeAllConditionVariable(&PRIVATE->nativeHandle);
#else
	pthread_cond_broadcast(&PRIVATE->nativeHandle);
#endif
}

