#pragma once

#include <cstdint>
#include <cstdlib>
#include <STM32DMA.hpp>
#include <Timer_Interface.hpp>
#include <UART_Interface.hpp>

namespace STM32UART
{

enum devices : uint8_t
{
	usart1 = 0,
	usart2,
	usart3,
	uart4,
	uart5,
	usart6,
	uart7,
	uart8,
	uart9,
	usart10,
	numUARTs
};

/** DMA Capable UART Class
 *
 * This class depends on DMA for both TX/RX
 * It only supports UART, not USART
 *
 * With the DMA model using a TX/RX operation pairing, the proper ordering of calls is to:
 * - invoke receive(), so that the receive operation is ready for an immediate response once
 * 	the transmission finishes. Note that the timeout should account for the transmit time, plus
 *  any delay between invoking receive() and transmit().
 * - invoke transmit() right afterward
 */
class DMA final : public UART
{
  public:
	struct config
	{
		uint16_t baudrate;
		STM32DMA& tx_channel;
		STM32DMA& rx_channel;
		Timer& timeout_timer;
		uint8_t char_match = 0x0A; // Default to \LF, unless overwritten
	};

	DMA(STM32UART::devices dev_id, STM32UART::DMA::config configuration);
	~DMA() final;

	void start() noexcept final;
	void stop() noexcept final;

	bool started() const
	{
		return started_;
	}

	void transmit(const uint8_t transmit_buffer[], size_t transmit_size) final;

	void transmit(const std::string_view str) final;

	void receive(uint8_t receive_buffer[], size_t receive_size, const UART::cb_t& rx_callback,
				 std::chrono::milliseconds receive_timeout_ms = std::chrono::milliseconds(0)) final;

  private:
	void configureDMA();
	void RxTimeoutHandler();

  private:
	const STM32UART::devices device_id_;
	uint16_t baud_;
	Timer& timeout_timer_;
	STM32DMA& tx_channel_;
	STM32DMA& rx_channel_;
	bool started_ = false;
	UART::cb_t curr_rx_callback_{};
	uint8_t char_match_;
};
}; // namespace STM32UART
