/*
 * https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32/api-reference/peripherals/lcd/index.html#functional-overview
 *
 * Implementing the interface draw to an LCD using various interface modes.
 * I2C interface mode is SSD1306
 * SPI interface mode is ST7789
 * I80 interface mode is NT35510 or ST7789
 *
 * Actually, I think any driver can be used with any interface mode
 * Along with additional third party drivers via the component manager
 * https://github.com/espressif/esp-idf/tree/0d6099ec533c4b647fb7a7b0b8942bc7aeb82f90/examples/peripherals/lcd/spi_lcd_touch#spi-lcd-and-touch-panel-example
 */

#ifndef SSD1306_H
#define SSD1306_H

#include <esp_lcd_panel_ssd1306.h>

#include "panel_device.h"

// According to specific display hardware.
// https://www.digikey.com/en/products/detail/winstar-display/WEA012864DWPP3N00003/20533255
#define SCREEN_WIDTH           128  // OLED display width, in pixels.
#define SCREEN_HEIGHT          64  // OLED display height, in pixels.

// According to SSD1306 datasheet.
// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
#define LCD_H_RES              128
#define LCD_V_RES              64
#define I2C_HW_ADDR            0x3C
#define LCD_PIXEL_CLOCK_HZ     (400 * 1000)
// Bit number used to represent command and parameter
#define LCD_CMD_BITS           8
#define LCD_PARAM_BITS         8

/**
 * Example of implementing the IPanelDevice interface for SSD1306 LCD device.
 */
class SSD1306 final : public IPanelDevice {
public:
  /**
   * Construct a new SSD1306 device.
   *
   * @param i2c I2C master bus to manage this device.
   */
  explicit SSD1306(I2C &i2c) :
      SSD1306(i2c, {.height = SCREEN_HEIGHT}) { }

  /**
   * Construct a new SSD1306 device given a specific SSD1306 configuration.
   *
   * @param i2c I2C master bus to manage this device.
   * @param config SSD1306 vendor configuration.
   * @param width Width of the device screen in pixels.
   * @param height Height of the device screen in pixels.
   */
  SSD1306(I2C &i2c,
          esp_lcd_panel_ssd1306_config_t config,
          int width = SCREEN_WIDTH,
          int height = SCREEN_HEIGHT
  ) :
      IPanelDevice(i2c,
                   (esp_lcd_panel_io_i2c_config_t) {
                       .dev_addr = I2C_HW_ADDR,
                       .control_phase_bytes = 1,
                       .dc_bit_offset = 6,
                       .lcd_cmd_bits = LCD_CMD_BITS,
                       .lcd_param_bits = LCD_PARAM_BITS,
                       .scl_speed_hz = LCD_PIXEL_CLOCK_HZ,
                   },
                   width,
                   height
      ),
      ssd1306_config_(config) { }

  ~SSD1306() final = default;

  /**
   * Provides the SSD1306 vendor configuration to IPanelDevice consumers.
   *
   * @return Address of the SSD1306 vendor configuration structure.
   */
  void *vendor_config() override
  {
    return &ssd1306_config_;
  }

  /// SSD1306 configuration structure.
  esp_lcd_panel_ssd1306_config_t ssd1306_config_;

  /**
   * Draw buffer for this panel device.
   * For LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data.
   */
  static uint8_t oled_buffer_[LCD_H_RES * LCD_V_RES / 8];

private:
  /// Initializes the ESP LCD panel handle for the SSD1306 device.
  void init_panel(esp_lcd_panel_dev_config_t &config,
                  esp_lcd_panel_io_handle_t io,
                  esp_lcd_panel_handle_t &panel) override
  {
    ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io, &config, &panel));
  }
};

#endif // SSD1306_H
