// Copyright (c) 2004 David Muse
// See the COPYING file for more information

#include <rudiments/private/config.h>
#include <rudiments/snooze.h>
#include <rudiments/error.h>

#if defined(RUDIMENTS_HAVE_NANOSLEEP) || defined(RUDIMENTS_HAVE_CLOCK_NANOSLEEP)
	#include <time.h>
#else
	#include <sys/types.h>
	#ifdef RUDIMENTS_HAVE_SYS_SELECT_H
		#include <sys/select.h>
	#endif
	#ifdef RUDIMENTS_HAVE_UNISTD_H
		#include <unistd.h>
	#endif
#endif

#ifdef RUDIMENTS_NAMESPACE
namespace rudiments {
#endif

bool snooze::macrosnooze(long seconds) {
	timespec	nanotimetosnooze;
	nanotimetosnooze.tv_sec=seconds;
	nanotimetosnooze.tv_nsec=0;
	return nanosnooze(&nanotimetosnooze);
}

bool snooze::macrosnooze(long seconds, long *remainingseconds) {
	timespec	nanotimetosnooze;
	nanotimetosnooze.tv_sec=seconds;
	nanotimetosnooze.tv_nsec=0;
	timespec	nanotimeremaining;
	bool	retval=nanosnooze(&nanotimetosnooze,&nanotimeremaining);
	*remainingseconds=nanotimeremaining.tv_sec;
	return retval;
}

bool snooze::microsnooze(long seconds, long microseconds) {
	timeval		timetosnooze;
	timetosnooze.tv_sec=seconds;
	timetosnooze.tv_usec=microseconds;
	return microsnooze(&timetosnooze);
}

bool snooze::microsnooze(long seconds, long microseconds,
		long *secondsremaining, long *microsecondsremaining) {
	timeval		timetosnooze;
	timetosnooze.tv_sec=seconds;
	timetosnooze.tv_usec=microseconds;
	timeval		timeremaining;
	bool	retval=microsnooze(&timetosnooze,&timeremaining);
	if (secondsremaining) {
		*secondsremaining=timeremaining.tv_sec;
	}
	if (microsecondsremaining) {
		*microsecondsremaining=timeremaining.tv_usec;
	}
	return retval;
}

bool snooze::microsnooze(timeval *timetosnooze) {
	timespec	nanotimetosnooze;
	nanotimetosnooze.tv_sec=timetosnooze->tv_sec;
	nanotimetosnooze.tv_nsec=timetosnooze->tv_usec*1000;
	return nanosnooze(&nanotimetosnooze);
}

bool snooze::microsnooze(timeval *timetosnooze, timeval *timeremaining) {
	timespec	nanotimetosnooze;
	nanotimetosnooze.tv_sec=timetosnooze->tv_sec;
	nanotimetosnooze.tv_nsec=timetosnooze->tv_usec*1000;
	timespec	nanotimeremaining;
	bool	retval=nanosnooze(&nanotimetosnooze,&nanotimeremaining);
	timeremaining->tv_sec=nanotimeremaining.tv_sec;
	timeremaining->tv_usec=nanotimeremaining.tv_nsec/1000;
	return retval;
}

bool snooze::nanosnooze(long seconds, long nanoseconds) {
	timespec	timetosnooze;
	timetosnooze.tv_sec=seconds;
	timetosnooze.tv_nsec=nanoseconds;
	return nanosnooze(&timetosnooze);
}

bool snooze::nanosnooze(long seconds, long nanoseconds,
				long *secondsremaining,
				long *nanosecondsremaining) {
	timespec	timetosnooze;
	timetosnooze.tv_sec=seconds;
	timetosnooze.tv_nsec=nanoseconds;
	timespec	timeremaining;
	bool	retval=nanosnooze(&timetosnooze,&timeremaining);
	if (secondsremaining) {
		*secondsremaining=timeremaining.tv_sec;
	}
	if (nanosecondsremaining) {
		*nanosecondsremaining=timeremaining.tv_nsec;
	}
	return retval;
}

bool snooze::nanosnooze(timespec *timetosnooze) {

	timespec	snoozetime;
	snoozetime.tv_sec=timetosnooze->tv_sec;
	snoozetime.tv_nsec=timetosnooze->tv_nsec;
	timespec	remaining;
	for (;;) {
		if (nanosnooze(&snoozetime,&remaining)) {
			return true;
		} else if (error::getErrorNumber()!=EINTR) {
			return false;
		}
		snoozetime.tv_sec=remaining.tv_sec;
		snoozetime.tv_nsec=remaining.tv_nsec;
	}
}

bool snooze::nanosnooze(timespec *timetosnooze, timespec *timeremaining) {

	#ifdef RUDIMENTS_HAVE_NANOSLEEP
	return !::nanosleep(timetosnooze,timeremaining);
	#elif RUDIMENTS_HAVE_CLOCK_NANOSLEEP
	return !clock_nanosleep(CLOCK_REALTIME,TIME_ABSTIME,
					timetosnooze,timeremaining);
	#elif MINGW32
		// on windows, we only have millisecond resolution and we
		// can't the remaining time back
		sleep(timetosnooze->tv_sec*1000+timetosnooze->tv_nsec/1000000);

		// set timeremaining to 0
		timeremaining->tv_sec=0;
		timeremaining->tv_nsec=0;
		return true;
	#else

		// use regular sleep command to handle the whole seconds
		if (timetosnooze->tv_sec) {
			unsigned int	remainder=::sleep(timetosnooze->tv_sec);
			if (remainder) {
				timeremaining->tv_sec=remainder;
				timeremaining->tv_nsec=timetosnooze->tv_nsec;
				return false;
			}
		}

		// set timeremaining to 0
		timeremaining->tv_sec=0;
		timeremaining->tv_nsec=0;

		// Use select or pselect to wait for the remaining time.
		//
		// It's tempting just to use select/pselect to sleep the entire
		// interval.  But on many platforms, select() doesn't modify
		// it's time struct to indicate how much time was left when it
		// timed out.  And on the platforms that it does do that, if
		// an error occurs such as being interrupted by a signal, then
		// the values in the time struct are undefined anyway.
		//
		// So, when using select/pselect, we can't return the number of
		// nanoseconds that were left if a signal interrupts the call.
		// But, at least we can return the number of seconds.
		#ifdef RUDIMENTS_HAVE_PSELECT
		timespec	ts;
		ts.tv_sec=0;
		ts.tv_nsec=timetosnooze->tv_nsec;
		return (pselect(0,NULL,NULL,NULL,&ts,NULL)!=-1);
		#else
		timeval		tv;
		tv.tv_sec=0;
		tv.tv_usec=timetosnooze->tv_nsec/1000;
		return (select(0,NULL,NULL,NULL,&tv)!=-1);
		#endif

	#endif
}

#ifdef RUDIMENTS_NAMESPACE
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1