/*
 *   timer.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 1995 Sebastien Carlier
 *
 */
#include "pch.h"
#include "Emu48.h"

BOOL bAccurateTimer = TRUE;
UINT uT1Period  = 62;

static BOOL  bStarted   = FALSE;
static UINT  uT1TimerId = 0;
static UINT  uT2TimerId = 0;
static DWORD dwT1Ticks = 0;
static DWORD dwT2Init  = 0;
static DWORD dwT2Step  = 0;
static DWORD dwT2Ticks = 0;

static __inline MAX(int a, int b) {return (a>b)?a:b;} 

static void CALLBACK TimeProc(UINT uEventId, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);

static VOID CheckT1()
{
	_ASSERT(bStarted);
	if ((Chipset.t1&8) == 0)
	{
		Chipset.IORam[0x2E] &= 0x7; // clear bit 3
		return;
	}
	if (Chipset.IORam[0x2E]&4)
	{ // T1 -> Wake Up
		if (Chipset.Shutdn)
		{
			ResumeThread(hThread);
			Chipset.IORam[0x2E] &= 0xB; // clear bit 2
			Chipset.IORam[0x2E] |= 0x8; // set bit 3
		}
	}
	if (Chipset.IORam[0x2E]&2)
	{ // T1 -> Interrupt
		Chipset.IORam[0x2E] |= 0x8; // set bit 3
		Chipset.SoftInt = TRUE;
		bInterrupt=TRUE;
	}
	return;
}

static VOID CheckT2()
{
	_ASSERT(bStarted);
	if ((Chipset.t2&0x80000000) == 0)
	{
		Chipset.IORam[0x2F] &= 7; // clear bit 3
		return;
	}
	if (Chipset.IORam[0x2F]&4)
	{ // T2 -> Wake Up
		if (Chipset.Shutdn)
		{
			ResumeThread(hThread);
			Chipset.IORam[0x2F] &= 0xB; // clear bit 2
			Chipset.IORam[0x2F] |= 0x8; // set bit 3
		}
	}
	if (Chipset.IORam[0x2F]&2)
	{ // T2 -> Interrupt
		Chipset.IORam[0x2F] |= 0x8; // set bit 3
		Chipset.SoftInt = TRUE;
		bInterrupt=TRUE;
	}
	return;
}

static VOID RescheduleT2()
{
	_ASSERT(uT2TimerId == 0);
	dwT2Init   = timeGetTime();
	dwT2Ticks  = dwT2Init;
	uT2TimerId = timeSetEvent(MAX(16,Chipset.t2>>3),0,(LPTIMECALLBACK)&TimeProc,2,TIME_ONESHOT);
	return;
}

static VOID AbortT2()
{
	_ASSERT(uT2TimerId != 0);
	uT2TimerId = 0;
	timeKillEvent(uT2TimerId);
	return;
}

static void CALLBACK TimeProc(UINT uEventId, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
	if (!bStarted)
		return;
	if ((uT1TimerId!=0) && (uEventId == uT1TimerId))
	{
		Chipset.t1 = (Chipset.t1-1)&0xF;
		CheckT1();
		return;
	}
	if ((uT2TimerId!=0) && (uEventId == uT2TimerId))
	{
		uT2TimerId = 0;
		Chipset.t2 = 0xFFFFFFFF;
		CheckT2();
		RescheduleT2();
		return;
	}
	return;
	UNREFERENCED_PARAMETER(uMsg);
}


VOID StartTimers()
{
	if (bStarted)
		return;
	if (Chipset.IORam[0x2F]&1)
	{
		bStarted = TRUE;
		if (bAccurateTimer)
		{
			MMRESULT uResult;
			uResult = timeBeginPeriod(1);
			if (uResult!=TIMERR_NOERROR)
				bAccurateTimer=FALSE;
		}
		uT1TimerId = timeSetEvent(uT1Period,0,(LPTIMECALLBACK)&TimeProc,1,TIME_PERIODIC);
		RescheduleT2();
	}
	return;
}

VOID StopTimers()
{
	if (!bStarted)
		return;
	Chipset.t2 = ReadT2();
	if (uT1TimerId != 0)
	{
		timeKillEvent(uT1TimerId);
		uT1TimerId = 0;
	}
	if (uT2TimerId != 0)
		AbortT2();
	bStarted = FALSE;
	if (bAccurateTimer)
	{
		timeEndPeriod(1);
	}
	return;
}

DWORD ReadT2()
{
	DWORD dwT2;
	if (!bStarted)
	{
		dwT2 = Chipset.t2;
	}
	else
	{
		DWORD dwNow = timeGetTime();
		if (dwNow == dwT2Ticks)
		{
			if (dwT2Step < 7)
				dwT2Step++;
			return Chipset.t2-(8*(dwNow-dwT2Init)+dwT2Step);
		}
		dwT2Step = 0;
		dwT2Ticks = dwNow;
		dwT2 = Chipset.t2-8*(dwNow-dwT2Init);
	}
	return dwT2;
}

VOID SetT2(DWORD dwValue)
{
	if (!bStarted)
	{
		Chipset.t2 = dwValue;
		return;
	}
	if (uT2TimerId != 0)
		AbortT2();
	Chipset.t2 = dwValue;
	CheckT2();
	RescheduleT2();
	return;
}

BYTE ReadT1()
{
	return Chipset.t1;
}

VOID SetT1(BYTE byValue)
{
	Chipset.t1 = byValue&0xF;
	CheckT1();
	return;
}

