#pragma once

#include <I2C_Interface.hpp>
#include <cstdint>
#include <cstdlib>
#include <STM32DMA.hpp>
#include <Timer_Interface.hpp>
#include "STM32_I2C_Definitions.hpp"

/** DMA-Driven I2C Master Implementation
 *
 * The current implementation does not support SMBus.
 *
 * Note that this class is implemented using DMA, so you need to create
 * DMA instances for the tx_channel and rx_channel in order to use this driver.
 *
 * @code
 * STM32DMA dma_ch_i2c_tx{STM32DMA::device::dma1, STM32DMA::channel::CH2};
 * STM32DMA dma_ch_i2c_rx{STM32DMA::device::dma1, STM32DMA::channel::CH3};
 * STM32I2C_DMA i2c2{STM32I2C_DMA::device::i2c2, dma_ch_i2c_tx, dma_ch_i2c_rx};
 * @endcode
 *
 * The I2C driver will handle its specific configuration, address assignment, interrupt handlers,
 * and starting/stopping of the driver internally. You must, however, enable the appropriate
 * DMA device clock in the hardware platform; the I2C driver will not handle that.
 *
 * @see STM32DMA
 */
class STM32I2C_DMA final : public I2C
{
  public:

  public:
	explicit STM32I2C_DMA(STM32I2C::device dev, STM32DMA& tx_channel,
							STM32DMA& rx_channel, STM32I2C::baud baudrate = STM32I2C::baud::standard,
							STM32I2C::pullups pull = STM32I2C::pullups::external) noexcept
		: device_(dev), tx_channel_(tx_channel), rx_channel_(rx_channel),
		pull_setting_(pull), baudrate_(baudrate)
	{
	}
	~STM32I2C_DMA() noexcept = default;

	/// Start the device, turning it on and configuring it
	/// @pre The device is stopped
	void start() noexcept final;

	/// Stop the device, turning it off and de-initializing it
	/// @pre The device is started
	void stop() noexcept final;

	/// Configure the device to use internal or external pullups.
	/// By default, the driver assumes external pullups
	/// @pre The driver is stopped
	///		The changes are only applied during start(), so this function
	///		can only be invoked when the driver is stopped.
	void setPullups(STM32I2C::pullups setting) noexcept;

	/// Set the baudrate for the I2C bus.
	///
	/// @param in baud The requested baudrate.
	/// @pre The driver is stopped
	///		The changes are only applied during start(), so this function
	///		can only be invoked when the driver is stopped.
	void baudrate(STM32I2C::baud baud) noexcept;

	I2C::status transfer(I2C::op_t& op,
						 const I2C::cb_t& cb) noexcept final;

private:
	void configureDMA() noexcept;
	void enableInterrupts() noexcept;
	void disableInterrupts() noexcept;

  private:
	const STM32I2C::device device_;
	STM32DMA& tx_channel_;
	STM32DMA& rx_channel_;
	bool started_ = false;
	bool busy_ = false;
	I2C::op_t active_op_{};
	STM32I2C::pullups pull_setting_ = STM32I2C::pullups::external;
	STM32I2C::baud baudrate_ = STM32I2C::baud::standard;
};
