/*
 *   timer.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 1995 Sebastien Carlier
 *
 */
#include "pch.h"
#include "Emu48.h"
#include "ops.h"
#include "io.h"								// I/O definitions

#define AUTO_OFF	10						// Time in minutes for 'auto off'

// Ticks for 'auto off'
#define OFF_TIME	((ULONGLONG) (AUTO_OFF * 60) << 13)

// memory address for clock and auto off
// S(X) = 0x70052-0x70070, G(X) = 0x80058-0x80076, 49G = 0x80058-0x80076
#define RPLTIME		((cCurrentRomType=='S')?0x52:0x58)

#define T1_FREQ		62						// Timer1 1/frequency in ms
#define T2_FREQ		8192					// Timer2 frequency

static BOOL  bStarted   = FALSE;
static BOOL  bOutRange  = FALSE;			// flag if timer value out of range
static UINT  uT1TimerId = 0;
static UINT  uT2TimerId = 0;

static BOOL  bNINT2T1 = FALSE;				// state of NINT2 affected from timer1
static BOOL  bNINT2T2 = FALSE;				// state of NINT2 affected from timer2

static BOOL bAccurateTimer;					// flag if accurate timer is used
static LARGE_INTEGER lT2Ref;				// counter value at timer2 start
static TIMECAPS tc;							// timer information
static UINT uT2MaxTicks;					// max. timer2 ticks handled by one timer event

static DWORD dwT2Ref;						// timer2 value at last timer2 access
static DWORD dwT2Cyc;						// cpu cycle counter at last timer2 access

static void CALLBACK TimeProc(UINT uEventId, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2);

static DWORD CalcT2(VOID)					// calculate timer2 value
{
	DWORD dwT2 = Chipset.t2;				// get value from chipset
	if (bStarted)							// timer2 running
	{
		LARGE_INTEGER lT2Act;
		DWORD         dwT2Dif;

		// timer should run a little bit faster (10%) than maschine in authentic speed mode
		DWORD dwCycPerTick = (9 * dwT2Cycles) / 5;

		QueryPerformanceCounter(&lT2Act);	// actual time
		// calculate realtime timer2 ticks since reference point
		dwT2 -= (DWORD)
				(((lT2Act.QuadPart - lT2Ref.QuadPart) * T2_FREQ)
				/ lFreq.QuadPart);

		dwT2Dif = dwT2Ref - dwT2;			// timer2 ticks since last request

		// checking if the MSB of dwT2Dif can be used as sign flag
		_ASSERT((DWORD) tc.wPeriodMax < ((1<<(sizeof(dwT2Dif)*8-1))/8192)*1000);

		// 2nd timer call in a 32ms time frame or elapsed time is negative (Win2k bug)
		if (!Chipset.Shutdn && ((dwT2Dif > 0x01 && dwT2Dif <= 0x100) || (dwT2Dif & 0x80000000) != 0))
		{
			DWORD dwT2Ticks = ((DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwT2Cyc) / dwCycPerTick;

			// estimated < real elapsed timer2 ticks or negative time
			if (dwT2Ticks < dwT2Dif || (dwT2Dif & 0x80000000) != 0)
			{
				// real time too long or got negative time elapsed
				dwT2 = dwT2Ref - dwT2Ticks;	// estimated timer2 value from CPU cycles
				dwT2Cyc += dwT2Ticks * dwCycPerTick; // estimated CPU cycles for the timer2 ticks
			}
			else
			{
				// reached actual time -> new synchronizing
				dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwCycPerTick;
			}
		}
		else
		{
			// valid actual time -> new synchronizing
			dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwCycPerTick;
		}

		// check if timer2 interrupt is active -> no timer2 value below 0xFFFFFFFF
		if (   Chipset.inte
			&& (dwT2 & 0x80000000) != 0
			&& (!Chipset.Shutdn || (Chipset.IORam[TIMER2_CTRL]&WKE))
			&& (Chipset.IORam[TIMER2_CTRL]&INTR)
		   )
		{
			dwT2 = 0xFFFFFFFF;
			dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwCycPerTick;
		}

		dwT2Ref = dwT2;						// new reference time
	}
	return dwT2;
}

static VOID CheckT1(BYTE nT1)
{
	// implementation of TSRQ
	bNINT2T1 = (Chipset.IORam[TIMER1_CTRL]&INTR) != 0 && (nT1&8) != 0;
	IOBit(SRQ1,TSRQ,bNINT2T1 || bNINT2T2);

	if ((nT1&8) == 0)						// timer1 MSB not set
	{
		Chipset.IORam[TIMER1_CTRL] &= ~SRQ;	// clear SRQ bit
		return;
	}

	_ASSERT((nT1&8) != 0);					// timer1 MSB set

	// timer MSB and INT or WAKE bit is set
	if ((Chipset.IORam[TIMER1_CTRL]&(WKE|INTR)) != 0)
		Chipset.IORam[TIMER1_CTRL] |= SRQ;	// set SRQ
	// cpu not sleeping and T1 -> Interrupt
	if (   (!Chipset.Shutdn || (Chipset.IORam[TIMER1_CTRL]&WKE))
		&& (Chipset.IORam[TIMER1_CTRL]&INTR))
	{
		Chipset.SoftInt = TRUE;
		bInterrupt = TRUE;
	}
	// cpu sleeping and T1 -> Wake Up
	if (Chipset.Shutdn && (Chipset.IORam[TIMER1_CTRL]&WKE))
	{
		Chipset.IORam[TIMER1_CTRL] &= ~WKE;	// clear WKE bit
		Chipset.bShutdnWake = TRUE;			// wake up from SHUTDN mode
		SetEvent(hEventShutdn);				// wake up emulation thread
	}
	return;
}

static VOID CheckT2(DWORD dwT2)
{
	// implementation of TSRQ
	bNINT2T2 = (Chipset.IORam[TIMER2_CTRL]&INTR) != 0 && (dwT2&0x80000000) != 0;
	IOBit(SRQ1,TSRQ,bNINT2T1 || bNINT2T2);

	if ((dwT2&0x80000000) == 0)				// timer2 MSB not set
	{
		Chipset.IORam[TIMER2_CTRL] &= ~SRQ;	// clear SRQ bit
		return;
	}

	_ASSERT((dwT2&0x80000000) != 0);		// timer2 MSB set

	// timer MSB and INT or WAKE bit is set
	if ((Chipset.IORam[TIMER2_CTRL]&(WKE|INTR)) != 0)
		Chipset.IORam[TIMER2_CTRL] |= SRQ;	// set SRQ
	// cpu not sleeping and T2 -> Interrupt
	if (   (!Chipset.Shutdn || (Chipset.IORam[TIMER2_CTRL]&WKE))
		&& (Chipset.IORam[TIMER2_CTRL]&INTR))
	{
		Chipset.SoftInt = TRUE;
		bInterrupt = TRUE;
	}
	// cpu sleeping and T2 -> Wake Up
	if (Chipset.Shutdn && (Chipset.IORam[TIMER2_CTRL]&WKE))
	{
		Chipset.IORam[TIMER2_CTRL] &= ~WKE;	// clear WKE bit
		Chipset.bShutdnWake = TRUE;			// wake up from SHUTDN mode
		SetEvent(hEventShutdn);				// wake up emulation thread
	}
	return;
}

static VOID RescheduleT2(BOOL bRefPoint)
{
	UINT uDelay;
	_ASSERT(uT2TimerId == 0);				// timer2 must stopped
	if (bRefPoint)							// save reference time
	{
		dwT2Ref = Chipset.t2;				// timer2 value at last timer2 access
		dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); // cpu cycle counter at last timer2 access
		QueryPerformanceCounter(&lT2Ref);	// time of corresponding Chipset.t2 value
		uDelay = Chipset.t2;				// timer value for delay
	}
	else									// called without new refpoint, restart t2 with actual value
	{
		uDelay = CalcT2();					// actual timer value for delay
	}
	if ((bOutRange = uDelay > uT2MaxTicks))	// delay greater maximum delay
		uDelay = uT2MaxTicks;				// wait maximum delay time
	uDelay = (uDelay * 125 + 1023) / 1024;	// timer delay in ms (1000/8192 = 125/1024)
	uDelay = __max(tc.wPeriodMin,uDelay);	// wait minimum delay of timer
	_ASSERT(uDelay <= tc.wPeriodMax);		// inside maximum event delay
	// start timer2; schedule event, when Chipset.t2 will be zero
	VERIFY(uT2TimerId = timeSetEvent(uDelay,0,&TimeProc,2,TIME_ONESHOT));
	return;
}

static VOID AbortT2(VOID)
{
	_ASSERT(uT2TimerId);
	timeKillEvent(uT2TimerId);				// kill event
	uT2TimerId = 0;							// then reset var
	return;
}

static void CALLBACK TimeProc(UINT uEventId, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
	if (uEventId == 0) return;				// illegal EventId

	if (uEventId == uT1TimerId)				// called from timer1 event (default period 16 Hz)
	{
		EnterCriticalSection(&csT1Lock);
		{
			Chipset.t1 = (Chipset.t1-1)&0xF;// decrement timer value
			CheckT1(Chipset.t1);			// test timer1 control bits
		}
		LeaveCriticalSection(&csT1Lock);
		return;
	}
	if (uEventId == uT2TimerId)				// called from timer2 event, Chipset.t2 should be zero
	{
		EnterCriticalSection(&csT2Lock);
		{
			uT2TimerId = 0;					// single shot timer timer2 stopped
			if (!bOutRange)					// timer event elapsed
			{
				// timer2 overrun, test timer2 control bits else restart timer2
				Chipset.t2 = CalcT2();		// calculate new timer2 value
				CheckT2(Chipset.t2);		// test timer2 control bits
			}
			RescheduleT2(!bOutRange);		// restart timer2
		}
		LeaveCriticalSection(&csT2Lock);
		return;
	}
	return;
	UNREFERENCED_PARAMETER(uMsg);
	UNREFERENCED_PARAMETER(dwUser);
	UNREFERENCED_PARAMETER(dw1);
	UNREFERENCED_PARAMETER(dw2);
}

VOID SetHP48Time(VOID)						// set date and time
{
	SYSTEMTIME ts;
	ULONGLONG  ticks, time;
	DWORD      dw;
	WORD       crc, i;
	LPBYTE     pbyTime;

	_ASSERT(sizeof(ULONGLONG) == 8);		// check size of datatype

	GetLocalTime(&ts);						// local time, _ftime() cause memory/resource leaks

	// calculate days until 01.01.0000 (Erlang BIF localtime/0)
	dw = (DWORD) ts.wMonth;
	if (dw > 2)
		dw -= 3L;
	else
	{
		dw += 9L;
		--ts.wYear;
	}
	dw = (DWORD) ts.wDay + (153L * dw + 2L) / 5L;
	dw += (146097L * (((DWORD) ts.wYear) / 100L)) / 4L;
	dw +=   (1461L * (((DWORD) ts.wYear) % 100L)) / 4L;
	dw += (-719469L + 719528L);				// days from year 0

	ticks = (ULONGLONG) dw;					// convert to 64 bit

	// convert into seconds and add time
	ticks = ticks * 24L + (ULONGLONG) ts.wHour;
	ticks = ticks * 60L + (ULONGLONG) ts.wMinute;
	ticks = ticks * 60L + (ULONGLONG) ts.wSecond;

	// create timerticks = (s + ms) * 8192
	ticks = (ticks << 13) | (((ULONGLONG) ts.wMilliseconds << 10) / 125);

	ticks += Chipset.t2;					// add actual timer2 value

	time = ticks;							// save for calc. timeout
	time += OFF_TIME;						// add 10 min for auto off

	pbyTime = Port0 + RPLTIME;				// HP addresses for clock in port0

	crc = 0x0;								// reset crc value
	for (i = 0; i < 13; ++i)				// write date and time
	{
		*pbyTime = (BYTE) ticks & 0xf;		// time
		crc = UpCRC(crc,*pbyTime);
		ticks >>= 4;

		pbyTime[13+4] = (BYTE) time & 0xf;	// auto off
		time >>= 4;
		++pbyTime;
	}

	Nunpack(pbyTime,crc,4);					// write crc

	pbyTime[13+4] = 0xf;
	return;
}

VOID StartTimers(VOID)
{
	if (bStarted)							// timer running
		return;								// -> quit
	if (Chipset.IORam[TIMER2_CTRL]&RUN)		// start timer1 and timer2 ?
	{
		bStarted = TRUE;					// flag timer running
		// initialisation of NINT2 lines
		bNINT2T1 = (Chipset.IORam[TIMER1_CTRL]&INTR) != 0 && (Chipset.t1 & 8) != 0;
		bNINT2T2 = (Chipset.IORam[TIMER2_CTRL]&INTR) != 0 && (Chipset.t2 & 0x80000000) != 0;
		timeGetDevCaps(&tc,sizeof(tc));		// get timer resolution

		// max. timer2 ticks that can be handled by one timer event
		uT2MaxTicks = __min((0xFFFFFFFF / 1024),tc.wPeriodMax);
		uT2MaxTicks = __min((0xFFFFFFFF - 1023) / 125,uT2MaxTicks * 1024 / 125);

		CheckT1(Chipset.t1);				// check for timer1 interrupts
		CheckT2(Chipset.t2);				// check for timer2 interrupts
		// set timer resolution to greatest possible one
		bAccurateTimer = (timeBeginPeriod(tc.wPeriodMin) == TIMERR_NOERROR);
		// set timer1 with given period
		VERIFY(uT1TimerId = timeSetEvent(T1_FREQ,0,&TimeProc,1,TIME_PERIODIC));
		RescheduleT2(TRUE);					// start timer2
	}
	return;
}

VOID StopTimers(VOID)
{
	if (!bStarted)							// timer stopped
		return;								// -> quit
	if (uT1TimerId != 0)					// timer1 running
	{
		// Critical Section handler may cause a dead lock
		timeKillEvent(uT1TimerId);			// stop timer1
		uT1TimerId = 0;						// set flag timer1 stopped
	}
	if (uT2TimerId != 0)					// timer2 running
	{
		EnterCriticalSection(&csT2Lock);
		{
			Chipset.t2 = CalcT2();			// update chipset timer2 value
		}
		LeaveCriticalSection(&csT2Lock);
		AbortT2();							// stop timer2 outside critical section
	}
	bStarted = FALSE;
	if (bAccurateTimer)						// "Accurate timer" running
	{
		timeEndPeriod(tc.wPeriodMin);		// finish service
	}
	return;
}

DWORD ReadT2(VOID)
{
	DWORD dwT2;
	EnterCriticalSection(&csT2Lock);
	{
		dwT2 = CalcT2();					// calculate timer2 value or if stopped last timer value
		CheckT2(dwT2);						// update timer2 control bits
	}
	LeaveCriticalSection(&csT2Lock);
	return dwT2;
}

VOID SetT2(DWORD dwValue)
{
	// calling AbortT2() inside Critical Section handler may cause a dead lock
	if (uT2TimerId != 0)					// timer2 running
		AbortT2();							// stop timer2
	EnterCriticalSection(&csT2Lock);
	{
		Chipset.t2 = dwValue;				// set new value
		CheckT2(Chipset.t2);				// test timer2 control bits
		if (bStarted)						// timer running
			RescheduleT2(TRUE);				// restart timer2
	}
	LeaveCriticalSection(&csT2Lock);
	return;
}

BYTE ReadT1(VOID)
{
	BYTE nT1;
	EnterCriticalSection(&csT1Lock);
	{
		nT1 = Chipset.t1;					// read timer1 value
		CheckT1(nT1);						// update timer1 control bits
	}
	LeaveCriticalSection(&csT1Lock);
	return nT1;
}

VOID SetT1(BYTE byValue)
{
	BOOL bEqual;

	_ASSERT(byValue < 0x10);				// timer1 is only a 4bit counter

	EnterCriticalSection(&csT1Lock);
	{
		bEqual = (Chipset.t1 == byValue);	// check for same value
	}
	LeaveCriticalSection(&csT1Lock);
	if (bEqual) return;						// same value doesn't restart timer period

	if (uT1TimerId != 0)					// timer1 running
	{
		timeKillEvent(uT1TimerId);			// stop timer1
		uT1TimerId = 0;						// set flag timer1 stopped
	}
	EnterCriticalSection(&csT1Lock);
	{
		Chipset.t1 = byValue;				// set new timer1 value
		CheckT1(Chipset.t1);				// test timer1 control bits
	}
	LeaveCriticalSection(&csT1Lock);
	if (bStarted)							// timer running
	{
		// restart timer1 to get full period of frequency
		VERIFY(uT1TimerId = timeSetEvent(T1_FREQ,0,&TimeProc,1,TIME_PERIODIC));
	}
	return;
}
