/***************************************************************************

    timer.h

    Functions needed to generate timing and synchronization between several
    CPUs.

    Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

***************************************************************************/

#pragma once

#ifndef __TIMER_H__
#define __TIMER_H__

#include "mamecore.h"
#include <math.h>


/***************************************************************************
    CONSTANTS
***************************************************************************/

/* subseconds are tracked in attosecond (10^-18) increments */
#define MAX_SUBSECONDS_SQRT			((subseconds_t)1000000000)
#define MAX_SUBSECONDS				(MAX_SUBSECONDS_SQRT * MAX_SUBSECONDS_SQRT)
#define MAX_SECONDS					((seconds_t)1000000000)



/***************************************************************************
    MACROS
***************************************************************************/

/* convert between a double and subseconds */
#define SUBSECONDS_TO_DOUBLE(x)		((double)(x) * (1.0 / (double)MAX_SUBSECONDS))
#define DOUBLE_TO_SUBSECONDS(x)		((subseconds_t)((x) * (double)MAX_SUBSECONDS))
#define USEC_TO_SUBSECONDS(us)		((subseconds_t)((us) * (MAX_SUBSECONDS / 1000000)))

/* hertz to subseconds macros */
#define SUBSECONDS_TO_HZ(x)			((double)MAX_SUBSECONDS / (double)(x))
#define HZ_TO_SUBSECONDS(x)			(MAX_SUBSECONDS / (x))

/* convert cycles on a given CPU to/from mame_time */
#define MAME_TIME_TO_CYCLES(cpu,t)	((t).seconds * cycles_per_second[cpu] + (t).subseconds / subseconds_per_cycle[cpu])
#define MAME_TIME_IN_CYCLES(c,cpu)	(make_mame_time((c) / cycles_per_second[cpu], (c) * subseconds_per_cycle[cpu]))

/* useful macros for describing mame_times */
#define MAME_TIME_IN_HZ(hz)		make_mame_time(0, MAX_SUBSECONDS / (hz))
#define MAME_TIME_IN_SEC(s)		make_mame_time((s)  / 1, (s) % 1 /* mod here errors on doubles, which is intended */)
#define MAME_TIME_IN_MSEC(ms)	make_mame_time((ms) / 1000, ((ms) % 1000) * (MAX_SUBSECONDS / 1000))
#define MAME_TIME_IN_USEC(us)	make_mame_time((us) / 1000000, ((us) % 1000000) * (MAX_SUBSECONDS / 1000000))
#define MAME_TIME_IN_NSEC(ns)	make_mame_time((ns) / 1000000000, ((ns) % 1000000000) * (MAX_SUBSECONDS / 1000000000))

/* macro for the RC time constant on a 74LS123 with C > 1000pF */
/* R is in ohms, C is in farads */
#define TIME_OF_74LS123(r,c)			(0.45 * (double)(r) * (double)(c))

/* macros for the RC time constant on a 555 timer IC */
/* R is in ohms, C is in farads */
#define PERIOD_OF_555_MONOSTABLE(r,c)	MAME_TIME_IN_NSEC((subseconds_t)(1100000000 * (double)(r) * (double)(c)))
#define PERIOD_OF_555_ASTABLE(r1,r2,c)	MAME_TIME_IN_NSEC((subseconds_t)( 693000000 * ((double)(r1) + 2.0 * (double)(r2)) * (double)(c)))

/* macros that map all allocations to provide file/line/functions to the callee */
#define mame_timer_alloc(c)				_mame_timer_alloc(c, __FILE__, __LINE__, #c)
#define mame_timer_alloc_ptr(c,p)		_mame_timer_alloc_ptr(c, p, __FILE__, __LINE__, #c)
#define mame_timer_pulse(e,p,c)			_mame_timer_pulse(e, p, c, __FILE__, __LINE__, #c)
#define mame_timer_pulse_ptr(e,p,c)		_mame_timer_pulse_ptr(e, p, c, __FILE__, __LINE__, #c)
#define mame_timer_set(d,p,c)			_mame_timer_set(d, p, c, __FILE__, __LINE__, #c)
#define mame_timer_set_ptr(d,p,c)		_mame_timer_set_ptr(d, p, c, __FILE__, __LINE__, #c)

/* macros for a timer callback functions */
#define TIMER_CALLBACK(name)			void name(running_machine *machine, int param)
#define TIMER_CALLBACK_PTR(name)		void name(running_machine *machine, void *param)



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/* opaque type for representing a timer */
typedef struct _mame_timer mame_timer;

/* these core types describe a 96-bit time value */
typedef INT64 subseconds_t;
typedef INT32 seconds_t;

#ifndef __GNUC__	/* Fix compiler bug (ICC) */
#undef DOUBLE_TO_SUBSECONDS

INLINE subseconds_t DOUBLE_TO_SUBSECONDS(double x)
{
	x *= (double)MAX_SUBSECONDS;
	return (subseconds_t)x;
}
#endif

typedef struct _mame_time mame_time;
struct _mame_time
{
	seconds_t		seconds;
	subseconds_t	subseconds;
};



/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

/* constant times for zero and never */
extern mame_time time_zero;
extern mame_time time_never;

/* arrays containing mappings between CPU cycle times and timer values */
extern subseconds_t subseconds_per_cycle[];
extern UINT32 cycles_per_second[];



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

void timer_init(running_machine *machine);
void timer_destructor(void *ptr, size_t size);
int timer_count_anonymous(void);

mame_time mame_timer_next_fire_time(void);
void mame_timer_set_global_time(mame_time newbase);
mame_timer *_mame_timer_alloc(void (*callback)(running_machine *, int), const char *file, int line, const char *func);
mame_timer *_mame_timer_alloc_ptr(void (*callback)(running_machine *, void *), void *param, const char *file, int line, const char *func);
void mame_timer_adjust(mame_timer *which, mame_time duration, INT32 param, mame_time period);
void mame_timer_adjust_ptr(mame_timer *which, mame_time duration, mame_time period);
void _mame_timer_pulse(mame_time period, INT32 param, void (*callback)(running_machine *, int), const char *file, int line, const char *func);
void _mame_timer_pulse_ptr(mame_time period, void *param, void (*callback)(running_machine *, void *), const char *file, int line, const char *func);
void _mame_timer_set(mame_time duration, INT32 param, void (*callback)(running_machine *, int), const char *file, int line, const char *func);
void _mame_timer_set_ptr(mame_time duration, void *param, void (*callback)(running_machine *, void *), const char *file, int line, const char *func);
void mame_timer_reset(mame_timer *which, mame_time duration);
int mame_timer_enable(mame_timer *which, int enable);
int mame_timer_enabled(mame_timer *which);
int mame_timer_get_param(mame_timer *which);
void *mame_timer_get_param_ptr(mame_timer *which);
mame_time mame_timer_timeelapsed(mame_timer *which);
mame_time mame_timer_timeleft(mame_timer *which);
mame_time mame_timer_get_time(void);
mame_time mame_timer_starttime(mame_timer *which);
mame_time mame_timer_firetime(mame_timer *which);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    make_mame_time - assemble a mame_time from
    seconds and subseconds components
-------------------------------------------------*/

INLINE mame_time make_mame_time(seconds_t _secs, subseconds_t _subsecs)
{
	mame_time result;
	result.seconds = _secs;
	result.subseconds = _subsecs;
	return result;
}


/*-------------------------------------------------
    mame_time_to_subseconds - convert a mame_time
    to subseconds, clamping to maximum positive/
    negative values
-------------------------------------------------*/

INLINE subseconds_t mame_time_to_subseconds(mame_time _time)
{
	/* positive values between 0 and 1 second */
	if (_time.seconds == 0)
		return _time.subseconds;

	/* negative values between -1 and 0 seconds */
	else if (_time.seconds == -1)
		return _time.subseconds - MAX_SUBSECONDS;

	/* out-of-range positive values */
	else if (_time.seconds > 0)
		return MAX_SUBSECONDS;

	/* out-of-range negative values */
	else
		return -MAX_SUBSECONDS;
}


/*-------------------------------------------------
    mame_time_to_double - convert from mame_time
    to double
-------------------------------------------------*/

INLINE double mame_time_to_double(mame_time _time)
{
	return (double)_time.seconds + SUBSECONDS_TO_DOUBLE(_time.subseconds);
}


/*-------------------------------------------------
    double_to_mame_time - convert from double to
    mame_time
-------------------------------------------------*/

INLINE mame_time double_to_mame_time(double _time)
{
	mame_time abstime;

	/* set seconds to the integral part */
	abstime.seconds = floor(_time);

	/* set subseconds to the fractional part */
	_time -= (double)abstime.seconds;
	abstime.subseconds = DOUBLE_TO_SUBSECONDS(_time);
	return abstime;
}


/*-------------------------------------------------
    add_mame_times - add two mame_times
-------------------------------------------------*/

INLINE mame_time add_mame_times(mame_time _time1, mame_time _time2)
{
	mame_time result;

	/* if one of the items is time_never, return time_never */
	if (_time1.seconds >= MAX_SECONDS || _time2.seconds >= MAX_SECONDS)
		return time_never;

	/* add the seconds and subseconds */
	result.subseconds = _time1.subseconds + _time2.subseconds;
	result.seconds = _time1.seconds + _time2.seconds;

	/* normalize and return */
	if (result.subseconds >= MAX_SUBSECONDS)
	{
		result.subseconds -= MAX_SUBSECONDS;
		result.seconds++;
	}
	return result;
}


/*-------------------------------------------------
    add_subseconds_to_mame_time - add subseconds
    to a mame_time
-------------------------------------------------*/

INLINE mame_time add_subseconds_to_mame_time(mame_time _time1, subseconds_t _subseconds)
{
	mame_time result;

	/* if one of the items is time_never, return time_never */
	if (_time1.seconds >= MAX_SECONDS)
		return time_never;

	/* add the seconds and subseconds */
	result.subseconds = _time1.subseconds + _subseconds;
	result.seconds = _time1.seconds;

	/* normalize and return */
	if (result.subseconds >= MAX_SUBSECONDS)
	{
		result.subseconds -= MAX_SUBSECONDS;
		result.seconds++;
	}
	return result;
}


/*-------------------------------------------------
    sub_mame_times - subtract two mame_times
-------------------------------------------------*/

INLINE mame_time sub_mame_times(mame_time _time1, mame_time _time2)
{
	mame_time result;

	/* if time1 is time_never, return time_never */
	if (_time1.seconds >= MAX_SECONDS)
		return time_never;

	/* add the seconds and subseconds */
	result.subseconds = _time1.subseconds - _time2.subseconds;
	result.seconds = _time1.seconds - _time2.seconds;

	/* normalize and return */
	if (result.subseconds < 0)
	{
		result.subseconds += MAX_SUBSECONDS;
		result.seconds--;
	}
	return result;
}


/*-------------------------------------------------
    sub_subseconds_from_mame_time - subtract
    subseconds from a mame_time
-------------------------------------------------*/

INLINE mame_time sub_subseconds_from_mame_time(mame_time _time1, subseconds_t _subseconds)
{
	mame_time result;

	/* if time1 is time_never, return time_never */
	if (_time1.seconds >= MAX_SECONDS)
		return time_never;

	/* add the seconds and subseconds */
	result.subseconds = _time1.subseconds - _subseconds;
	result.seconds = _time1.seconds;

	/* normalize and return */
	if (result.subseconds < 0)
	{
		result.subseconds += MAX_SUBSECONDS;
		result.seconds--;
	}
	return result;
}


/*-------------------------------------------------
    scale_up_mame_time - multiply a mame_time by
    a constant
-------------------------------------------------*/

INLINE mame_time scale_up_mame_time(mame_time _time1, UINT32 factor)
{
	UINT32 subseclo, subsechi;
	UINT64 templo, temphi;
	mame_time result;

	/* if one of the items is time_never, return time_never */
	if (_time1.seconds >= MAX_SECONDS)
		return time_never;

	/* 0 times anything is zero */
	if (factor == 0)
		return time_zero;

	/* break subseconds into lower and upper halves */
	subseclo = (UINT32)(_time1.subseconds % MAX_SUBSECONDS_SQRT);
	subsechi = (UINT32)(_time1.subseconds / MAX_SUBSECONDS_SQRT);

	/* get a 64-bit product for each one */
	templo = (UINT64)subseclo * (UINT64)factor;
	temphi = (UINT64)subsechi * (UINT64)factor;

	/* separate out the low and carry into the high */
	subseclo = templo % MAX_SUBSECONDS_SQRT;
	temphi += templo / MAX_SUBSECONDS_SQRT;
	subsechi = temphi % MAX_SUBSECONDS_SQRT;

	/* assemble the two parts into final subseconds and carry into seconds */
	result.subseconds = (subseconds_t)subseclo + MAX_SUBSECONDS_SQRT * (subseconds_t)subsechi;
	result.seconds = _time1.seconds * factor + temphi / MAX_SUBSECONDS_SQRT;

	/* max out at never time */
	if (result.seconds >= MAX_SECONDS)
		return time_never;

	return result;
}


/*-------------------------------------------------
    scale_down_mame_time - divide a mame_time by
    a constant
-------------------------------------------------*/

INLINE mame_time scale_down_mame_time(mame_time _time1, UINT32 factor)
{
	mame_time result;
	int shift_count;
	int carry;

	/* if one of the items is time_never, return time_never */
	if (_time1.seconds >= MAX_SECONDS)
		return time_never;

	result.seconds = _time1.seconds / factor;
	carry = _time1.seconds - (result.seconds * factor);

	/* shift the subseconds to the right by log2 of the factor, so it won't overflow */
	shift_count = (log(carry) / log(2)) + 1;
	result.subseconds = (((carry * (MAX_SUBSECONDS >> shift_count)) + (_time1.subseconds >> shift_count)) / factor) << shift_count;

	return result;
}


/*-------------------------------------------------
    compare_mame_times - compare two mame_times
-------------------------------------------------*/

INLINE int compare_mame_times(mame_time _time1, mame_time _time2)
{
	if (_time1.seconds > _time2.seconds)
		return 1;
	if (_time1.seconds < _time2.seconds)
		return -1;
	if (_time1.subseconds > _time2.subseconds)
		return 1;
	if (_time1.subseconds < _time2.subseconds)
		return -1;
	return 0;
}


/*-------------------------------------------------
    timer_call_after_resynch - synchs the CPUs
    and calls the callback immediately
-------------------------------------------------*/

INLINE void timer_call_after_resynch(INT32 param, void (*callback)(running_machine *, int))
{
	mame_timer_set(time_zero, param, callback);
}


INLINE void timer_call_after_resynch_ptr(void *param, void (*callback)(running_machine *, void *))
{
	mame_timer_set_ptr(time_zero, param, callback);
}


#endif	/* __TIMER_H__ */
