#include <stdint.h>

#include <avr/io.h>
#include <avr/interrupt.h>

#include <util/delay.h>
#include <util/atomic.h>

#include "sn76489.h"

#define SAMPLE_RATE (44100)

#define VGM_BUFFER_SIZE 768

volatile uint16_t vgm_sample_timer;
volatile uint16_t vgm_buffer_read_pointer = 0;
volatile uint16_t vgm_buffer_write_pointer = 0;
volatile uint16_t vgm_buffer_count = 0;

volatile uint8_t vgm_buffer[VGM_BUFFER_SIZE];

ISR(TWI_vect) {
	switch (TWSR) {
		case 0x60: // Own SLA+W has been received; ACK has been returned.
			break;
		case 0x80: // Previously addressed with own SLA+W; data has been received; ACK has been returned.
			if (vgm_buffer_count < VGM_BUFFER_SIZE) {
				vgm_buffer[vgm_buffer_write_pointer++] = TWDR;
				vgm_buffer_write_pointer %= VGM_BUFFER_SIZE;
				++vgm_buffer_count;
			}
			break;
		case 0xA8: // Own SLA+R has been received; ACK has been returned.
			TWDR = (vgm_buffer_count < VGM_BUFFER_SIZE) ? 0x00 : 0x01;
			break;
	}
	TWCR |= _BV(TWINT);
}

uint8_t vgm_buffer_read(void) {
	while (vgm_buffer_count == 0);
	uint8_t value;
	ATOMIC_BLOCK(ATOMIC_FORCEON) {
		value = vgm_buffer[vgm_buffer_read_pointer++];
		--vgm_buffer_count;
		vgm_buffer_read_pointer %= VGM_BUFFER_SIZE;
	}
	return value;

}

int main(void) {
	
	// Use fast PWM mode to generate an output "analogue" (PWM) waveform.
	TCCR0A |= _BV(COM0A0) | _BV(COM0A1) | _BV(COM0B0) | _BV(COM0B1) | _BV(WGM00) | _BV(WGM01);
	TCCR0B |= _BV(CS00);

	// OC0A is PD6; OC0B is PD5.
	DDRD |= _BV(6) | _BV(5);

	// Set up timer 1 to run at the sample rate.
	TCCR1B |= (0b001 << CS10); // Run at native clock speed, no prescaling.
	TIMSK1 |= _BV(OCIE1A);     // Interrupt on match A.
	TCCR1B |= _BV(WGM12);      // Mode 4 (TOP=OCR1A, CTC).
	OCR1A = (F_CPU / SAMPLE_RATE) - 1;	
	
	// Enable the I²C slave receiver.
	TWAR = 0x7E; // Respond to address 0x7E
	TWCR = _BV(TWEA) | _BV(TWEN) | _BV(TWIE);
	
	sei();
	
	for (;;) {
	
		// Initialise the SN76489 emulator.
		sn76489_init();
	
		uint8_t reset = 0;
	
		while (!reset) {
			while (vgm_sample_timer > 0);
			uint8_t data = 0;
			switch (data = vgm_buffer_read()) {
				case 0x00:
					switch (vgm_buffer_read()) {
						case 0x0C:
							sn76489_frequency = (sn76489_frequency & 0xFFFFFF00UL) | vgm_buffer_read();
							break;
						case 0x0D:
							sn76489_frequency = (sn76489_frequency & 0xFFFF00FFUL) | (vgm_buffer_read() << 8);
							break;
						case 0x0E:
							sn76489_frequency = (sn76489_frequency & 0xFF00FFFFUL) | ((uint32_t)vgm_buffer_read() << 16);
							break;
						case 0x0F:
							sn76489_frequency = (sn76489_frequency & 0x00FFFFFFUL) | ((uint32_t)vgm_buffer_read() << 24);
							break;
						case 0x28:
							sn76489_tapped_bits = (sn76489_tapped_bits & 0xFF00) | vgm_buffer_read();
							break;
						case 0x29:
							sn76489_tapped_bits = (sn76489_tapped_bits & 0x00FF) | (vgm_buffer_read() << 8);
							break;
						case 0x2A:
							sn76489_shift_register_width = vgm_buffer_read();
							break;
						default:
							vgm_buffer_read();
							break;
					}
					break;
				case 0x4F:
					sn76489_stereo_mask = vgm_buffer_read();
					break;
				case 0x50:
					sn76489_write(vgm_buffer_read());
					break;
				case 0x61:
					if (SAMPLE_RATE > 44100) {
						vgm_sample_timer = (vgm_buffer_read() + (256 * vgm_buffer_read())) * (SAMPLE_RATE / 44100);
					} else {
						vgm_sample_timer = (vgm_buffer_read() + (256 * vgm_buffer_read())) / (44100 / SAMPLE_RATE);
					}
					break;
				case 0x62:
					vgm_sample_timer = (SAMPLE_RATE > 44100) ? (735 * (SAMPLE_RATE / 44100)) : (735 / (44100 / SAMPLE_RATE));
					break;
				case 0x63:
					vgm_sample_timer = (SAMPLE_RATE > 44100) ? (882 * (SAMPLE_RATE / 44100)) : (882 / (44100 / SAMPLE_RATE));
					break;
				case 0x66:
					reset = !0;
					break;
				default:
					if (data >= 0x70 && data <= 0x7F) {
						if (SAMPLE_RATE > 44100) {
							vgm_sample_timer = ((data & 0xF) + 1) * (SAMPLE_RATE / 44100);
						} else {
							vgm_sample_timer = ((data & 0xF) + 1) / (44100 / SAMPLE_RATE);
						}
					} else if (data >= 0x30 && data < 0x50) {
						vgm_buffer_read();
					} else if (data >= 0x50 && data < 0xC0) {
						vgm_buffer_read();
						vgm_buffer_read();
					} else if (data >= 0xC0 && data <= 0xE0) {
						vgm_buffer_read();
						vgm_buffer_read();
						vgm_buffer_read();
					} else if (data >= 0xE1) {
						vgm_buffer_read();
						vgm_buffer_read();
						vgm_buffer_read();
						vgm_buffer_read();
					}
					break;
			}
		}
	}

}