#include "BaseExpansionAudio.h"

class UnlDripGameAudio : public BaseExpansionAudio
{
private:
	uint8_t _buffer[256];
	uint8_t _readPos;
	uint8_t _writePos;
	bool _bufferFull;
	bool _bufferEmpty;

	uint16_t _freq;
	uint16_t _timer;
	uint8_t _volume;
	int16_t _prevOutput;

protected:
	void StreamState(bool saving) override
	{
		BaseExpansionAudio::StreamState(saving);
		ArrayInfo<uint8_t> buffer { _buffer, 256 };
		Stream(_readPos, _writePos, _bufferFull, _bufferEmpty, _freq, _timer, _volume, _prevOutput, buffer);
	}

	void ClockAudio() override
	{
		if(_bufferEmpty) {
			return;
		}

		_timer--;
		if(_timer == 0) {
			//Each time the timer reaches zero, it is reloaded and a byte is removed from the
			//channel's FIFO and is output (with 0x80 being the 'center' voltage) at the
			//channel's specified volume.
			_timer = _freq;

			if(_readPos == _writePos) {
				_bufferFull = false;
			}

			_readPos++;
			SetOutput(((int)_buffer[_readPos] - 0x80) * _volume);

			if(_readPos == _writePos) {
				_bufferEmpty = true;
			}
		}
	}

	void SetOutput(int16_t output)
	{
		_console->GetApu()->AddExpansionAudioDelta(AudioChannel::VRC7, (output - _prevOutput) * 3);
		_prevOutput = output;
	}

	void ResetBuffer()
	{
		memset(_buffer, 0, 256);
		_readPos = 0;
		_writePos = 0;
		_bufferFull = false;
		_bufferEmpty = true;
	}

public:
	UnlDripGameAudio(shared_ptr<Console> console) : BaseExpansionAudio(console)
	{
		_freq = 0;
		_timer = 0;
		_volume = 0;
		_prevOutput = 0;
		ResetBuffer();
	}

	uint8_t ReadRegister()
	{
		uint8_t result = 0;
		if(_bufferFull) {
			result |= 0x80;
		}
		if(_bufferEmpty) {
			result |= 0x40;
		}
		return result;
	}

	void WriteRegister(uint16_t addr, uint8_t value)
	{
		switch(addr & 0x03) {
			case 0:
				//Writing any value will silence the corresponding sound channel
				//When a channel's Clear FIFO register is written to, its timer is reset to the
				//last written frequency and it is silenced, outputting a 'center' voltage.
				ResetBuffer();
				SetOutput(0);
				_timer = _freq;
				break;

			case 1:
				//Writing a value will insert it into the FIFO.
				if(_readPos == _writePos) {
					//When data is written to an empty channel's Data Port, the channel's timer is
					//reloaded from the Period registers and playback begins immediately.
					_bufferEmpty = false;
					SetOutput((value - 0x80) * _volume);
					_timer = _freq;
				}

				_buffer[_writePos++] = value;
				if(_readPos == _writePos) {
					_bufferFull = true;
				}
				break;

			case 2:
				//Specifies channel playback rate, in cycles per sample (lower 8 bits)
				_freq = (_freq & 0x0F00) | value;
				break;

			case 3:
				//Specifies channel playback rate, in cycles per sample (higher 8 bits) (bits 0-3)
				//Specifies channel playback volume (bits 4-7)
				_freq = (_freq & 0xFF) | ((value & 0x0F) << 8);
				_volume = (value & 0xF0) >> 4;

				if(!_bufferEmpty) {
					//Updates to a channel's Period do not take effect until the current
					//sample has finished playing, but updates to a channel's Volume take effect immediately.
					SetOutput(((int)_buffer[_readPos] - 0x80) * _volume);
				}
				break;
		}
	}
};
