/*
 * interrupt.h
 *
 *  Created on: Aug 29, 2019
 *      Author: abody
 */

#ifndef CONSOLEHANDLER_H_
#define CONSOLEHANDLER_H_

#include <cstring>
#include "usart.h"
#include <f4ll_cpp/dmahelper.h>
#include <f4ll_cpp/uartbase.h>


namespace f4ll_cpp {

template<size_t bufSize> class SerialConsole : protected UartBase
{
public:
	struct Buffer {
		volatile bool busy = false;
		volatile bool error = false;
		uint8_t len;
		char buffer[bufSize];
	};

	struct ISerialConsoleCallback {
		virtual void LineReceived(void *userParam, Buffer *buffer) = 0;
		virtual void TransmissionComplete(void *userParam, Buffer *buffer) = 0;
	};

	SerialConsole(USART_TypeDef *uart, DMA_TypeDef *dma, uint32_t stream_rx, uint32_t stream_tx,
			ISerialConsoleCallback *callback, void *callbackUserParam);

	void Send(char const *buffer, uint8_t length = 0);
	void SendLine(char const *buffer, uint8_t length = 0);

	void HandleRxDmaIrq();
	void HandleTxDmaIrq();
	void HandleUsartIrq();

private:
	void SetupTransmit(void *buffer, uint16_t length);

	bool m_activeRxBuffer = false;
	Buffer m_rxBuffers[2];
	Buffer m_txBuffer;

	ISerialConsoleCallback *m_callback;
	void *m_callbackUserParam;
};

template<size_t bufSize> SerialConsole<bufSize>::SerialConsole(
		USART_TypeDef *uart, DMA_TypeDef *dma, uint32_t stream_rx, uint32_t stream_tx,
		ISerialConsoleCallback *callback, void *callbackUserParam
		)
		: UartBase(uart, dma, stream_rx, stream_tx)
		, m_callback(callback), m_callbackUserParam(callbackUserParam)
{
	LL_USART_EnableIT_IDLE(uart);
	SetupReceive(m_rxBuffers[m_activeRxBuffer].buffer, bufSize);
}

template<size_t bufSize> void SerialConsole<bufSize>::SetupTransmit(void *buffer, uint16_t length)
{
	m_txBuffer.busy = true;
	UartBase::SetupTransmit(buffer, length);
}

template<size_t bufSize> void SerialConsole<bufSize>::HandleRxDmaIrq()
{
	if(*m_rxDma.GetIsReg() & m_rxDma.GetTcMask()) {
		*m_rxDma.GetIfcReg() = m_rxDma.GetTcMask();
		m_rxBuffers[m_activeRxBuffer].busy = true;
		if(m_callback)
			m_callback->LineReceived(m_callbackUserParam, &m_rxBuffers[m_activeRxBuffer]);
	}

	if(*m_rxDma.GetIsReg() & m_rxDma.GetTeMask()) {
		*m_rxDma.GetIfcReg() = m_rxDma.GetTeMask();
	}

	m_activeRxBuffer = !m_activeRxBuffer;

	SetupReceive(m_rxBuffers[m_activeRxBuffer].buffer, bufSize);
}

template<size_t bufSize> void SerialConsole<bufSize>::HandleTxDmaIrq()
{
	if(*m_txDma.GetIsReg() & m_txDma.GetTcMask()) {	// DMA transfer complete
		*m_txDma.GetIfcReg() = m_txDma.GetTcMask();
		LL_USART_EnableIT_TC(m_uart);
		LL_DMA_DisableStream(m_txDma.GetDma(), m_txDma.GetStream());
	}
	if(*m_txDma.GetIsReg() & m_txDma.GetTeMask()) {
		*m_txDma.GetIfcReg() = m_txDma.GetTeMask();
		m_txBuffer.error = true;
		LL_USART_EnableIT_TC(m_uart);
		LL_DMA_DisableStream(m_txDma.GetDma(), m_txDma.GetStream());
	}
}

template<size_t bufSize> void SerialConsole<bufSize>::HandleUsartIrq()
{
	if(LL_USART_IsActiveFlag_IDLE(m_uart) && LL_USART_IsEnabledIT_IDLE(m_uart)) {	//	receiver idle
		// we assume that new line marker will arrive without an idle cycle even if it is CRLF
		LL_USART_ClearFlag_IDLE(m_uart);
		uint16_t rcvdLen = bufSize - LL_DMA_GetDataLength(m_rxDma.GetDma(), m_rxDma.GetStream());
		if(rcvdLen ) {
			bool newLine = false;;
			do {
				auto lastChar = m_rxBuffers[m_activeRxBuffer].buffer[rcvdLen-1];
				if( lastChar == '\r' || lastChar == '\n')
					newLine = true;
				else
					break;
			} while(--rcvdLen);

			if(newLine) {
				m_rxBuffers[m_activeRxBuffer].buffer[rcvdLen] = 0;
				m_rxBuffers[m_activeRxBuffer].len = rcvdLen;
				LL_DMA_DisableStream(m_rxDma.GetDma(), m_rxDma.GetStream());
			}
		}
	}

	if(LL_USART_IsActiveFlag_TC(m_uart) && LL_USART_IsEnabledIT_TC(m_uart)) {	// transmission complete
		LL_USART_DisableIT_TC(m_uart);
		LL_USART_DisableDirectionTx(m_uart);	// enforcing an idle frame
		LL_USART_EnableDirectionTx(m_uart);
		m_txBuffer.busy = false;
		if(m_callback)
			m_callback->TransmissionComplete(m_callbackUserParam, &m_txBuffer);
	}
}

template<size_t bufSize> void SerialConsole<bufSize>::Send(char const *buffer, uint8_t length)
{
	if(!length) {
		auto computedLength = strlen(buffer);
		if(computedLength <= (uint8_t)-1)
			length = computedLength;
	}
	if(length) {
		while( m_txBuffer.busy );
		memcpy(m_txBuffer.buffer, buffer, length);
		SetupTransmit(m_txBuffer.buffer, length);
	}
}

template<size_t bufSize> void SerialConsole<bufSize>::SendLine(char const *buffer, uint8_t length)
{
	if(!length) {
		auto computedLength = strlen(buffer);
		if(computedLength <= (uint8_t)-1)
			length = computedLength;
	}
	if(length) {
		while( m_txBuffer.busy );
		memcpy(m_txBuffer.buffer, buffer, length);
		if(m_txBuffer.buffer[length-1] != '\n') {
			m_txBuffer.buffer[length++] = '\r';
			m_txBuffer.buffer[length++] = '\n';
		}
		SetupTransmit(m_txBuffer.buffer, length);
	}
}

}	//	f4ll_cpp

#endif /* CONSOLEHANDLER_H_ */
