/*****************************************************************************
 * mtime.h: high resolution time management functions
 * This header provides portable high precision time management functions,
 * which should be the only ones used in other segments of the program, since
 * functions like gettimeofday() and ftime() are not always supported.
 * Most functions are declared as inline or as macros since they are only
 * interfaces to system calls and have to be called frequently.
 * 'm' stands for 'micro', since maximum resolution is the microsecond.
 * Functions prototyped are implemented in interface/mtime.c.
 *****************************************************************************
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 the VideoLAN team
 * $Id: mtime.h 13905 2006-01-12 23:10:04Z dionoea $
 *
 * Authors: Vincent Seguin <seguin@via.ecp.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#pragma once

#include <windows.h>
#include <mmsystem.h>

#pragma comment(lib, "Winmm.lib")

/*****************************************************************************
 * LAST_MDATE: date which will never happen
 *****************************************************************************
 * This date can be used as a 'never' date, to mark missing events in a function
 * supposed to return a date, such as nothing to display in a function
 * returning the date of the first image to be displayed. It can be used in
 * comparaison with other values: all existing dates will be earlier.
 *****************************************************************************/
#define LAST_MDATE ((mtime_t)((uint64_t)(-1)/2))

/*****************************************************************************
 * MSTRTIME_MAX_SIZE: maximum possible size of mstrtime
 *****************************************************************************
 * This values is the maximal possible size of the string returned by the
 * mstrtime() function, including '-' and the final '\0'. It should be used to
 * allocate the buffer.
 *****************************************************************************/
#define MSTRTIME_MAX_SIZE 22

/* Well, Duh? But it does clue us in that we are converting from
   millisecond quantity to a second quantity or vice versa.
*/
#define MILLISECONDS_PER_SEC 1000

#define msecstotimestr(psz_buffer, msecs) \
  secstotimestr( psz_buffer, (msecs / (int) MILLISECONDS_PER_SEC) )

#if defined( __cplusplus )
extern "C" {
#endif

/*****************************************************************************
 * Prototypes
 *****************************************************************************/

/**
* More precise sleep()
*
* Portable usleep() function.
* \param delay the amount of time to sleep
*/
void msleep( mtime_t delay )
{
	Sleep( (int) (delay / 1000) );
}

/**
* Return a date in a readable format
*
* This function converts a mtime date into a string.
* psz_buffer should be a buffer long enough to store the formatted
* date.
* \param date to be converted
* \param psz_buffer should be a buffer at least MSTRTIME_MAX_SIZE characters
* \return psz_buffer is returned so this can be used as printf parameter.
*/
char *mstrtime( char *psz_buffer, mtime_t date )
{
	static mtime_t ll1000 = 1000, ll60 = 60, ll24 = 24;

	snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%02d:%02d:%02d-%03d.%03d",
		(int) (date / (ll1000 * ll1000 * ll60 * ll60) % ll24),
		(int) (date / (ll1000 * ll1000 * ll60) % ll60),
		(int) (date / (ll1000 * ll1000) % ll60),
		(int) (date / ll1000 % ll1000),
		(int) (date % ll1000) );
	return( psz_buffer );
}

/**
* Return high precision date
*
* Use a 1 MHz clock when possible, or 1 kHz
*
* Beware ! It doesn't reflect the actual date (since epoch), but can be the machine's uptime or anything (when monotonic clock is used)
*/
mtime_t mdate( void )
{
	mtime_t res;

	/* We don't need the real date, just the value of a high precision timer */
	static mtime_t freq = INT64_C(-1);

	if( freq == INT64_C(-1) )
	{
		/* Extract from the Tcl source code:
		* (http://www.cs.man.ac.uk/fellowsd-bin/TIP/7.html)
		*
		* Some hardware abstraction layers use the CPU clock
		* in place of the real-time clock as a performance counter
		* reference.  This results in:
		*    - inconsistent results among the processors on
		*      multi-processor systems.
		*    - unpredictable changes in performance counter frequency
		*      on "gearshift" processors such as Transmeta and
		*      SpeedStep.
		* There seems to be no way to test whether the performance
		* counter is reliable, but a useful heuristic is that
		* if its frequency is 1.193182 MHz or 3.579545 MHz, it's
		* derived from a colorburst crystal and is therefore
		* the RTC rather than the TSC.  If it's anything else, we
		* presume that the performance counter is unreliable.
		*/
		LARGE_INTEGER buf;

		freq = ( QueryPerformanceFrequency( &buf ) &&
			(buf.QuadPart == INT64_C(1193182) || buf.QuadPart == INT64_C(3579545) ) )
			? buf.QuadPart : 0;


		/* on windows 2000, XP and Vista detect if there are two
		cores there - that makes QueryPerformanceFrequency in
		any case not trustable?
		(may also be true, for single cores with adaptive
		CPU frequency and active power management?)
		*/
		HINSTANCE h_Kernel32 = LoadLibraryA("kernel32.dll");
		if(h_Kernel32)
		{
			typedef void WINAPI DLL_FUN (LPSYSTEM_INFO);

			DLL_FUN* pf_GetSystemInfo;
			pf_GetSystemInfo = (DLL_FUN*) GetProcAddress(h_Kernel32, "GetSystemInfo");
			if(pf_GetSystemInfo)
			{
				SYSTEM_INFO system_info;
				pf_GetSystemInfo(&system_info);
				if(system_info.dwNumberOfProcessors > 1)
					freq = 0;
			}
			FreeLibrary(h_Kernel32);
		}
	}

	if( freq != 0 )
	{
		LARGE_INTEGER counter;
		QueryPerformanceCounter (&counter);

		/* Convert to from (1/freq) to microsecond resolution */
		/* We need to split the division to avoid 63-bits overflow */
		ldiv_t d = ldiv (counter.QuadPart, freq);

		res = (d.quot * 1000000) + ((d.rem * 1000000) / freq);
	}
	else
	{
		/* Fallback on timeGetTime() which has a milisecond resolution
		* (actually, best case is about 5 ms resolution)
		* timeGetTime() only returns a DWORD thus will wrap after
		* about 49.7 days so we try to detect the wrapping. */

		static CRITICAL_SECTION date_lock;
		static mtime_t i_previous_time = INT64_C(-1);
		static int i_wrap_counts = -1;

		if( i_wrap_counts == -1 )
		{
			/* Initialization */
			i_previous_time = INT64_C(1000) * timeGetTime();
			InitializeCriticalSection( &date_lock );
			i_wrap_counts = 0;
		}

		EnterCriticalSection( &date_lock );
		res = INT64_C(1000) *
			(i_wrap_counts * INT64_C(0x100000000) + timeGetTime());
		if( i_previous_time > res )
		{
			/* Counter wrapped */
			i_wrap_counts++;
			res += INT64_C(0x100000000) * 1000;
		}
		i_previous_time = res;
		LeaveCriticalSection( &date_lock );
	}

	return res;
}

/**
* Wait for a date
*
* This function uses select() and an system date function to wake up at a
* precise date. It should be used for process synchronization. If current date
* is posterior to wished date, the function returns immediately.
* \param date The date to wake up at
*/
void mwait( mtime_t date )
{
	mtime_t usec_time, delay;

	usec_time = mdate();
	delay = date - usec_time;
	if( delay <= 0 )
	{
		return;
	}
	msleep( delay );
}

/**
* Convert seconds to a time in the format h:mm:ss.
*
* This function is provided for any interface function which need to print a
* time string in the format h:mm:ss
* date.
* \param secs  the date to be converted
* \param psz_buffer should be a buffer at least MSTRTIME_MAX_SIZE characters
* \return psz_buffer is returned so this can be used as printf parameter.
*/
char *secstotimestr( char *psz_buffer, int i_seconds )
{
	snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%d:%2.2d:%2.2d",
		(int) (i_seconds / (60 *60)),
		(int) ((i_seconds / 60) % 60),
		(int) (i_seconds % 60) );
	return( psz_buffer );
}

/*****************************************************************************
 * date_t: date incrementation without long-term rounding errors
 *****************************************************************************/
struct date_t
{
    mtime_t  date;
    uint32_t i_divider_num;
    uint32_t i_divider_den;
    uint32_t i_remainder;
};

/*
* Date management (internal and external)
*/

/**
* Initialize a date_t.
*
* \param date to initialize
* \param divider (sample rate) numerator
* \param divider (sample rate) denominator
*/

void date_Init( date_t *p_date, uint32_t i_divider_n, uint32_t i_divider_d )
{
	p_date->date = 0;
	p_date->i_divider_num = i_divider_n;
	p_date->i_divider_den = i_divider_d;
	p_date->i_remainder = 0;
}

/**
* Change a date_t.
*
* \param date to change
* \param divider (sample rate) numerator
* \param divider (sample rate) denominator
*/

void date_Change( date_t *p_date, uint32_t i_divider_n, uint32_t i_divider_d )
{
	p_date->i_divider_num = i_divider_n;
	p_date->i_divider_den = i_divider_d;
}

/**
* Set the date value of a date_t.
*
* \param date to set
* \param date value
*/
void date_Set( date_t *p_date, mtime_t i_new_date )
{
	p_date->date = i_new_date;
	p_date->i_remainder = 0;
}

/**
* Get the date of a date_t
*
* \param date to get
* \return date value
*/
mtime_t date_Get( const date_t *p_date )
{
	return p_date->date;
}

/**
* Move forwards or backwards the date of a date_t.
*
* \param date to move
* \param difference value
*/
void date_Move( date_t *p_date, mtime_t i_difference )
{
	p_date->date += i_difference;
}

/**
* Increment the date and return the result, taking into account
* rounding errors.
*
* \param date to increment
* \param incrementation in number of samples
* \return date value
*/
mtime_t date_Increment( date_t *p_date, uint32_t i_nb_samples )
{
	mtime_t i_dividend = (mtime_t)i_nb_samples * 1000000;
	p_date->date += i_dividend / p_date->i_divider_num * p_date->i_divider_den;
	p_date->i_remainder += (int)(i_dividend % p_date->i_divider_num);

	if( p_date->i_remainder >= p_date->i_divider_num )
	{
		/* This is Bresenham algorithm. */
		p_date->date += p_date->i_divider_den;
		p_date->i_remainder -= p_date->i_divider_num;
	}

	return p_date->date;
}

#if defined( __cplusplus )
}
#endif