/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmtime.c,v 1.16 2005/11/18 01:17:59 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.16 $
 * $Log: rmtime.c,v $
 * Revision 1.16  2005/11/18 01:17:59  wes
 * Bug fix: in private_deInitTimer, reset the static variable to NULL
 * after it has been free to avoid potential reuse of freed memory.
 *
 * Revision 1.15  2005/09/12 04:01:37  wes
 * Modified end-of-nap test in unix code to be <= rather than <.
 *
 * Revision 1.14  2005/09/10 19:07:41  wes
 * Fix for Windows in time conversion. Fixes a long-standing bug in the
 * constant-rate rendering code on Windows.
 *
 * Revision 1.13  2005/06/06 02:04:29  wes
 * Lots of small additions to clean up compiler warnings.
 *
 * Revision 1.12  2005/03/19 17:17:19  wes
 * Win32 clock init stuff, and deinit upon rmFinish (in response to report
 * of 8-byte memory leak).
 *
 * Revision 1.11  2005/02/19 16:22:50  wes
 * Distro sync and consolidation.
 *
 * Revision 1.10  2005/01/23 17:08:42  wes
 * Minor tweaks to improve accuracy.
 *
 * Revision 1.9  2004/03/18 15:49:56  wes
 * Fixed buglet in private_rmTimeSpinDelay that resulted in inaccurate
 * spins for time values greater than, say, tens of ms.
 *
 * Revision 1.8  2004/02/23 03:04:01  wes
 * *** empty log message ***
 *
 * Revision 1.7  2004/01/16 16:51:33  wes
 * (1) Updated copyright line to 2004; (2) changed name of routine from
 * rmTimeDifferenceMilliseconds() to rmTimeDifferenceMS(); (3) rewrote
 * the spinlock routine, tested on Win32 & Unix.
 *
 * Revision 1.6  2003/12/06 03:26:06  wes
 * Documentation tweaks to RMtime routines, updates to RMtime routines for
 * Windows.
 *
 * Revision 1.5  2003/12/01 02:13:05  wes
 * Additions to support constant frame-rate rendering on both Unix and Win32.
 *
 * Revision 1.4  2003/11/21 17:07:52  wes
 * For RM_X platforms, use a spinlock to implement high-resolution sleeps.
 *
 * Revision 1.3  2003/11/16 16:20:06  wes
 * Added routine rmTimeSleep.
 *
 * Revision 1.2  2003/11/05 15:24:29  wes
 * Minor updates.
 *
 */

#include <rm/rm.h>
#include "rmprivat.h"

#ifdef RM_X
#include <sys/time.h>
/* unix-side uses gettimeofday() */
#endif

#ifdef RM_WIN
static LARGE_INTEGER *static_clockFrequency=NULL;
static double static_usecsPerTick=0.0;
#endif


static void private_rmTimeSpinDelay(const RMtime *w, RMtime *prev);

/*
 * ----------------------------------------------------
 * @Name rmTimeCurrent
 @pstart
 RMenum rmTimeCurrent (RMtime *result)
 @pend

 @astart
 RMtime *result - a handle to a caller-supplied RMtime struct.
 @aend

 @dstart

 Time measurement in RM is performed by querying the time with rmTimeCurrent(),
 by computing time difference with rmTimeDifference(), or computing the
 elapsed time in milliseconds with rmTimeDifferenceMS(). 

 rmTimeCurrent() will initialize an RMtime object, which is essentially
 the same as a "struct timeval" on Unix systems, with the current time. The
 caller supplies a handle to an RMtime object, and it will be set to contain
 the  current time. Time zones are not relevant, as the purpose of RM's timing
 facilities is limited to taking millisecond-resolution measurements of time.
 RM_CHILL is returned upon success, or RM_WHACKED upon failure.

 rmTimeDifference() takes two RMtimeVal objects that have been set with
 rmTimeCurrent(), computes the difference in time between the two, and places
 the difference into a third RMtime object.  RM_CHILL is returned upon
 success, or RM_WHACKED upon failure.

 rmTimeDifferenceMS() takes two RMtime objects that have been set
 with rmTimeCurrent(), and returns the difference in milliseconds between
 the two.

 rmTimeSet() allows you to set the secs/usecs fields of the RMtime object
 to specific values. rmTimeGet() will tell you what those values are.

 rmTimeSleep() implements a spinlock high-resolution delay function that
 will block the caller for the amount of time (secs, usecs) specified in
 the input RMtime object.

 rmTimeDecodeMS() will return a double value representing the number of
 milliseconds represented by the values in the input RMtime object.
 rmTimeEncodeMS() will accept a double value interpreted as milliseconds
 and encode that information into an RMtime object.

 rmTimeNew() and rmTimeDelete() are used to create and destroy RMtime
 objects, respectively.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTimeCurrent(RMtime *r)
{
#ifdef RM_X
    struct timeval t1;
    
    if (RM_ASSERT(r,"rmTimeCurrent error: the input RMtime object is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
    gettimeofday(&t1, NULL);
    r->sec = t1.tv_sec;
    r->usec = t1.tv_usec;

#else /* assume RM_WIN */

    LARGE_INTEGER ticks;
    LARGE_INTEGER fracSecs;
    /*
     * in order to be completely thread safe, we need to protect the following
     * if block with a mutex to prevent simultaneous access by multiple threads
     */
    if (RM_ASSERT(r,"rmTimeCurrent error: the input RMtime object is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
    if (static_clockFrequency == NULL)
    {
	static_clockFrequency = (LARGE_INTEGER *)malloc(sizeof(LARGE_INTEGER));
	if (QueryPerformanceFrequency(static_clockFrequency) == 0)
	{
	    rmWarning(" The Win32 implementation you are using does not provide a high resolution clock. This means that it is not possible to use any of the rmTime*() routines on your implementation of Windows.");
	    return RM_WHACKED;
	}
    }

    if (QueryPerformanceCounter(&ticks) == 0)
    {
	rmWarning(" The Win32 implementation you are using does not provide a high resolution clock. This means that it is not possible to use any of the rmTime*() routines on your implementation of Windows.");
	return RM_WHACKED;
    }
    
    r->sec = (long)(ticks.QuadPart / static_clockFrequency->QuadPart); /* secs */
    fracSecs.QuadPart = ticks.QuadPart % static_clockFrequency->QuadPart; /* ticks */
    r->usec = (long)((double)fracSecs.QuadPart * static_usecsPerTick);

#endif

    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmTimeDifference
 @pstart
 RMenum rmTimeDifference (const RMtime *start, const RMtime *end, RMtime *result)
 @pend

 @astart
 const RMtime *start, *end - handles to caller-supplied RMtime structs
 that contain valid time values.
 RMtime *result - a handle to a caler-supplied RMtime struct. This
 value will be computed as the difference between "start" and "end."
 @aend

 @dstart

 Time measurement in RM is performed by querying the time with rmTimeCurrent(),
 by computing time difference with rmTimeDifference(), or computing the
 elapsed time in milliseconds with rmTimeDifferenceMS().

 rmTimeDifference() takes as input two RMtime objects, start and end,
 and computes the difference between them. The result is placed into the
 "result" RMtime object.

 Limitations: it is assumed that time in "end" is later than the value
 in "start." If this assumption doesn't hold, then the computed difference
 may not be accurate.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTimeDifference(const RMtime *s,
		 const RMtime *e,
		 RMtime *d)
{
    /* compute d = e - s */
    long secs, usecs;

    if ((RM_ASSERT(s,"rmTimeDifference() error: the start RMtime is NULL")==RM_WHACKED) ||
	(RM_ASSERT(e,"rmTimeDifference() error: the end RMtimeVal is NULL")==RM_WHACKED) ||
	(RM_ASSERT(d,"rmTimeDifference() error: the result RMtimeVal is NULL") == RM_WHACKED))
    {
	return RM_WHACKED;
    }
    
    secs = e->sec - s->sec;
    usecs = e->usec - s->usec;
    
    if (usecs < 0)
    {
	usecs += 1000000;
	secs -= 1;
    }
    
    d->sec = secs;
    d->usec = usecs;
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmTimeDifferenceMS
 @pstart
 double rmTimeDifferenceMS (const RMtime *start, const RMtime *end)
 @pend

 @astart
 const RMtime *start, *end - handles to caller-supplied RMtime structs
 that contain valid time values.
 @aend

 @dstart

 Time measurement in RM is performed by querying the time with rmTimeCurrent(),
 by computing time differences with rmTimeDifference(), and by reporting the
 time difference in milliseconds with rmTimeDifferenceMS().

 rmTimeDifferenceMS() takes two RMtimeVal objects that have been
 set with rmTimeCurrent(), computes the difference in time between the two,
 and returns a double precision value indicating the number of milliseconds
 difference between the two RMtime objects.
 
 It is assumed that the time value in "end" is greater than or equal to the
 time value in "start." If this assumption does not hold, the computed
 and returned difference may be inaccurate.

 Upon success, a non-negative integer is returned. Upon failure, -1 is
 returned. If the time value in "end" is less than the time value in "start",
 the returned value is not guaranteed to be accurate. It is the application's
 responsibility to ensure the timevalue in "end" is greater than or equal
 to the time value in "start".

 @dend
 * ----------------------------------------------------
 */
double
rmTimeDifferenceMS(const RMtime *s,
		   const RMtime *e)
{
    /* return d = e - s where d is msec */
    long secs, usecs;
    double msecs;

    if ((RM_ASSERT(s,"rmTimeDifferenceMS() error: the start RMtimeVal is NULL") == RM_WHACKED) ||
	(RM_ASSERT(e, "rmTimeDifferenceMS() error: the end RMtimeVal is NULL") == RM_WHACKED))
	return -1;

    secs = e->sec - s->sec;
    usecs = e->usec - s->usec;
    
    if (usecs < 0)
    {
	usecs += 1000000;
	secs -= 1;
    }
    
    msecs = (double)(secs)*1000.0;
    msecs += ((double)(usecs) * 0.001);
    
    return msecs;
}


/*
 * ----------------------------------------------------
 * @Name rmTimeNew
 @pstart
 RMtime * rmTimeNew(void)
 @pend

 @astart
 No arguments.
 @aend

 @dstart
 rmTimeNew() will create an RMtime object, and return the handle of the
 new RMtime object to the caller upon success. Upon failure, NULL is
 returned.

 No special processing is performed inside rmTimeNew(). It is safe for
 applications to allocate RMtime objects off the stack using normal
 variable declaration if so desired.

 @dend
 * ----------------------------------------------------
 */
RMtime *
rmTimeNew(void)
{
    RMtime *r = (RMtime *)malloc(sizeof(RMtime));
    if (r == NULL)
    {
	rmError("rmTimeNew() malloc failure.");
	return NULL;
    }
    
    rmTimeSet(r, (long)0, (long)0);
    return r;
}

/*
 * ----------------------------------------------------
 * @Name rmTimeDelete
 @pstart
 RMenum rmTimeDelete(RMtime *toDelete)
 @pend

 @astart
 RMtime *toDelete - handle to an RMtime object that will be deleted.
 @aend

 @dstart
 This routine will free resources associated with the input RMtime object
 "toDelete." Upon success, RM_CHILL is returned. Upon failure, RM_WHACKED
 is returned.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmTimeDelete(RMtime *d)
{
    if (RM_ASSERT(d,"rmTimeDelete() error: the input RMtime is NULL."))
	return RM_WHACKED;
    
    free((void *)d);
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmTimeSet
 @pstart
 RMenum rmTimeSet(RMtime *toModify, long secs, long usecs)
 @pend

 @astart
 long secs, usecs: input arguments specifing number of seconds and
 microseconds, respectively.
 @aend

 @dstart
 rmTimeSet is used to assign known values to the RMtime object
 "toModify." The RMtime object contains two fields - seconds and
 microseconds. When set by rmTimeCurrent(), those values represent
 the amount of time elapsed  since the current epoch.

 (Note that since we use longs, OpenRM is Y2038 safe!!)

 Upon success, RM_CHILL is returned. Upon failure, RM_WHACKED is returned.

 Limitations: OpenRM doesn't perform any sanity checking on the input
 values for secs/usecs. It is possible to assign garbage to an RMtime
 object.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTimeSet(RMtime *r,
	  long secs,
	  long usecs)
{
    if (RM_ASSERT(r,"rmTimeSet() error: the input RMtime is NULL.") == RM_WHACKED)
	return RM_WHACKED;
    
    r->sec = secs;
    r->usec = usecs;

    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmTimeGet
 @pstart
 RMenum rmTimeGet(const RMtime *toQuery, long *returnSecs, long *returnUSecs)
 @pend

 @astart
 const RMtime *toQuery - an input RMtime object.
 long *returnSecs, long *returnUSecs - pointers to longs.
 @aend

 @dstart
 rmTimeGet() will copy the seconds/microseconds fields from the input RMtime
 object "toQuery" into the memory pointed to by "returnSecs" and
 "returnUSecs."

 If you just want to know the number of milliseconds contained in an RMtime
 representation, use the routine rmTimeDecodsMS().

 RM_CHILL is returned upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTimeGet(const RMtime *r,
	  long *returnSecs,
	  long *returnUSecs)
{
    if (RM_ASSERT(r,"rmTimeGet() error: the input RMtime object is NULL.") == RM_WHACKED)
	return RM_WHACKED;

    if (returnSecs != NULL)
	*returnSecs = r->sec;
    if (returnUSecs != NULL)
	*returnUSecs = r->usec;

    return RM_CHILL;
}


/* this is routine that was used internally for testing. it permits the
   caller to know how much error there was from a sleep operation. it works
   OK, but isn't really needed for our purposes. We might add it back into
   the public API at some point if someone asks for it. */
RMenum
rmTimeSleepDrift(const RMtime *tSleep,
		 RMtime *drift)
{
    private_rmTimeSpinDelay(tSleep, drift);
    return RM_CHILL;
}


/*
 * ----------------------------------------------------
 * @Name rmTimeSleep
 @pstart
 RMenum rmTimeSleep(const RMtime *toSleep)
 @pend

 @astart
 RMtime *toSleep - input RMtime object that specifies the length of the
 sleep period.

 @aend

 @dstart
 rmTimeSleep() will block execution of the caller for the amount of time
 specified in the toSleep argument. This routine will block execution for
 a period of time that is at least as long as the time specified in the
 toSleep object. In our preliminary testing, we have found the error to
 be quite small - less than a microsecond on current hardware.

 Internally, a high-resolution spinlock is used to implement precision naps.
 There is a large body of knowledge on the subject of problems with precision
 sleeps. The brute-force solution is to use a spinlock, which is what we do
 here. The two primary advantages of the spinlock are: (1) they are of
 a very high resolution, and (2) the time required to wake up from a nap
 is less likely to be interruped or influenced by the OS doing something
 else, like checking to see if the printer is awake.

 Since we use a spinlock, your CPU meter will be pegged at 100% during
 rmTimeSleep naps.

 This routine returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTimeSleep(const RMtime *tSleep)
{
    if (RM_ASSERT(tSleep,"rmTimeSleep() error: the input RMtime object is NULL.") == RM_WHACKED)
	return RM_WHACKED;
    
    private_rmTimeSpinDelay(tSleep, NULL);
    return RM_CHILL;
}


/*
 * ----------------------------------------------------
 * @Name rmTimeEncodeMS
 @pstart
 RMenum rmTimeEncodeMS(RMtime *toModify, double ms)
 @pend

 @astart
 RMtime *toModify - input RMtime object to be modified.
 double ms - double precision value interpreted as milliseconds.
 @aend

 @dstart
 rmTimeEncodeMS() will take an input double precision value that is
 interpreted as milliseconds, and encode the number of milliseconds
 (that may be fractional) into the RMtime object.

 RM_CHILL is returned upon success, and RM_WHACKED is returned upon
 failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTimeEncodeMS(RMtime *t,
	       double ms)
{
    long sec, usec;
    
    if (RM_ASSERT(t,"rmTimeEncodeMS() error: the input RMtime object is NULL.") == RM_WHACKED)
	return RM_WHACKED;

    sec = (long)(ms) / 1000;
    usec = (long)(ms * 1000.0F) % 1000000;
    
    rmTimeSet(t, sec, usec);

    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmTimeDecodeMS
 @pstart
 RMenum rmTimeDecodeMS(const RMtime *toQuery, double *resultMS)
 @pend

 @astart
 const RMtime *toQuery - an input RMtime object.
 double *resultMS - a pointer to a double precision value; the results of
   this routine will be returned in this caller-supplied memory.
 @aend

 @dstart
 rmTimeDecodeMS will compute the number of milliseconds represented by
 the contents of the input RMtime object toQuery, and return the results
 into caller supplied memory.

 RM_CHILL is returned upon success, and RM_WHACKED is returned upon
 failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmTimeDecodeMS(const RMtime *src, double *resultMS)
{
    double r;
    long sec, usec;

    if (RM_ASSERT(src,"rmDecodeMS() error: the input RMtime object is NULL.") == RM_WHACKED)
	return RM_WHACKED;
    
    rmTimeGet(src, &sec, &usec);
    
    r = (double)(sec * 1000);
    r += (double)(usec / 1000);
    
    *resultMS = r;

    return RM_CHILL;
}

/* internal/private - this is the spindelay loop. */
static void
private_rmTimeSpinDelay(const RMtime *w,
			RMtime *drift)
{
    /*
     * the X (non-Windoze) version of the timer interfaces directly
     * to gettimeofday(), and uses only integer arithmetic. the
     * Win32 version uses floating point math.
     *
     * Early testing showed that the code in the Win32 section, which is
     * painfully correct and easy to read, runs more slowly in some
     * circumstances than the RM_X code that uses gettimeofday() and
     * integer math. Oh well.
     *
     * Testing of the Win32 code shows that the Win32 clock is of
     * higher resolution and accuracy than that affored by gettimeofday().
     * Sad, but true.
     *
     * Future work:
     * 1. strive for even better accuracy
     * 2. Update the windoze code to use only integer math.
     */
#ifdef RM_X 
    struct timeval start,now,d;
    gettimeofday(&start,0);
    do
    {
	gettimeofday(&now,0);
	d.tv_sec = now.tv_sec - start.tv_sec;
	d.tv_usec = now.tv_usec - start.tv_usec;

	if (d.tv_usec < 0)
	{
	    d.tv_usec += 1000000; 
	    d.tv_sec--;  
	}

    }
    while((d.tv_sec<w->sec) || ((d.tv_sec==w->sec && d.tv_usec<=w->usec)));

    if (drift != NULL)
    {
	long usec = d.tv_usec - w->usec;

	if (usec < 0)
	    rmWarning("private_rmTimeSpinDelay() : usec < 0 \n");
	/*	usec++; */ /* fudge factor */
	usec--;
	rmTimeSet(drift, 0, usec);
    }
#else

    RMtime start, now, d;
    double waitMS;
    double startMS, nowMS, deltaMS;
    
    rmTimeDecodeMS(w,&waitMS);
    rmTimeCurrent(&start);
    rmTimeDecodeMS(&start, &startMS);

    rmTimeCurrent(&now);
    rmTimeDecodeMS(&now, &nowMS);
    deltaMS = nowMS - startMS;

    for (; deltaMS < waitMS ; )
    {
	rmTimeCurrent(&now);
	rmTimeDecodeMS(&now, &nowMS);
	deltaMS = nowMS - startMS;
    }

    /*
     * how late are we?
     * d = now-start, which is the amount of elapsed time
     * so d-w would the the amount we are over budget. We assume
     * that d >= w, and we also assume that the seconds component of
     * d and w are equal.
     */
    if (drift != NULL)
    {
	double driftMS = deltaMS - waitMS;
	rmTimeEncodeMS(drift, driftMS);
	
    }
#endif

}

RMenum
private_initTimer(void)
{
#ifdef RM_WIN
    if (static_clockFrequency == NULL)
    {
	static_clockFrequency = (LARGE_INTEGER *)malloc(sizeof(LARGE_INTEGER));
	if (QueryPerformanceFrequency(static_clockFrequency) == 0)
	{
	    rmWarning(" The Win32 implementation you are using does not provide a high resolution clock. This means that it is not possible to use any of the rmTime*() routines on your implementation of Windows.");
	    return RM_WHACKED;
	}
	static_usecsPerTick = 1000000.0/(double)((static_clockFrequency->QuadPart));

    }
#endif
    /* only windows needs a timer init */
    return RM_CHILL;
}

void
private_deInitTimer(void)
{
#ifdef RM_WIN
    if (static_clockFrequency != NULL)
    {
	free((void *)static_clockFrequency);
	static_clockFrequency = NULL;
    }
#endif
}

/* EOF */
