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

	timer.c

	タイマー管理

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

#include "cps1.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
		10000000.0/1000000.0,	// usec_to_cycles
		1000000.0/10000000.0,	// cycles_to_usec
		0,						// cycles
		0						// suspended
	},
	{
		z80_execute,			// execute
		&z80_ICount,			// icount
		3579545.0/1000000.0,	// usec_to_cycles
		1000000.0/3579545.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_vblank_interrupt(void)
{
	timer_set(VBLANK_INTERRUPT, USECS_PER_SCANLINE * 256, 0, cps1_vblank_interrupt);
}


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

static TIMER_CALLBACK( qsound_interrupt )
{
	z80_set_irq_line(0, HOLD_LINE);
	timer_set(QSOUND_INTERRUPT, TIME_IN_HZ(250), 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;
	}

	switch (machine_driver_type)
	{
	case MACHINE_qsound:
		cpu[CPU_Z80].usec_to_cycles = 8000000.0/1000000.0;
		cpu[CPU_Z80].cycles_to_usec = 1000000.0/8000000.0;
		timer_set(QSOUND_INTERRUPT, TIME_IN_HZ(250), 0, qsound_interrupt);
		break;

	default:
		cpu[CPU_Z80].usec_to_cycles = 3579646.0/1000000.0;
		cpu[CPU_Z80].cycles_to_usec = 1000000.0/3579545.0;
		break;
	}
}


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

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


/*------------------------------------------------------
	CPUをリセットする
------------------------------------------------------*/

void timer_set_resetline(int cpunum, int state)
{
	if (state == ASSERT_LINE)
		cpu[cpunum].suspended |= SUSPEND_REASON_RESET;
	else
		cpu[cpunum].suspended &= ~SUSPEND_REASON_RESET;
}


/*------------------------------------------------------
	CPUのサスペンドの状態を取得
------------------------------------------------------*/

int timer_get_cpu_status(int cpunum)
{
	return cpu[cpunum].suspended;
}


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

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[CPU1_SPIN_TIMER].enable)
				{
					timer_suspend_cpu(CPU_M68000, 0, SUSPEND_REASON_SPIN);
					timer[CPU1_SPIN_TIMER].enable = 1;
					timer[CPU1_SPIN_TIMER].expire = time + time_left;
					timer[CPU1_SPIN_TIMER].param = CPU_M68000;
					timer[CPU1_SPIN_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;

	frame_base = 0;
	timer_left = time_slice;

	timer_set_vblank_interrupt();

	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++;
}


/******************************************************************************
	セーブ/ロード ステート
******************************************************************************/
int get_timer_size(void){
	int i;
	int tsize = 0;

	tsize += 4;//state_save_float(&global_offset, 1);
	tsize += 4;//state_save_float(&base_time, 1);
	tsize += 4;//state_save_long(&current_frame, 1);

	tsize += 4;//state_save_long(&cpu[0].suspended, 1);
	tsize += 4;//state_save_long(&cpu[1].suspended, 1);

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

int save_timer_to_mem(unsigned char *stor){
	unsigned char *pos_ini = stor;
	int i;

	//state_save_float(&global_offset, 1);
	memcpy(stor, &global_offset, 4);
	stor += 4;
	
	
	//state_save_float(&base_time, 1);
	memcpy(stor, &base_time, 4);
	stor += 4;
	
	
	//state_save_long(&current_frame, 1);
	memcpy(stor, &current_frame, 4);
	stor += 4;
	
	
	//state_save_long(&cpu[0].suspended, 1);
	memcpy(stor, &cpu[0].suspended, 4);
	stor += 4;
	
	
	//state_save_long(&cpu[1].suspended, 1);
	memcpy(stor, &cpu[1].suspended, 4);
	stor += 4;
	
	

	for (i = 0; i < MAX_TIMER; i++)
	{
		//state_save_float(&timer[i].expire, 1);
		memcpy(stor, &timer[i].expire, 4);
		stor += 4;
		
		//state_save_long(&timer[i].enable, 1);
		memcpy(stor, &timer[i].enable, 4);
		stor += 4;
		
		
		//state_save_long(&timer[i].param, 1);
		memcpy(stor, &timer[i].param, 4);
		stor += 4;
	}
	
	return (stor - pos_ini);
}

#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;

	if (machine_sound_type == SOUND_QSOUND)
	{
		timer[QSOUND_INTERRUPT].callback = qsound_interrupt;
	}
	else
	{
		timer[YM2151_TIMERA].callback = timer_callback_2151_a;
		timer[YM2151_TIMERB].callback = timer_callback_2151_b;
	}
	timer[CPU1_SPIN_TIMER].callback  = cpu_spin_trigger;
	timer[CPU2_SPIN_TIMER].callback  = cpu_spin_trigger;
	timer[VBLANK_INTERRUPT].callback = cps1_vblank_interrupt;
}


int load_timer_from_mem(unsigned char *stor){
	unsigned char *pos_ini = stor;
	int i;

	//state_load_float(&global_offset, 1);
	memcpy(&global_offset, stor, 4);
	stor += 4;
	
	
	//state_load_float(&base_time, 1);
	memcpy(&base_time, stor, 4);
	stor += 4;
	
	
	//state_load_long(&current_frame, 1);
	memcpy(&current_frame, stor, 4);
	stor += 4;
	
	
	//state_load_long(&cpu[0].suspended, 1);
	memcpy(&cpu[0].suspended,stor, 4);
	stor += 4;
	
	
	//state_load_long(&cpu[1].suspended, 1);
	memcpy(&cpu[1].suspended, stor, 4);
	stor += 4;
	
	

	for (i = 0; i < MAX_TIMER; i++)
	{
		//state_load_float(&timer[i].expire, 1);
		memcpy(&timer[i].expire, stor, 4);
		stor += 4;
		
		//state_load_long(&timer[i].enable, 1);
		memcpy(&timer[i].enable, stor, 4);
		stor += 4;
		
		
		//state_load_long(&timer[i].param, 1);
		memcpy(&timer[i].param, stor, 4);
		stor += 4;
	}
	
	timer_left  = 0;
	timer_ticks = 0;
	frame_base  = 0;
	//active_cpu = CPU_NOTACTIVE;

	if (machine_sound_type == SOUND_QSOUND)
	{
		timer[QSOUND_INTERRUPT].callback = qsound_interrupt;
	}
	else
	{
		timer[YM2151_TIMERA].callback = timer_callback_2151_a;
		timer[YM2151_TIMERB].callback = timer_callback_2151_b;
	}
	timer[CPU1_SPIN_TIMER].callback  = cpu_spin_trigger;
	timer[CPU2_SPIN_TIMER].callback  = cpu_spin_trigger;
	timer[VBLANK_INTERRUPT].callback = cps1_vblank_interrupt;
	
	
	return (stor - pos_ini);
}



#endif /* SAVE_STATE */
