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

	timer.c

	Sucedule CPU and sound timer

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

#include "ncdz.h"


#define YM2610_TIMERA		0
#define YM2610_TIMERB		1
#define SCANLINE_TIMER		2
#define MAX_TIMER			3


/***************************************************************************
	Helper macros
 **************************************************************************/

/*------------------------------------------------------
	Get elapsed time
 -----------------------------------------------------*/

#define cpu_elapsed_time(cpunum)	((double)(cpu[cpunum].cycles - *cpu[cpunum].icount) * cpu[cpunum].cycle_to_sec)


/*------------------------------------------------------
	Execute CPU
 -----------------------------------------------------*/

#define cpu_execute(cpunum)														\
{																				\
	if (!cpu[cpunum].suspend)													\
	{																			\
		active_cpu = cpunum;													\
		cpu[cpunum].cycles = (int)(timer_ticks * cpu[cpunum].sec_to_cycle);		\
		cpu[cpunum].execute(cpu[cpunum].cycles);								\
		active_cpu = CPU_NOTACTIVE;												\
	}																			\
}


/***************************************************************************
	Private structure
 **************************************************************************/

typedef struct timer_t
{
	double expire;
	int enable;
	int param;
	void (*callback)(int param);
} TIMER;

typedef struct cpuinfo_t
{
	int type;
	int clock;
	int (*execute)(int cycles);
	int *icount;
	double cycle_to_sec;
	double sec_to_cycle;
	int cycles;
	int suspend;
} CPUINFO;


static TIMER timer[MAX_TIMER];

static CPUINFO cpu[MAX_CPU] =
{
	{
		CPU_M68000,					// type
		M68K_CLOCKS,				// clock
		m68000_execute,				// execute
		&m68k_ICount,				// icount
		TIME_IN_HZ(M68K_CLOCKS),	// cycle_to_sec
		(double)M68K_CLOCKS,		// sec_to_cycle
		0,							// cycles
		0							// suspend
	},
	{
		CPU_Z80,					// type
		Z80_CLOCKS,					// clock
		z80_execute,				// execute
		&z80_ICount,				// icount
		TIME_IN_HZ(Z80_CLOCKS),		// cycle_to_sec
		(double)Z80_CLOCKS,			// sec_to_cycle
		0,							// cycles
		0							// suspend
	}
};


/***************************************************************************
	Private variables
 **************************************************************************/

static double global_offset;
static double base_time;
static double timer_ticks;
static double timer_left;
static int active_cpu;


/*------------------------------------------------------
	Initialize sound timer
 -----------------------------------------------------*/

void timer_init(void)
{
	global_offset = 0.0;
	timer_reset();
}


/*------------------------------------------------------
	Reset sound timer
 -----------------------------------------------------*/

void timer_reset(void)
{
	base_time = 0.0;
	active_cpu = CPU_NOTACTIVE;
	memset(&timer, 0, sizeof(timer));

	timer_suspend_cpu(CPU_M68000, 1, SUSPEND_REASON_NOTSUSPEND);
	if (neogeo_sound_enable)
		timer_suspend_cpu(CPU_Z80, 1, SUSPEND_REASON_NOTSUSPEND);
	else
		timer_suspend_cpu(CPU_Z80, 0, SUSPEND_REASON_DISABLE);
}


/*------------------------------------------------------
	Enable / Disable timer
 -----------------------------------------------------*/

int timer_enable(int which, int enable)
{
	int old = timer[which].enable;

	timer[which].enable = enable;
	return old;
}


/*------------------------------------------------------
	Set sound timer
 -----------------------------------------------------*/

void timer_set(int which, double duration, int param, void (*callback)(int param))
{
	timer[which].expire   = timer_get_time() + duration;
	timer[which].param    = param;
	timer[which].callback = callback;
}


/*------------------------------------------------------
	Adjust timer (scanline timer)
 -----------------------------------------------------*/

void timer_adjust(double duration, int param, void (*callback)(int param))
{
	timer[SCANLINE_TIMER].expire   = timer_get_time() + duration;
	timer[SCANLINE_TIMER].enable   = 1;
	timer[SCANLINE_TIMER].param    = param;
	timer[SCANLINE_TIMER].callback = callback;
}


/*------------------------------------------------------
	Get absolute time in current frame
 -----------------------------------------------------*/

INLINE double getabsolutetime(void)
{
	if (active_cpu != CPU_NOTACTIVE)
	{
		if (*cpu[active_cpu].icount > 0)
			return base_time + cpu_elapsed_time(active_cpu);
		else
			return base_time + timer_ticks;
	}
	return base_time;
}


/*------------------------------------------------------
	Get current time
 -----------------------------------------------------*/

double timer_get_time(void)
{
	return global_offset + getabsolutetime();
}


/*------------------------------------------------------
	Get elapsed time in this frame
 -----------------------------------------------------*/

double timer_timeelapsed(void)
{
	return getabsolutetime();
}


/*------------------------------------------------------
	Suspend CPU
 -----------------------------------------------------*/

void timer_suspend_cpu(int cpunum, int state, int reason)
{
	if (state == 0)
		cpu[cpunum].suspend |= reason;
	else
		cpu[cpunum].suspend &= ~reason;
}


/*------------------------------------------------------
	Scedule and execute CPU
 -----------------------------------------------------*/

void timer_scedule_cpu(void)
{
	int i, cpunum;

	base_time = 0.0;
	timer_left = TIME_IN_HZ(FPS);
	timer_ticks = 0.0;

#ifdef EMULATE_RASTER_EFFECT
	if (driver_type == RASTER)
		timer_adjust(SECS_PER_SCANLINE, 1, neogeo_raster_interrupt);
#endif

	while (timer_left > 0.0)
	{
		timer_ticks = timer_left;

		for (i = 0; i < MAX_TIMER; i++)
		{
			if (timer[i].enable)
			{
				if (global_offset + base_time >= timer[i].expire)
				{
					timer[i].enable = 0;
					timer[i].callback(timer[i].param);
				}
				if (timer[i].enable)
				{
					double time = timer[i].expire - (global_offset + base_time);
					if (time < timer_ticks) timer_ticks = time;
				}
			}
		}

		for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
			cpu_execute(cpunum);

		base_time += timer_ticks;
		timer_left -= timer_ticks;
	}

	global_offset += TIME_IN_HZ(FPS);
	base_time = 0.0;

	if (driver_type == AOF2)
		neogeo_interrupt_aof2(RASTER_LINES);
#ifdef ENABLE_RASTER_EFFECT
	else if (driver_type == RASTER)
		neogeo_raster_interrupt(RASTER_LINES);
#endif
	else
		neogeo_interrupt(RASTER_LINES);
}


#ifdef SAVE_STATE
/*------------------------------------------------------
	Save timer state
 -----------------------------------------------------*/

void state_save_timer(u8 *state_buf)
{
	u8 *in = state_buf;
	int i;

	for (i = 0; i < MAX_TIMER; i++)
	{
		state_save_double(&timer[i].expire, 1);
		state_save_long(&timer[i].enable, 1);
		state_save_long(&timer[i].param, 1);
	}

	state_save_double(&global_offset, 1);
}


/*------------------------------------------------------
	Load timer state
 -----------------------------------------------------*/

void state_load_timer(u8 *state_buf)
{
	u8 *out = state_buf;
	int i;

	for (i = 0; i < MAX_TIMER; i++)
	{
		state_load_double(&timer[i].expire, 1);
		state_load_long(&timer[i].enable, 1);
		state_load_long(&timer[i].param, 1);
	}

	state_load_double(&global_offset, 1);

	timer_left  = 0.0;
	timer_ticks = 0.0;
	base_time   = 0.0;
	active_cpu = CPU_NOTACTIVE;

	if (timer[SCANLINE_TIMER].enable)
	{
		if (NGH_NUMBER(0x0056))
			timer[SCANLINE_TIMER].callback = neogeo_interrupt_aof2;
#ifdef EMULATE_RASTER_EFFECT
		else
			timer[SCANLINE_TIMER].callback = neogeo_raster_interrupt;
#endif
	}
	timer[YM2610_TIMERA].callback = timer_callback_2610;
	timer[YM2610_TIMERB].callback = timer_callback_2610;
}
#endif
