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

	timer.c

	タイマー管理

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

#include "cps2.h"


#define CPU_NOTACTIVE	-1


/******************************************************************************
	マクロ
******************************************************************************/

/*------------------------------------------------------
	CPUの消費した時間を取得 (単位:マイクロ秒)
------------------------------------------------------*/

#define cpu_elapsed_time(cpunum)	\
	(float)(cpu[cpunum].cycles - *cpu[cpunum].icount) * cpu[cpunum].cycles_to_usec


/******************************************************************************
	ローカル構造体
******************************************************************************/

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

typedef struct cpuinfo_t
{
	int (*execute)(int cycles);
	int *icount;
	float usec_to_cycles;
	float cycles_to_usec;
	int cycles;
	int suspended;
} CPUINFO;


static TIMER timer[MAX_TIMER];

static CPUINFO cpu[MAX_CPU] =
{
	{
		m68000_execute,			// execute
		&m68k_ICount,			// icount
		11800000.0/1000000.0,	// usec_to_cycles
		1000000.0/11800000.0,	// cycles_to_usec
		0,						// cycles
		0						// suspended
	},
	{
		z80_execute,			// execute
		&z80_ICount,			// icount
		8000000.0/1000000.0,	// usec_to_cycles
		1000000.0/8000000.0,	// cycles_to_usec
		0,						// cycles
		0						// suspended
	}
};


/******************************************************************************
	ローカル変数
******************************************************************************/

static const float time_slice = (int)(1000000.0 / FPS);

static float global_offset;
static float base_time;
static float frame_base;
static float timer_ticks;
static float timer_left;
static int active_cpu;
static u32 current_frame;


/******************************************************************************
	ローカル関数
******************************************************************************/

/*------------------------------------------------------
	CPUを実行
------------------------------------------------------*/

static void cpu_execute(int cpunum)
{
	if (!cpu[cpunum].suspended)
	{
		active_cpu = cpunum;
		cpu[cpunum].cycles = (int)(timer_ticks * cpu[cpunum].usec_to_cycles);
		cpu[cpunum].execute(cpu[cpunum].cycles);
		active_cpu = CPU_NOTACTIVE;
	}
}


/*------------------------------------------------------
	CPUのスピンを解除(トリガ)
------------------------------------------------------*/

static void cpu_spin_trigger(int param)
{
	timer_suspend_cpu(param, 1, SUSPEND_REASON_SPIN);
}


/*------------------------------------------------------
	現在の秒以下の時間を取得 (単位:マイクロ秒)
------------------------------------------------------*/

static float getabsolutetime(void)
{
	float time = base_time + frame_base;

	if (active_cpu != CPU_NOTACTIVE)
		time += cpu_elapsed_time(active_cpu);

	return time;
}


/*------------------------------------------------------
	描画割り込み
------------------------------------------------------*/

static void timer_set_raster_interrupt(int which, int scanline)
{
	int param = (which << 16) | scanline;

	timer_set(which, USECS_PER_SCANLINE * scanline, param, cps2_raster_interrupt);
}


static void timer_set_vblank_interrupt(void)
{
	timer_set(VBLANK_INTERRUPT, USECS_PER_SCANLINE * 256, 0, cps2_vblank_interrupt);
}


/*------------------------------------------------------
	サウンド割り込み
------------------------------------------------------*/

static TIMER_CALLBACK( qsound_interrupt )
{
	z80_set_irq_line(0, HOLD_LINE);
	timer_set(QSOUND_INTERRUPT, TIME_IN_HZ(251), 0, qsound_interrupt);
}


/******************************************************************************
	グローバル関数
******************************************************************************/

/*------------------------------------------------------
	タイマーをリセット
------------------------------------------------------*/

void timer_reset(void)
{
	int cpunum;

	global_offset = 0;
	base_time = 0;
	frame_base = 0;
	current_frame = 0;

	active_cpu = CPU_NOTACTIVE;
	memset(&timer, 0, sizeof(timer));

	for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
	{
		cpu[cpunum].cycles = 0;
		cpu[cpunum].suspended = 0;
	}

	timer_set(QSOUND_INTERRUPT, TIME_IN_HZ(251), 0, qsound_interrupt);
}


/*------------------------------------------------------
	CPUをサスペンドする
------------------------------------------------------*/

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


/*------------------------------------------------------
	タイマーを有効/無効にする
------------------------------------------------------*/

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

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


/*------------------------------------------------------
	タイマーをセット
------------------------------------------------------*/

void timer_adjust(int which, float duration, int param, void (*callback)(int param))
{
	float time = getabsolutetime();

	timer[which].expire = time + duration;
	timer[which].param = param;
	timer[which].callback = callback;

	if (active_cpu != CPU_NOTACTIVE)
	{
		// CPU実行中の場合は、残りサイクルを破棄
		int cycles_left = *cpu[active_cpu].icount;
		float time_left = cycles_left * cpu[active_cpu].cycles_to_usec;

		if (duration < timer_left)
		{
			timer_ticks -= time_left;
			cpu[active_cpu].cycles -= cycles_left;
			*cpu[active_cpu].icount = 0;

			if (active_cpu == CPU_Z80)
			{
				// CPU2の場合はCPU1を停止しCPU1が消費した余分なサイクルを調整する
				if (!timer[CPUSPIN_TIMER].enable)
				{
					timer_suspend_cpu(CPU_M68000, 0, SUSPEND_REASON_SPIN);
					timer[CPUSPIN_TIMER].enable = 1;
					timer[CPUSPIN_TIMER].expire = time + time_left;
					timer[CPUSPIN_TIMER].param = CPU_M68000;
					timer[CPUSPIN_TIMER].callback = cpu_spin_trigger;
				}
			}
		}
	}
}


/*------------------------------------------------------
	タイマーをセット
------------------------------------------------------*/

void timer_set(int which, float duration, int param, void (*callback)(int param))
{
	timer[which].enable = 1;
	timer_adjust(which, duration, param, callback);
}


/*------------------------------------------------------
	現在のエミュレーション時間を取得 (単位:秒)
------------------------------------------------------*/

float timer_get_time(void)
{
	return global_offset + (getabsolutetime() / 1000000.0);
}


/*------------------------------------------------------
	現在のフレームを取得
------------------------------------------------------*/

u32 timer_getcurrentframe(void)
{
	return current_frame;
}


/*------------------------------------------------------
	CPUを更新
------------------------------------------------------*/

void timer_update_cpu(void)
{
	int i;
	float time;
	extern int scanline1;
	extern int scanline2;

	frame_base = 0;
	timer_left = time_slice;

	if (scanline1 != RASTER_LINES) timer_set_raster_interrupt(RASTER_INTERRUPT1, scanline1);
	if (scanline2 != RASTER_LINES) timer_set_raster_interrupt(RASTER_INTERRUPT2, scanline2);
	timer_set_vblank_interrupt();

	if (cps2_build_palette) (*cps2_build_palette)();

	while (timer_left > 0)
	{
		timer_ticks = timer_left;
		time = base_time + frame_base;

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

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

		frame_base += timer_ticks;
		timer_left -= timer_ticks;
	}

	base_time += time_slice;
	if (base_time >= 1000000.0)
	{
		global_offset += 1.0;
		base_time -= 1000000.0;

		for (i = 0; i < MAX_TIMER; i++)
		{
			if (timer[i].enable)
				timer[i].expire -= 1000000.0;
		}
	}

	current_frame++;
}


/******************************************************************************
	セーブ/ロード ステート
******************************************************************************/

#ifdef SAVE_STATE

STATE_SAVE( timer )
{
	int i;

	state_save_float(&global_offset, 1);
	state_save_float(&base_time, 1);
	state_save_long(&current_frame, 1);

	state_save_long(&cpu[0].suspended, 1);
	state_save_long(&cpu[1].suspended, 1);

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

STATE_LOAD( timer )
{
	int i;

	state_load_float(&global_offset, 1);
	state_load_float(&base_time, 1);
	state_load_long(&current_frame, 1);

	state_load_long(&cpu[0].suspended, 1);
	state_load_long(&cpu[1].suspended, 1);

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

	timer_left  = 0;
	timer_ticks = 0;
	frame_base  = 0;
	active_cpu = CPU_NOTACTIVE;

	timer[QSOUND_INTERRUPT].callback  = qsound_interrupt;
	timer[CPUSPIN_TIMER].callback     = cpu_spin_trigger;
	timer[VBLANK_INTERRUPT].callback  = cps2_vblank_interrupt;
	timer[RASTER_INTERRUPT1].callback = cps2_raster_interrupt;
	timer[RASTER_INTERRUPT2].callback = cps2_raster_interrupt;
}

#endif /* SAVE_STATE */
