#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "rom/ets_sys.h"

// INÍCIO DA BIBLIOTECA DO LCD -------------------------------------------

// PINOS DO PCF8475
#define RS 0
#define RW 1
#define EN 2
#define BL 3

// MEDIDAS DOS DISPLAYS
#define DISPLAY_16X02 0
#define DISPLAY_20X04 1

// INSTRUÇÕES DO DISPLAY LCD
#define CLEAR_DISPLAY               0x01
#define RETURN_HOME_UNSHIFT         0x02
#define CURSOR_RIGHT_NO_SHIFT       0x04
#define CURSOR_RIGHT_SHIFT          0x05
#define CURSOR_RIGHT_NO_SHIFT_LEFT  0x06
#define CURSOR_RIGHT_SHIFT_LEFT     0x07
#define DISPLAY_OFF                 0x08
#define DISPLAY_ON_CURSOR_OFF       0x0C
#define DISPLAY_ON_CURSOR_ON_STEADY 0x0E
#define DISPLAY_ON_CURSOR_ON_BLINK  0x0F
#define RETURN_HOME                 0x80
#define SHIFT_CURSOR_LEFT           0x10
#define SHIFT_CURSOR_RIGHT          0x14
#define SHIFT_DISPLAY_LEFT          0x18
#define SHIFT_DISPLAY_RIGHT         0x1C
#define SET_4BIT_MODE               0x28

typedef struct {
  uint8_t address;
  uint8_t num;
  uint8_t backlight;
  uint8_t size;
} lcd_i2c_handle_t;

void i2c_write_byte(lcd_i2c_handle_t * lcd, uint8_t data) {
  i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
  i2c_master_start(i2c_cmd);
  i2c_master_write_byte(i2c_cmd, (lcd->address << 1) | I2C_MASTER_WRITE, 1);
  i2c_master_write_byte(i2c_cmd, data, 1);
  i2c_master_stop(i2c_cmd);
  i2c_master_cmd_begin(lcd->num, i2c_cmd, 10 / portTICK_PERIOD_MS);
  i2c_cmd_link_delete(i2c_cmd);
}

void lcd_i2c_write(lcd_i2c_handle_t * lcd, char rs_flag, char data_byte) {
  uint8_t buffer_byte = ((1 << RS) * rs_flag) | ((1 << BL) * lcd->backlight);

  buffer_byte |= (buffer_byte & 0x0F) | (0xF0 & data_byte);
  buffer_byte |= (1 << EN);
  i2c_write_byte(lcd, buffer_byte);
  ets_delay_us(10);
  buffer_byte &= ~(1 << EN);
  i2c_write_byte(lcd, buffer_byte);
  ets_delay_us(50);

  buffer_byte = (buffer_byte & 0x0F) | (data_byte << 4);
  buffer_byte |= (1 << EN);
  i2c_write_byte(lcd, buffer_byte);
  ets_delay_us(10);
  buffer_byte &= ~(1 << EN);
  i2c_write_byte(lcd, buffer_byte);
  ets_delay_us(50);

  if (data_byte == CLEAR_DISPLAY) {
    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

void lcd_i2c_init(lcd_i2c_handle_t * lcd) {
  vTaskDelay(10 / portTICK_PERIOD_MS);
  lcd_i2c_write(lcd, 0, RETURN_HOME_UNSHIFT);
  lcd_i2c_write(lcd, 0, SET_4BIT_MODE);
  lcd_i2c_write(lcd, 0, CLEAR_DISPLAY);
  lcd_i2c_write(lcd, 0, DISPLAY_ON_CURSOR_OFF);
  lcd_i2c_write(lcd, 0, CURSOR_RIGHT_NO_SHIFT_LEFT);
  vTaskDelay(10 / portTICK_PERIOD_MS);
}

void lcd_i2c_cursor_set(lcd_i2c_handle_t * lcd, uint8_t column, uint8_t row) {
  if (lcd->size == DISPLAY_16X02) {
    if (row) lcd_i2c_write(lcd, 0, 0x80 + 0x40 + column);
    else lcd_i2c_write(lcd, 0, 0x80 + column);
  }

  else if (lcd->size == DISPLAY_20X04) {
    switch (row) {
      case 0:
        lcd_i2c_write(lcd, 0, 0x80 + column);
        break;

      case 1:
        lcd_i2c_write(lcd, 0, 0x80 + 0x40 + column);
        break;

      case 2:
        lcd_i2c_write(lcd, 0, 0x80 + 0x14 + column);
        break;

      case 3:
        lcd_i2c_write(lcd, 0, 0x80 + 0x54 + column);
        break;

      default:
        break;
    }
  }
}

void lcd_i2c_custom_char(lcd_i2c_handle_t * lcd, char char_address, const char * pixels) {
  lcd_i2c_write(lcd, 0, 0x40 | (char_address << 3));

  for (uint8_t i = 0; i < 8; i++) {
    lcd_i2c_write(lcd, 1, pixels[i]);
  }

  lcd_i2c_write(lcd, 0, RETURN_HOME);
}

void lcd_i2c_print(lcd_i2c_handle_t * lcd, const char * format_string, ...) {
  uint16_t i = 0;
  char buffer_string[128];

  va_list arguments;
  va_start(arguments, format_string);
  vsnprintf(buffer_string, sizeof(buffer_string), format_string, arguments);
  va_end(arguments);

  while (buffer_string[i] != '\0') {
    lcd_i2c_write(lcd, 1, buffer_string[i]);
    i++;
  }
}

// FIM DA BIBLIOTECA DO LCD ----------------------------------------------

// INÍCIO DO CÓDIGO ESPECÍFICO DO PROJETO --------------------------------

// Endereços de caracteres especiais
#define SKULL1       1 // Boca aberta
#define SKULL2       2 // Boca fechada
#define DROP         3
#define A_COM_ACENTO 4 // á
#define SMILE        5

void i2c_init() {
  i2c_config_t i2c_config = {
      .mode = I2C_MODE_MASTER,
      .sda_io_num = 22,
      .sda_pullup_en = 1,
      .scl_io_num = 21,
      .scl_pullup_en = 1,
      .master.clk_speed = 100000,
    };

    i2c_param_config(I2C_NUM_1, &i2c_config);

    i2c_driver_install(I2C_NUM_1, I2C_MODE_MASTER, 0, 0, 0);
}

void app_main() {
  i2c_init();

  lcd_i2c_handle_t display = {
    .address = 0x27,
    .num = I2C_NUM_1,
    .backlight = 1,
    .size = DISPLAY_20X04
  };

  // Declaração dos bytes dos caracteres especiais
  char smile[8] = {0x00, 0x0A, 0x0A, 0x00, 0x11, 0x0E, 0x00, 0x00};
  char skull1[8] = {0x0E, 0x15, 0x15, 0x1F, 0x0E, 0x11, 0x0E, 0x00};
  char skull2[8] = {0x0E, 0x15, 0x15, 0x1F, 0x1F, 0x0E, 0x00, 0x00};
  char drop[8] = {0x04, 0x04, 0x0E, 0x0E, 0x1F, 0x1F, 0x1F, 0x0E};
  char a_com_acento[8] = {0x02, 0x04, 0x0E, 0x01, 0x0F, 0x11, 0x0F, 0x00};

  // Inicializa o display
  lcd_i2c_init(&display);

  // Envia os caracteres especiais para o display
  lcd_i2c_custom_char(&display, SMILE, smile);
  lcd_i2c_custom_char(&display, SKULL1, skull1);
  lcd_i2c_custom_char(&display, SKULL2, skull2);
  lcd_i2c_custom_char(&display, DROP, drop);
  lcd_i2c_custom_char(&display, A_COM_ACENTO, a_com_acento);

  int8_t count_drop = 0;
  int8_t count_skull = 0;
  int8_t count_hello = 0;
  int8_t toggle_hello = 1;

  while (1) {
    if (!count_drop) lcd_i2c_cursor_set(&display, 0, 3);
    else lcd_i2c_cursor_set(&display, 0, count_drop - 1);
    lcd_i2c_print(&display, " ");
    lcd_i2c_cursor_set(&display, 0, count_drop);
    lcd_i2c_print(&display, "%c", DROP);
    count_drop++;
    if (count_drop > 3) count_drop = 0;

    if (count_skull <= 1) lcd_i2c_cursor_set(&display, 19, 3);
    else lcd_i2c_cursor_set(&display, 19, (count_skull / 2) - 1);
    lcd_i2c_print(&display, " ");
    lcd_i2c_cursor_set(&display, 19, count_skull / 2);
    if (count_skull % 2) lcd_i2c_print(&display, "%c", SKULL1);
    else lcd_i2c_print(&display, "%c", SKULL2);
    count_skull++;
    if (count_skull > 7) count_skull = 0;

    lcd_i2c_cursor_set(&display, 4, count_hello / 4);
    if (toggle_hello) lcd_i2c_print(&display, "Ol%c, Wokwi%c!", A_COM_ACENTO, SMILE);
    else lcd_i2c_print(&display, "            ");
    count_hello++;
    if (count_hello > 15) {
      count_hello = 0;
      toggle_hello = !toggle_hello;
    }

    vTaskDelay(250 / portTICK_PERIOD_MS);
  }
}

// FIM DO CÓDIGO ESPECÍFICO DO PROJETO -----------------------------------