/*
 *   timer.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 2004 Christoph Gießelink
 *
 */
#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 01.01.1970 00:00:00
#define UNIX_0_TIME	((ULONGLONG) 0x0001cf2e8f800000)

// 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 HANDLE hCThreadT1 = NULL;			// timer1 thread control
static HANDLE hEventT1;

static HANDLE hCThreadT2 = NULL;			// timer2 thread control
static HANDLE hEventT2;

static BOOL   bNINT2T1 = FALSE;				// state of NINT2 affected from timer1
static BOOL   bNINT2T2 = FALSE;				// state of NINT2 affected from timer2

static LARGE_INTEGER lT2Ref;				// counter value at timer2 start
static DWORD  dwT2Ref;						// timer2 value at last timer2 access
static DWORD  dwT2Cyc;						// cpu cycle counter at last timer2 access

static BOOL   bT2Running;

static __inline VOID SetT2Refpoint(VOID)
{
	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
}

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 much faster for fixing long freeze times
		DWORD dwCycPerTick = (3 * T2CYCLES) / 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

		// 2nd timer call in a 156ms time frame or elapsed time is negative (Win2k bug)
		if (!Chipset.Shutdn && ((dwT2Dif > 0x01 && dwT2Dif <= 0x500) || (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 DWORD WINAPI T1Thread(LPVOID pParam)
{
	while (WaitForSingleObject(hEventT1,T1_FREQ) == WAIT_TIMEOUT)
	{
		EnterCriticalSection(&csT1Lock);
		{
			Chipset.t1 = (Chipset.t1-1)&0xF;// decrement timer value
			CheckT1(Chipset.t1);			// test timer1 control bits
		}
		LeaveCriticalSection(&csT1Lock);
	}
	return 0;
	UNREFERENCED_PARAMETER(pParam);
}

static DWORD WINAPI T2Thread(LPVOID pParam)
{
	DWORD dwDelay;
	BOOL  bOutRange = FALSE;

	while (bT2Running)
	{
		EnterCriticalSection(&csT2Lock);
		{
			if (!bOutRange)					// save reference time
			{
				SetT2Refpoint();			// reference point for CalcT2()
				dwDelay = Chipset.t2;		// timer value for delay
			}
			else							// called without new refpoint, restart t2 with actual value
			{
				dwDelay = CalcT2();			// actual timer value for delay
			}

			// delay too big for multiplication
			if ((bOutRange = dwDelay > (0xFFFFFFFF - 1023) / 125))
				dwDelay = (0xFFFFFFFF - 1023) / 125; // wait maximum delay time
		}
		LeaveCriticalSection(&csT2Lock);

		dwDelay = (dwDelay * 125 + 1023) / 1024; // timer delay in ms (1000/8192 = 125/1024)
		dwDelay = __max(1,dwDelay);			// wait minimum delay of timer

		if (WaitForSingleObject(hEventT2,dwDelay) != WAIT_TIMEOUT)
		{
			// got new timer2 value
			bOutRange = FALSE;				// set new refpoint
			continue;
		}

		EnterCriticalSection(&csT2Lock);
		{
			// timer2 overrun, test timer2 control bits
			Chipset.t2 = CalcT2();			// calculate new timer2 value
			CheckT2(Chipset.t2);			// test timer2 control bits
		}
		LeaveCriticalSection(&csT2Lock);
	}
	return 0;
	UNREFERENCED_PARAMETER(pParam);
}

static VOID AbortT1(VOID)
{
	_ASSERT(hCThreadT1);
	SetEvent(hEventT1);						// leave timer1 update thread
	WaitForSingleObject(hCThreadT1,INFINITE);
	CloseHandle(hCThreadT1);
	hCThreadT1 = NULL;						// set flag display update stopped
	return;
}

static VOID AbortT2(VOID)
{
	_ASSERT(hCThreadT2);
	bT2Running = FALSE;						// leave working loop
	SetEvent(hEventT2);						// leave timer2 update thread
	WaitForSingleObject(hCThreadT2,INFINITE);
	CloseHandle(hCThreadT2);
	hCThreadT2 = NULL;						// set flag display update stopped
	return;
}

VOID SetHP48Time(VOID)						// set date and time
{
	SYSTEMTIME ts;
	ULONGLONG  ticks, time;
	DWORD      dw;
	WORD       crc, i;
	BYTE       p[4];

	_ASSERT(sizeof(ULONGLONG) == 8);		// check size of datatype

	GetLocalTime(&ts);						// local time, _ftime() cause memory/resource leaks

	// calculate days until 01.01.1970
	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;

	// convert into seconds and add time
	dw = dw * 24L + (DWORD) ts.wHour;
	dw = dw * 60L + (DWORD) ts.wMinute;
	dw = dw * 60L + (DWORD) ts.wSecond;

	// create timerticks = (s + ms) * 8192
	ticks = ((ULONGLONG) dw << 13) | (((ULONGLONG) ts.wMilliseconds << 10) / 125);

	ticks += UNIX_0_TIME;					// add offset ticks from year 0
	ticks += Chipset.t2;					// add actual timer2 value

	time = ticks;							// save for calc. timeout
	time += OFF_TIME;						// add 10 min for auto off

	dw = RPLTIME;							// HP addresses for clock in port0

	crc = 0x0;								// reset crc value
	for (i = 0; i < 13; ++i, ++dw)			// write date and time
	{
		*p = (BYTE) ticks & 0xf;
		crc = (crc >> 4) ^ (((crc ^ ((WORD) *p)) & 0xf) * 0x1081);
		Chipset.Port0[dw] = *p;				// always store in port0
		ticks >>= 4;
	}

	Nunpack(p,crc,4);						// write crc
	memcpy(Chipset.Port0+dw,p,4);			// always store in port0

	dw += 4;								// HP addresses for timeout

	for (i = 0; i < 13; ++i, ++dw)			// write time for auto off
	{
		// always store in port0
		Chipset.Port0[dw] = (BYTE) time & 0xf;
		time >>= 4;
	}

	Chipset.Port0[dw] = 0xf;				// always store in port0
	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;
		CheckT1(Chipset.t1);				// check for timer1 interrupts
		CheckT2(Chipset.t2);				// check for timer2 interrupts

		// events to cancel timer loops
		hEventT1 = CreateEvent(NULL,FALSE,FALSE,NULL);
		hEventT2 = CreateEvent(NULL,FALSE,FALSE,NULL);

		bT2Running = TRUE;					// don't leave worker thread

		// start timer1 update thread
		VERIFY(hCThreadT1 = CreateThread(NULL,0,&T1Thread,NULL,CREATE_SUSPENDED,NULL));
		VERIFY(SetThreadPriority(hCThreadT1,THREAD_PRIORITY_HIGHEST));
		ResumeThread(hCThreadT1);

		// start timer2 update thread
		VERIFY(hCThreadT2 = CreateThread(NULL,0,&T2Thread,NULL,CREATE_SUSPENDED,NULL));
		VERIFY(SetThreadPriority(hCThreadT2,THREAD_PRIORITY_HIGHEST));
		SetT2Refpoint();					// reference point for CalcT2()
		ResumeThread(hCThreadT2);
	}
	return;
}

VOID StopTimers(VOID)
{
	if (!bStarted)							// timer stopped
		return;								// -> quit
	if (hCThreadT1 != NULL)					// timer1 running
	{
		// Critical Section handler may cause a dead lock
		AbortT1();							// stop timer1
	}
	if (hCThreadT2 != NULL)					// timer2 running
	{
		EnterCriticalSection(&csT2Lock);
		{
			Chipset.t2 = CalcT2();			// update chipset timer2 value
		}
		LeaveCriticalSection(&csT2Lock);
		AbortT2();							// stop timer2 outside critical section
	}
	CloseHandle(hEventT1);					// close timer1 event
	CloseHandle(hEventT2);					// close timer2 event
	bStarted = FALSE;
	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)
{
	EnterCriticalSection(&csT2Lock);
	{
		Chipset.t2 = dwValue;				// set new value
		CheckT2(Chipset.t2);				// test timer2 control bits
		if (bStarted)						// timer running
		{
			SetT2Refpoint();				// reference point for CalcT2()
			SetEvent(hEventT2);				// new delay
		}
	}
	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 (hCThreadT1 != NULL)					// timer1 running
	{
		AbortT1();							// stop timer1
	}
	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(hCThreadT1 = CreateThread(NULL,0,&T1Thread,NULL,CREATE_SUSPENDED,NULL));
		VERIFY(SetThreadPriority(hCThreadT1,THREAD_PRIORITY_HIGHEST));
		ResumeThread(hCThreadT1);
	}
	return;
}

