/**
 * \file cimditssd1306.h
 * \brief Declaration of the CimditSSD1306 class
 * \author GrumpyDeveloper (Sascha Nitsch)
 * \copyright 2022 Sascha Nitsch
 * Licensed under MIT license
 *
 */

#ifndef CIMDITSSD1306_H_
#define CIMDITSSD1306_H_

// system includes
#include <Wire.h>

/// memory adressing mode
#define SSD1306_SET_MEMORY_ADDRESSING_MODE 0x20
/// column address start and end
#define SSD1306_SET_COLUMN_ADDRESS 0x21
/// page start and end address
#define SSD1306_SET_PAGE_ADDRESS 0x22
/// deactivate scrolling
#define SSD1306_DEACTIVATE_SCROLL 0x2E
/// set display start line to 0
#define SSD1306_SET_DISPLAY_START_LINE_0 0x40
/// set contrast
#define SSD1306_SET_CONTRAST_CONTROL 0x81
/// charge pump setting
#define SSD1306_CHARGE_PUMP_SETTING 0x8D
/// set segment re-map to column 127
#define SSD1306_SET_SEGMENT_RE_MAP_127 0xA1
/// entire display on
#define SSD1306_ENTIRE_DISPLAY_ON 0xA4
/// set normal display
#define SSD1306_SET_NORMAL_DISPLAY 0xA6
/// set multiplex ratio
#define SSD1306_SET_MULTIPLEX_RATIO 0xA8
/// set display off
#define SSD1306_SET_DISPLAY_OFF 0xAE
/// set display on
#define SSD1306_SET_DISPLAY_ON 0xAF
/// set COM output scan direction to remapped
#define SSD1306_SET_COM_OUTPUT_SCAN_DIRECTION_REMAPPED 0xC8
/// set display offser
#define SSD1306_SET_DISPLAY_OFFSET 0xD3
/// set display clock divide ratio
#define SSD1306_SET_DISPLAY_CLOCK_DIVIDE_RATIO 0xD5
// set pre-charge period
#define SSD1306_SET_PRE_CHARGE_PERIOD 0xD9
// set com pins hardware configuration
#define SSD1306_SET_COM_PINS_HARDWARE_CONFIGURATION 0xDA
// set VCOM deselect level
#define SSD1306_SET_VCOM_DESELECT_LEVEL 0xDB

/**
 * \brief Simple drawing class for SSD1306 OLED displays
 */
class CimditSSD1306 : public Print {
 public:
  /**
   * \brief constructor
   * \param w width of display in pixel
   * \param h height of display in pixel
   * \param i2cAddr i2c address
   */
  CimditSSD1306(uint8_t w, uint8_t h, uint8_t i2cAddr);

  /**
   * \brief destructor
   *
   */
  ~CimditSSD1306();

  /**
   * \brief initialize library, needs to be called before any other graphic functions
   * \return true on success
   */
  bool begin();

  /**
   * \brief send internal buffer to display
   *
   */
  void display(void);

  /**
   * \brief send part of internal buffer to display
   * \param part which part of screen (each part is 8 pixel high, starting from top at 0
   */
  void displayPartial(uint8_t part);

  /**
   * \brief clear display content in internal buffer, call display or displayPartial to take effect
   *
   */
  void clearDisplay(void);
  
  /**
   * \brief set a pixel to on or off
   * \param x x coordinate of pixel
   * \param y y coordinate of pixel
   * \param state true for on, false for off
   */
  void drawPixel(uint8_t x, uint8_t y, bool state);

  /**
   * \brief draw a horizontal line
   * \param x start x position
   * \param y start y position
   * \param w width in pixel
   * \param state true for on, false for off
   */
  void drawFastHLine(uint8_t x, uint8_t y, uint8_t w, bool state);

  /**
   * \brief Draw a vertical line with a width and state.
   *        Functionality based on adafruit GFX library
   * \param x x coordinate
   * \param y top y coordinate
   * \param h height of the line in pixels
   * \param state true for pixel on, false for off
   * \note  Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display() or displayPartial()
  */
  void drawFastVLine(uint8_t x, uint8_t y, uint8_t h, bool state);

  /**
   * \brief draw a filled rectangle
   *
   * \param x x start position
   * \param y y start position
   * \param w width in pixel
   * \param h height in pixel
   * \param state
   */
  void fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, bool state);

  /**
   * \brief set text scaling factor
   *
   * \param sx X-scaling factor
   * \param sy Y-scaling factor
   */
  void setTextSize(uint8_t sx, uint8_t sy);

  using Print::write;
  /**
   * \brief print a single character to screen
   *
   * \param c character to print
   * \return 1
   */
  size_t write(uint8_t c);

  /**
   * \brief set text cursor position
   * \param x X coordinate
   * \param y Y coordinate
   */
  void setCursor(uint8_t x, uint8_t y);

  /**
   * \brief set text state
   * \param state state to output
   */
  void setTextState(bool state);

 private:
  /**
   * \brief draw a single character c at x,y with scaling sX, sY
   * \param x left coordinate of character
   * \param y lower coordinate of character
   * \param c character to print
   * \param sX horizontal scaling factor
   * \param sY veritcal scaling factor
   */
  void drawChar(uint8_t x, uint8_t y, unsigned char c, uint8_t size_x, uint8_t size_y);

  /**
   * \brief start command transmission
   *
   */
  void commandStart();

  /**
   * \brief send command list from program memory
   * \param c start pointer
   * \param n number of items to output
   */
  void commandListProgMem(const uint8_t *c, uint8_t n);

  /**
   * \brief finish command sending
   */
  void commandEnd();

  /// display width
  uint8_t m_width;

  /// display height
  uint8_t m_height;
 
  /// text cursor position X
  uint8_t m_cursorX;

  /// text cursor position Y
  uint8_t m_cursorY;

  /// text drawing state
  bool m_state;

  /// horizontal text scaling factor
  uint8_t m_textsizeX;
 
  /// vertical text scaling factor
  uint8_t m_textsizeY;
 
  /// internal buffer for screen pixel data
  uint8_t *m_buffer;
 
  /// i2c address of display
  uint8_t m_i2cAddr;
};

#endif  // CIMDITSSD1306_H_
