#pragma once
#include "stdafx.h"
#include "BaseFdsChannel.h"

class ModChannel : public BaseFdsChannel
{
private:
	const int32_t ModReset = 0xFF;
	const int32_t _modLut[8] = { 0,1,2,4,ModReset,-4,-2,-1 };

	int8_t _counter = 0;
	bool _modulationDisabled = false;

	uint8_t _modTable[64];
	uint8_t _modTablePosition = 0;
	uint16_t _overflowCounter = 0;
	int32_t _output = 0;

protected:
	void StreamState(bool saving) override
	{
		BaseFdsChannel::StreamState(saving);
		
		ArrayInfo<uint8_t> modTable = { _modTable, 64 };
		Stream(_counter, _modulationDisabled, _modTablePosition, _overflowCounter, modTable, _output);
	}

public:
	virtual void WriteReg(uint16_t addr, uint8_t value) override
	{
		switch(addr) {
			case 0x4084:
			case 0x4086:
				BaseFdsChannel::WriteReg(addr, value);
				break;
			case 0x4085:
				UpdateCounter(value & 0x7F);
				break;
			case 0x4087:
				BaseFdsChannel::WriteReg(addr, value);
				_modulationDisabled = (value & 0x80) == 0x80;
				if(_modulationDisabled) {
					_overflowCounter = 0;
				}
				break;
		}
	}

	void WriteModTable(uint8_t value)
	{
		//"This register has no effect unless the mod unit is disabled via the high bit of $4087."
		if(_modulationDisabled) {
			_modTable[_modTablePosition & 0x3F] = value & 0x07;
			_modTable[(_modTablePosition + 1) & 0x3F] = value & 0x07;
			_modTablePosition = (_modTablePosition + 2) & 0x3F;
		}
	}

	void UpdateCounter(int8_t value)
	{
		_counter = value;
		if(_counter >= 64) {
			_counter -= 128;
		} else if(_counter < -64) {
			_counter += 128;
		}
	}

	bool IsEnabled()
	{
		return !_modulationDisabled && _frequency > 0;
	}

	bool TickModulator()
	{
		if(IsEnabled()) {
			_overflowCounter += _frequency;

			if(_overflowCounter < _frequency) {
				//Overflowed, tick the modulator
				int32_t offset = _modLut[_modTable[_modTablePosition]];
				UpdateCounter(offset == ModReset ? 0 : _counter + offset);

				_modTablePosition = (_modTablePosition + 1) & 0x3F;

				return true;
			}
		}
		return false;
	}

	void UpdateOutput(uint16_t volumePitch)
	{
		//Code from NesDev Wiki

		// pitch   = $4082/4083 (12-bit unsigned pitch value)
		// counter = $4085 (7-bit signed mod counter)
		// gain    = $4084 (6-bit unsigned mod gain)

		// 1. multiply counter by gain, lose lowest 4 bits of result but "round" in a strange way
		int32_t temp = _counter * _gain;
		int32_t remainder = temp & 0xF;
		temp >>= 4;
		if(remainder > 0 && (temp & 0x80) == 0) {
			temp += _counter < 0 ? -1 : 2;
		}

		// 2. wrap if a certain range is exceeded
		if(temp >= 192) {
			temp -= 256;
		} else if(temp < -64) {
			temp += 256;
		}

		// 3. multiply result by pitch, then round to nearest while dropping 6 bits
		temp = volumePitch * temp;
		remainder = temp & 0x3F;
		temp >>= 6;
		if(remainder >= 32) {
			temp += 1;
		}

		// final mod result is in temp
		_output = temp;
	}

	int32_t GetOutput()
	{
		return IsEnabled() ? _output : 0;
	}
};
