/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    dlltimer.c

Abstract:

    This module implements client side stubs for timer related APIs

Author:

    Mark Lucovsky (markl) 08-Aug-1989

Revision History:

--*/

#include "psxdll.h"
#include <signal.h>


ULONG MagicMultiplier = 10000000;

VOID
SecondsToTime (
    OUT PLARGE_INTEGER Time,
    IN ULONG Seconds
    )

/*++

Routine Description:

    This function converts from seconds to an equivalent
    relative time value in units of 100ns intervals.

Arguments:

    Time - Returns the equivalant relative time value.

    Seconds - Supplies the time in seconds.

Return Value:

    None.

--*/


{
    Time->QuadPart = (LONGLONG)Seconds * (LONGLONG)MagicMultiplier;
    Time->QuadPart = -Time->QuadPart;
}

ULONG
TimeToSeconds (
    IN PLARGE_INTEGER Time
    )

/*++

Routine Description:

    This function converts from absolute time in units of 100ns
    intervals to seconds.

Arguments:

    Time - Supplies an absolute time in units of 100ns intervals.

Return Value:

    The value of time in seconds.

--*/

{
    LARGE_INTEGER Seconds;

#if 0
    Seconds = RtlExtendedMagicDivide(
		*Time,
		MagicDivisor,
		MagicShiftCount
		);

#else
    ULONG R;			// remainder

    Seconds = RtlExtendedLargeIntegerDivide(
	*Time,			// Dividend
	MagicMultiplier,	// Divisor
	&R
	);

#endif
    return Seconds.LowPart;

}

unsigned int _CRTAPI1
alarm(unsigned int seconds)
{
	PSX_API_MSG m;
	PPSX_ALARM_MSG args;
	NTSTATUS st;
	unsigned int PreviousSeconds;

	args = &m.u.Alarm;
	PSX_FORMAT_API_MSG(m, PsxAlarmApi, sizeof(*args));

	if (0 == seconds) {
		args->CancelAlarm = TRUE;
	} else {
		SecondsToTime(&args->Seconds, seconds);
		args->CancelAlarm = FALSE;
	}

	args->PreviousSeconds.LowPart = 0;
	args->PreviousSeconds.HighPart = 0;

	st = NtRequestWaitReplyPort(PsxPortHandle, (PPORT_MESSAGE)&m,
		(PPORT_MESSAGE)&m);

#ifdef PSX_MORE_ERRORS
	ASSERT(NT_SUCCESS(st));
#endif

	PreviousSeconds = TimeToSeconds(&args->PreviousSeconds);

	if (args->PreviousSeconds.LowPart != 0 && PreviousSeconds == 0)
		PreviousSeconds = 1;

	return PreviousSeconds;
}

static void
_CRTAPI1
sigalrm(int signo)
{
	//
	// XXX.mjb: do we need to do anything here?
	//
}

unsigned int
_CRTAPI1
sleep(unsigned int seconds)
{
	unsigned int prev, t;
	PVOID ohandler;
	sigset_t set, oset;

	if (seconds == 0) {
		getpid();		// encourage context switch
		return 0;
	}

	sigemptyset(&set);
	sigaddset(&set, SIGALRM);
	sigprocmask(SIG_UNBLOCK, &set, &oset);

	prev = alarm(0);

	if (0 != prev && prev < seconds) {
		//
		// There was already an alarm scheduled, and it would
		// have gone off before the sleep should have been done.
		// We sleep for the shorter amount of time, and return
		// with no alarm scheduled.
		//

		sigset_t s;

		// block SIGALRM until we're ready for it.

		sigemptyset(&s);
		sigaddset(&s, SIGALRM);
		sigprocmask(SIG_BLOCK, &s, NULL);

		(void)alarm(prev);		// restore previous alarm

		s = oset;
		sigdelset(&s, SIGALRM);
		sigsuspend(&s);

		sigprocmask(SIG_SETMASK, &oset, NULL);
		return seconds - prev;
	}


	ohandler = signal(SIGALRM, sigalrm);

	{
		sigset_t s;

#if 1
		// block SIGALARM until we're ready for it.

		sigemptyset(&s);
		sigaddset(&s, SIGALRM);
		sigprocmask(SIG_BLOCK, &s, NULL);
#endif

		(void)alarm(seconds);
#if 1
		s = oset;
		sigdelset(&s, SIGALRM);
		sigsuspend(&s);
#else
		pause();
#endif
	}

	t = alarm(0);
	signal(SIGALRM, ohandler);

	if (0 != prev) {
		//
		// There was a previous alarm scheduled, and we re-schedule
		// it to make it look like we hadn't fiddled with it.
		//

		if (prev - seconds == 0) {
			//
			// the previously-scheduled alarm would have gone
			// off at the same time the sleep was supposed to
			// return.  We want to make sure two alarms are
			// actually delivered, so we add a second to the
			// previous alarm time.
			//
			prev++;
		}
		(void)alarm(prev - seconds);
	}

	sigprocmask(SIG_SETMASK, &oset, NULL);

	return t;
}