#pragma once

#include <cstdint>
#include <functional>

class I2C
{
  public:
	I2C() = default;
	/// I2C address storage type.
	using addr_t = uint8_t;
	struct op_t;

	enum class operation : uint8_t
	{
		stop = 0, /// Generate a stop condition
		restart, /// Generate a bus restart

		/// Write to slave
		/// Performs the following sequence: start - address - write - stop
		write,

		/// Read from slave
		/// Performs the following sequence: start - address - read - stop
		read,

		/// write to slave and then read from slave
		/// Performs the following sequence: start - address - write - restart - address - read -
		/// stop
		writeRead,

		/// start a write to the slave and but do not issue a stop
		/// Performs the following sequence: start - address - write
		writeNoStop,

		/// continue a write to the slave and do not stop the transaction
		/// Performs the following sequence: write
		continueWriteNoStop,

		/// continue a write to the slave and then stop the transaction
		/// Performs the following sequence: write - stop
		continueWriteStop,

		/// Send only address to see if there is an ACK
		/// Performs the following sequence: start - address
		ping,
	};

	enum class status
	{
		/// The operation completed successfully.
		ok = 0,
		/// The operation was enqueued for later processing.
		enqueued,
		/// The device cannot handle the request at this time; try again.
		busy,
		/// Arbitration Loss - could be real, or could mean you need pullups
		arbitration_loss,
		/// A general bus-related error occurred with this transaction,
		/// such as missing start/stop condition.
		bus_error,
		/// Timeout occurred before the transaction could complete.
		timeout,
		/// An overrun or underrun was detected
		overrun,
		/// A general error, like a DMA error, that is not specifiaclly outlined here.
		error,
		/// The success/failure status of the transaction could not be determined.
		unknown,

		// Additional i2c errors

		/// Addr was transmitted, NACK received.
		addrNACK,

		/// Data was transmitted, NACK received.
		dataNACK,
	};

	using cb_t = std::function<void(I2C::status)>;
	virtual I2C::status transfer(op_t& op, const cb_t& cb = nullptr) = 0;

	/// Perform all actions required to initialize the I2C device and put it in an
	/// operational state.
	/// @pre Device is stopped
	virtual void start() = 0;

	/// Perform all actions required to stop the I2C device.
	/// @pre Device is started
	virtual void stop() = 0;

	struct op_t
	{
		/// Slave device address in 7-bit format
		/// The driver implementation is responsible
		/// for shifting to 8-bit and applying write/read bit as
		/// necessary for the transaction)
		addr_t address{0};

		/// Operation to perform.
		operation op{operation::stop};

		/// Pointer to the transmit buffer.
		/// Does not need to be specified for read-only commands.
		const uint8_t* tx_buffer{nullptr};

		/// Number of bytes to transmit.
		/// Does not need to be specified for read-only commands.
		size_t tx_size{0};

		/// Pointer to the receive buffer.
		/// Does not need to be specified for write-only commands.
		uint8_t* rx_buffer{nullptr};

		/// Number of bytes to receive.
		/// Does not need to be specified for write-only commands.
		size_t rx_size{0};
	};

  protected:
	virtual ~I2C() = default;
};
