// intellectual property is bullshit bgdc

#ifndef MIDIX_HPP
#define MIDIX_HPP

#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <assert.h>

#include <etl/queue.h>
#include <avrstuff/serial.hpp>
#include "midi.hpp"

#define MIDIX_SYSEX_LEN 64

// MIDI transceiver
class midix {
public:
	midix(serial & serport, etl::iqueue<struct midi_message> & mq)
		: m_serport(serport)
		, m_sysex_buf_avail(true)
		, m_message_pos(0)
		, m_receiving_sysex(false)
		, m_queue(mq)
	{
		m_message.source  = this;
		m_message.command = MIDI_ICMD_NOP;
	}

	// Receive Complete interrupt. Call within the serial port's RXC ISR.
	void rxc()
	{
		int c = m_serport.recv();

		if (c < 0)
		{
			m_message.command = MIDI_ICMD_NOP;
			m_message_pos = 0;
			if (m_receiving_sysex)
			{
				m_receiving_sysex = false;
				m_sysex_buf_avail = true;
			}
		}
		else if (m_receiving_sysex)
		{
			if (c == MIDI_CMD_END_SYSEX)
			{
				if (m_message_pos < MIDIX_SYSEX_LEN)
					m_sysex_buf[m_message_pos] = 0;
				else
					m_sysex_buf[MIDIX_SYSEX_LEN - 1] = 0;

				send();
			}
			else if (m_message_pos < MIDIX_SYSEX_LEN)
				m_sysex_buf[m_message_pos++] = c;
		}
		else if (c == MIDI_CMD_START_SYSEX)
		{
			if (m_sysex_buf_avail)
			{
				m_message.command = MIDI_ICMD_SYSEX;
				m_message.content = m_sysex_buf;
				m_message_pos = 0;
				m_receiving_sysex = true;
				m_sysex_buf_avail = false;
			}
			else
			{
				m_message.command = MIDI_ICMD_SYSEX_OVERFLOW;
				send();
			}
		}
		else if (c >= 0x80)
		{
			m_message.command = (uint8_t) c;
			m_message_pos = 1;
		}
		else
		{
			if (m_message_pos <= sizeof(m_message.arguments))
			{
				m_message.arguments[m_message_pos - 1] = c;
				m_message_pos++;
			}

			if (m_message.command &&
				m_message_pos == midi_num_args(m_message.command) + 1)
			{
				send();
			}
		}
	}

	// Release the sysex buffer
	void sysex_release_buffer(uint8_t * buf)
	{
		assert(!m_sysex_buf_avail);
		assert(buf == &m_sysex_buf[0]);
		buf[0] = 0;
		m_sysex_buf_avail = true;
	}

private:
	// Send the current message and initialize it for the next one
	void send()
	{
		m_queue.push(m_message);
		m_message_pos = 0;
		m_message.command = MIDI_ICMD_NOP;
		m_receiving_sysex = false;
	}

	serial & m_serport;
	uint8_t  m_sysex_buf[MIDIX_SYSEX_LEN];
	bool     m_sysex_buf_avail;

	struct midi_message m_message;
	size_t              m_message_pos;
	bool                m_receiving_sysex;
	etl::iqueue<struct midi_message> & m_queue;
};

#endif // !defined(MIDIX_HPP)
