#pragma once
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>

#include "esphome/core/helpers.h"

namespace esphome::i2c {

/// @brief Error codes returned by I2CBus and I2CDevice methods
enum ErrorCode {
  NO_ERROR = 0,                ///< No error found during execution of method
  ERROR_OK = 0,                ///< No error found during execution of method
  ERROR_INVALID_ARGUMENT = 1,  ///< method called invalid argument(s)
  ERROR_NOT_ACKNOWLEDGED = 2,  ///< I2C bus acknowledgment not received
  ERROR_TIMEOUT = 3,           ///< timeout while waiting to receive bytes
  ERROR_NOT_INITIALIZED = 4,   ///< call method to a not initialized bus
  ERROR_TOO_LARGE = 5,         ///< requested a transfer larger than buffers can hold
  ERROR_UNKNOWN = 6,           ///< miscellaneous I2C error during execution
  ERROR_CRC = 7,               ///< bytes received with a CRC error
};

/// @brief This Class provides the methods to read and write bytes from an I2CBus.
/// @note The I2CBus virtual class follows a *Factory design pattern* that provides all the interfaces methods required
/// by clients while deferring the actual implementation of these methods to a subclasses. I2C-bus specification and
/// user manual can be found here https://www.nxp.com/docs/en/user-guide/UM10204.pdf and an interesting I²C Application
/// note https://www.nxp.com/docs/en/application-note/AN10216.pdf
class I2CBus {
 public:
  virtual ~I2CBus() = default;

  /// @brief This virtual method writes bytes to an I2CBus from an array,
  /// then reads bytes into an array of ReadBuffer.
  /// @param address address of the I²C device on the i2c bus
  /// @param write_buffer pointer to data
  /// @param write_count number of bytes to write
  /// @param read_buffer pointer to an array to receive data
  /// @param read_count number of bytes to read
  /// transmission. False will send a restart, keeping the connection active.
  /// @return an i2c::ErrorCode
  /// @details This is a pure virtual method that must be implemented in the subclass.
  virtual ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
                                size_t read_count) = 0;

  // Legacy functions for compatibility

  ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) {
    return this->write_readv(address, nullptr, 0, buffer, len);
  }

  ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop = true) {
    return this->write_readv(address, buffer, len, nullptr, 0);
  }

 protected:
  /// @brief Scans the I2C bus for devices. Devices presence is kept in an array of std::pair
  /// that contains the address and the corresponding bool presence flag.
  void i2c_scan_();
  std::vector<std::pair<uint8_t, bool>> scan_results_;  ///< array containing scan results
  bool scan_{false};                                    ///< Should we scan ? Can be set in the yaml
};

class InternalI2CBus : public I2CBus {
 public:
  /// @brief Returns the I2C port number.
  /// @return the port number of the internal I2C bus
  virtual int get_port() const = 0;
};

}  // namespace esphome::i2c
