#include "pch.h"
#include "Menestrel.h"
#include "Config.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif

// установка режимов работы счётчика
void Counter8253::SetControl(const uint8_t ctrl)
{
	// у ctrl имеет значение только младшие 6 бит
	// по мотивам алгоритмов из MAME
	m_nRL = (ctrl >> 4) & 3; // режим READ/LOAD

	if (m_nRL)
	{
		m_bBCD = !!(ctrl & 1);
		m_nMode = (ctrl >> 1) & 7; // три бита режима

		if (m_nMode > 5)
		{
			m_nMode &= 3;
		}

		m_bwmsb = false;
		m_phase = 0; /* Phase 0 is always the phase after a m_nMode control write */
		set_output(m_nMode ? false : true);
	}
	else
	{
		/*  RL == 0, Experimentally verified: this command does not affect the m_nMode control */
	}
}

void Counter8253::load_count(uint16_t newcount)
{
	// алгоритм взят из MAME
	/* Значение счётчика 1 запрещено в режимах 2 и 3. Что происходит в этом случае
	определено экспериментально. */
	switch (m_nMode)
	{
		case 2:
			if (newcount == 1)
			{
				newcount++;
			}

			if (m_phase == 0)
			{
				m_phase = 1;
			}

			break;

		case 3:
			if (newcount == 1)
			{
				newcount = 0;
			}

			if (m_phase == 0)
			{
				m_phase = 1;
			}

			break;

		case 0:
		case 4:
			m_phase = 1;
			break;
	}

	m_nCounter = newcount;
}

void Counter8253::set_output(const bool v)
{
	// алгоритм взят из MAME
	m_nOutput = v;
}

void Counter8253::load_counter_value()
{
	// алгоритм взят из MAME
	m_nValue = m_nCounter;

	if (m_nMode == 3 && !m_nOutput)
		m_nValue &= 0xfffe;
}

int Counter8253::adjusted_count() const
{
	// алгоритм взят из MAME
	const uint16_t val = m_nValue;

	if (!m_bBCD)
	{
		return (val == 0) ? 0x10000 : val;
	}

	if (val == 0)
	{
		return 10000;
	}

	/* In BCD m_nMode, a nybble loaded with value A-F counts down the same as in
	   binary m_nMode, but wraps around to 9 instead of F after 0, so loading the
	   count with 0xFFFF gives a period of
	          0xF  - for the units to count down to 0
	   +   10*0xF  - for the tens to count down to 0
	   +  100*0xF  - for the hundreds to count down to 0
	   + 1000*0xF  - for the thousands to count down to 0
	   = 16665 cycles
	*/
	return
	    ((val >> 12) & 0xF) * 1000 +
	    ((val >>  8) & 0xF) * 100 +
	    ((val >>  4) & 0xF) * 10 +
	     (val        & 0xF);
}

/* This function subtracts 1 from m_nValue "cycles" times, taking into
   account binary or BCD operation, and wrapping around from 0 to 0xFFFF or
   0x9999 as necessary. */
void Counter8253::decrease_counter_value(unsigned int cycles)
{
	// алгоритм взят из MAME
	if (!m_bBCD)
	{
		m_nValue -= (cycles & 0xffff);
		return;
	}

	unsigned int units = m_nValue & 0xf;
	unsigned int tens = (m_nValue >> 4) & 0xf;
	unsigned int hundreds = (m_nValue >> 8) & 0xf;
	unsigned int thousands = (m_nValue >> 12) & 0xf;

	if (cycles <= units)
	{
		units -= cycles;
	}
	else
	{
		cycles -= units;
		units = (10 - cycles % 10) % 10;
		cycles = (cycles + 9) / 10; /* the +9 is so we get a carry if cycles%10 wasn't 0 */

		if (cycles <= tens)
		{
			tens -= cycles;
		}
		else
		{
			cycles -= tens;
			tens = (10 - cycles % 10) % 10;
			cycles = (cycles + 9) / 10;

			if (cycles <= hundreds)
			{
				hundreds -= cycles;
			}
			else
			{
				cycles -= hundreds;
				hundreds = (10 - cycles % 10) % 10;
				cycles = (cycles + 9) / 10;
				thousands = (10 + thousands - cycles % 10) % 10;
			}
		}
	}

	m_nValue = (thousands << 12) | (hundreds << 8) | (tens << 4) | units;
}

void Counter8253::reset()
{
	SetGate(false);
	set_output(true);
	m_phase = 0;
}


/* This emulates timer "timer" for "elapsed_cycles" cycles and assumes no
   callbacks occur during that time. */
void Counter8253::simulate(int elapsed_cycles)
{
	switch (m_nMode)
	{
		case 0:
			/* Mode 0: (Interrupt on Terminal Count)

					  +------------------
					  |
			----------+
			  <- n+1 ->

			  ^
			  +- counter load

			phase|output|length  |value|next|comment
			-----+------+--------+-----+----+----------------------------------
				0|low   |infinity|     |1   |waiting for count
				1|low   |1       |     |2   |internal delay when counter loaded
				2|low   |n       |n..1 |3   |counting down
				3|high  |infinity|0..1 |3   |counting down

			Gate level sensitive only. Low disables counting, high enables it. */

			if (m_phase)
			{
				if (m_phase == 1 && elapsed_cycles >= 0)
				{
					/* Counter load cycle */
					if (elapsed_cycles > 0)
					{
						--elapsed_cycles;
						m_phase = 2;
					}

					load_counter_value();
				}

				if (m_gate)
				{
					if (m_phase == 2)
					{
						int adjusted_value = adjusted_count();
						if (elapsed_cycles >= adjusted_value)
						{
							/* Counter wrapped, output goes high */
							elapsed_cycles -= adjusted_value;
							m_phase = 3;
							m_nValue = 0;
							set_output(true);
						}
					}

					decrease_counter_value(elapsed_cycles);
				}
			}
			break;


		case 1:
			/* Mode 1: (Hardware Retriggerable One-Shot a.k.a. Programmable One-Shot)

			-----+       +------------------
				 |       |
				 +-------+
				 <-  n  ->

			  ^
			  +- trigger

			phase|output|length  |value|next|comment
			-----+------+--------+-----+----+----------------------------------
				0|high  |infinity|     |1   |counting down
				1|high  |1       |     |2   |internal delay to load counter
				2|low   |n       |n..1 |3   |counting down
				3|high  |infinity|0..1 |3   |counting down

			Gate rising-edge sensitive only.
			Rising edge initiates counting and resets output after next clock. */

			if (m_phase == 1 && elapsed_cycles >= 0)
			{
				/* Counter load cycle, output goes low */
				if (elapsed_cycles > 0)
				{
					--elapsed_cycles;
					m_phase = 2;
				}
				load_counter_value();
				set_output(false);
			}

			if (m_phase == 2)
			{
				int adjusted_value = adjusted_count();
				if (elapsed_cycles >= adjusted_value)
				{
					/* Counter wrapped, output goes high */
					m_phase = 3;
					set_output(true);
				}
			}

			decrease_counter_value(elapsed_cycles);

			break;


		case 2:
			/* Mode 2: (Rate Generator)

			--------------+ +---------+ +----
						  | |         | |
						  +-+         +-+
			   <-    n    -X-    n    ->
						  <1>
			^
			+- counter load or trigger

			phase|output|length  |value|next|comment
			-----+------+--------+-----+----+----------------------------------
				0|high  |infinity|     |1   |waiting for count
				1|high  |1       |     |2   |internal delay to load counter
				2|high  |n       |n..2 |3   |counting down
				3|low   |1       |1    |2   |reload counter

			Counter rewrite has no effect until repeated

			Gate rising-edge and level sensitive.
			Gate low disables counting and sets output immediately high.
			Rising-edge reloads count and initiates counting
			Gate high enables counting. */

			if (m_gate == 0 || m_phase == 0)
			{
				/* Gate low or m_nMode control write forces output high */
				set_output(true);
			}
			else
			{
				if (m_phase == 1 && elapsed_cycles >= 0)
				{
					if (elapsed_cycles > 0)
					{
						--elapsed_cycles;
						m_phase = 2;
					}
					load_counter_value();
				}

				int adjusted_value = adjusted_count();

				do
				{
					if (m_phase == 2)
					{
						if (elapsed_cycles + 1 >= adjusted_value)
						{
							/* Counter hits 1, output goes low */
							m_phase = 3;
							set_output(false);
						}
					}

					if (m_phase == 3 && elapsed_cycles > 0)
					{
						/* Reload counter, output goes high */
						elapsed_cycles -= adjusted_value;
						m_phase = 2;
						load_counter_value();
						adjusted_value = adjusted_count();
						set_output(true);
					}
				} while (elapsed_cycles >= adjusted_value);

				/* Calculate counter value */
				decrease_counter_value(elapsed_cycles);
			}
			break;


		case 3:
			/* Mode 3: (Square Wave Generator)

			----------------+           +-----------+           +----
							|           |           |           |
							+-----------+           +-----------+
				<- (n+1)/2 -X-   n/2   ->
			 ^
			 +- counter load or trigger

			phase|output|length  |value|next|comment
			-----+------+--------+-----+----+----------------------------------
				0|high  |infinity|     |1   |waiting for count
				1|high  |1       |     |2   |internal delay to load counter
				2|high  |n/2(+1) |n..0 |3   |counting down double speed, reload counter
				3|low   |n/2     |n..0 |2   |counting down double speed, reload counter

			Counter rewrite has no effect until repeated (output falling or rising)

			Gate rising-edge and level sensitive.
			Gate low disables counting and sets output immediately high.
			Rising-edge reloads count and initiates counting
			Gate high enables counting. */

			if (m_gate == 0 || m_phase == 0)
			{
				/* Gate low or m_nMode control write forces output high */
				set_output(true);
			}
			else
			{
				if (m_phase == 1 && elapsed_cycles >= 0)
				{
					if (elapsed_cycles > 0)
					{
						--elapsed_cycles;
						m_phase = 2;
					}
					load_counter_value();
				}

				if (elapsed_cycles > 0)
				{
					int adjusted_value = adjusted_count();

					do
					{
						if (m_phase == 2 && elapsed_cycles >= ((adjusted_value + 1) >> 1))
						{
							/* High phase expired, output goes low */
							elapsed_cycles -= ((adjusted_value + 1) >> 1);
							m_phase = 3;
							load_counter_value();
							adjusted_value = adjusted_count();
							set_output(false);
						}

						if (m_phase == 3 && elapsed_cycles >= (adjusted_value >> 1))
						{
							/* Low phase expired, output goes high */
							elapsed_cycles -= (adjusted_value >> 1);
							m_phase = 2;
							load_counter_value();
							adjusted_value = adjusted_count();
							set_output(true);
						}
					} while ((m_phase == 2 && elapsed_cycles >= ((adjusted_value + 1) >> 1)) ||
							(m_phase == 3 && elapsed_cycles >= (adjusted_value >> 1)));

					decrease_counter_value(elapsed_cycles * 2);
				}
			}
			break;


		case 4:
		case 5:
			/* Mode 4: (Software Trigger Strobe)
			   Mode 5: (Hardware Trigger Strobe)

			--------------+ +--------------------
						  | |
						  +-+
				<-  n+1  ->
				^         <1>
				+- counter load (m_nMode 4) or trigger (m_nMode 5)

			phase|output|length  |value|next|comment
			-----+------+--------+-----+----+----------------------------------
				0|high  |infinity|0..1 |0   |waiting for count/counting down
				1|high  |1       |     |2   |internal delay when counter loaded
				2|high  |n       |n..1 |3   |counting down
				3|low   |1       |0    |0   |strobe

			Mode 4 only: counter rewrite loads new counter
			Mode 5 only: count not reloaded immediately.
			Mode control write doesn't stop count but sets output high

			Mode 4 only: Gate level sensitive only. Low disables counting, high enables it.
			Mode 5 only: Gate rising-edge sensitive only. Rising edge initiates counting */

			if (m_gate == 0 && m_nMode == 4)
			{
			}
			else
			{
				if (m_phase == 1 && elapsed_cycles >= 0)
				{
					if (elapsed_cycles > 0)
					{
						--elapsed_cycles;
						m_phase = 2;
					}
					load_counter_value();
				}

				int adjusted_value = (m_phase == 2 && m_nValue == 0) ? 0 : adjusted_count();

				if (m_phase == 2 && elapsed_cycles >= adjusted_value)
				{
					/* Counter has hit zero, set output to low */
					elapsed_cycles -= adjusted_value;
					m_phase = 3;
					m_nValue = 0;
					set_output(false);
				}

				if (m_phase == 3 && elapsed_cycles > 0)
				{
					--elapsed_cycles;
					m_phase = 0;
					decrease_counter_value(1);
					set_output(true);
				}

				decrease_counter_value(elapsed_cycles);
			}
			break;
	}
}




void Counter8253::SetCount(uint8_t cnt)
{
	switch (m_nRL)
	{
		case 0:
			// какая-то хрень. и она игнорируется.
			break;

		case 1:
			// запись только мл.байта
			load_count(cnt);
			simulate(0);

			if (m_nMode == 0)
			{
				set_output(true);
			}

			break;

		case 2:
			// запись только старшего байта
			load_count(cnt << 8);
			simulate(0);

			if (m_nMode == 0)
			{
				set_output(true);
			}

			break;

		case 3:

			// запись сперва мл. затем старшего байта
			if (m_bwmsb)
			{
				load_count(m_lowcount | (cnt << 8));
				simulate(0);
			}
			else
			{
				m_lowcount = cnt;

				if (m_nMode == 0)
				{
					/* The Intel docs say that writing the MSB in m_nMode 0, phase
					   2 won't stop the count, but this was experimentally
					   determined to be false. */
					m_phase = 0;
					set_output(true);
				}
			}

			m_bwmsb = !m_bwmsb;
			break;
	}
}

void Counter8253::SetGate(const bool state)
{
	//if (state != m_gate)
	{
		m_gate = state;

		if (state && (m_nMode == 1 || m_nMode == 2 || m_nMode == 5))
		{
			m_phase = 1;
		}
	}
}

bool Counter8253::GetOutput() const
{
	return m_gate ? !m_nOutput : false;
}

CMenestrel::CMenestrel()
	: m_dRealStep(0)
	, m_dPSGTime(0)
	, m_dPSGStep(0)
	, m_nClock(0)
	, m_nRate(0)
	, m_bGatePrev(false)
	, m_bWritePrev(false)
{
	SetFCFilterValue(1e-7 * 3200);

	if (CreateFIRBuffers(FIR_LENGTH))
	{
		ReInit();
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}
}

CMenestrel::~CMenestrel()
    = default;

void CMenestrel::init(int c, int r)
{
	m_nClock = c;
	m_nRate = r;
	m_dRealStep = 1.0 / double(m_nRate);
	m_dPSGStep = 1.0 / double(m_nClock);
}

void CMenestrel::ReInit()
{
	// 1000000 - тактовая частота для i8253
	init(1003000, g_Config.m_nSoundSampleRate);
	Reset();
	const double w0 = 2 * 12000.0 / double(g_Config.m_nSoundSampleRate);
	const double w1 = 0.0;
	const int res = fir_linphase(m_nFirLength, w0, w1, FIR_FILTER::LOWPASS,
	                       FIR_WINDOW::BLACKMAN_HARRIS, true, 0.0, m_pH.get());
}

void CMenestrel::Reset()
{
	m_dPSGTime = 0;
	m_bGatePrev = false;
	m_bWritePrev = false;

	for (auto &cnt : LeftCh)
	{
		cnt.reset();
	}

	for (auto &cnt : RightCh)
	{
		cnt.reset();
	}
}


void CMenestrel::SetData(const uint16_t inVal)
{
	constexpr uint16_t BIT15 = (1 << 15);
	constexpr uint16_t BIT12 = (1 << 12);
	constexpr uint16_t BIT11 = (1 << 11);
	constexpr uint16_t BIT10 = (1 << 10);

	// в inVal в старшем байте управляющие биты, а в младшем - данные
	uint16_t v = ~inVal; // проинвертируем
	const bool bGate = !!(v & BIT15); // старший бит - разрешение выхода всех счётчиков

	if (bGate != m_bGatePrev) // если состояние поменялось
	{
		// вызываем блок этих функций
		m_bGatePrev = bGate; // сохраняем новое состояние
		// в функции тоже есть проверка состояния, может её нафиг?
		LeftCh[0].SetGate(bGate);
		LeftCh[1].SetGate(bGate);
		LeftCh[2].SetGate(bGate);
		RightCh[0].SetGate(bGate);
		RightCh[1].SetGate(bGate);
		RightCh[2].SetGate(bGate);
	}

	const bool bWR = !(v & BIT12);

	if (bWR && !m_bWritePrev)
	{
		const bool bCSL = !(v & BIT10);
		const bool bCSR = !(v & BIT11);
		const uint32_t nReg = (v >> 8) & 3;
		v &= 0xff;

		if (bCSL)
		{
			if (nReg < 3)
			{
				LeftCh[nReg].SetCount(v);
			}
			else
			{
				// задаём режим управления счётчиком
				const uint32_t nCnt = (v >> 6) & 3;

				if (nCnt < 3)
				{
					LeftCh[nCnt].SetControl(v);
				}
			}
		}

		if (bCSR)
		{
			if (nReg < 3)
			{
				RightCh[nReg].SetCount(v);
			}
			else
			{
				// задаём режим управления счётчиком
				const uint32_t nCnt = (v >> 6) & 3;

				if (nCnt < 3)
				{
					RightCh[nCnt].SetControl(v);
				}
			}
		}
	}

	m_bWritePrev = bWR; // будем по спаду обрабатывать

}


void CMenestrel::calc()
{
	constexpr double v = (double(MAX_SAMPLE) / FLOAT_BASE) * (1.0 / 3.0);
	constexpr double bcr = v * 0.07;
	constexpr double b = v - bcr;
	m_dAccL = m_dAccR = 0.0;

	// дальше надо считать периоды отдельных счётчиков
	for (auto &chn : LeftCh)
	{
		chn.simulate(1);
		if (chn.GetOutput())
		{
			m_dAccL += b;
			m_dAccR += bcr;
		}
	}

	for (auto &chn : RightCh)
	{
		chn.simulate(1);
		if (chn.GetOutput())
		{
			m_dAccR += b;
			m_dAccL += bcr;
		}
	}
}


void CMenestrel::GetSample(sOneSample *pSm)
{
	SAMPLE_INT l = 0.0;
	SAMPLE_INT r = 0.0;

	if (m_bEnableSound)
	{
//      double f_mixL1 = 0.0;
//      double f_mixR1 = 0.0;
//      int n = 0;

		/* Simple m_nRate converter */
		do
		{
			calc();
			RCFilterLF(m_dPSGStep);
			RCFilterRF(m_dPSGStep);
//          f_mixL1 += RCFilterCalc(m_RCFL);
//          f_mixR1 += RCFilterCalc(m_RCFR);
//          n++;
			m_dPSGTime += m_dPSGStep;
		}
		while (m_dPSGTime < m_dRealStep);

		m_dPSGTime -= m_dRealStep;
//      l = SAMPLE_INT(f_mixL1 / n);
//      r = SAMPLE_INT(f_mixR1 / n);
		l = m_bFilter ? RCFilterCalc(m_RCFL) : m_dAccL;
		r = m_bFilter ? RCFilterCalc(m_RCFR) : m_dAccR;
	}

	if (m_bDCOffset)
	{
		// для чистоты огибающей это лучше не включать.
		l = DCOffset(l, m_dAvgL, m_pdDCBufL.get(), m_nDCBufPosL);
		r = DCOffset(r, m_dAvgR, m_pdDCBufR.get(), m_nDCBufPosR);
	}

	// фильтр
	pSm->s[OSL] = FIRFilter(l, m_pdFBufL.get(), m_nFBufPosL);
	pSm->s[OSR] = FIRFilter(r, m_pdFBufR.get(), m_nFBufPosR);
}

