// CLIQUE UMA VEZ PARA TROCAR MANUALMENTE O VALOR MOSTRADO NO DISPLAY
// CLIQUE DUAS VEZES NO BOTÃO PARA PAUSAR A TROCA AUTOMÁTICA DE VALORES MEDIDOS

#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/adc.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_timer.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, DISPLAY_ON_CURSOR_OFF);
  lcd_i2c_write(lcd, 0, CURSOR_RIGHT_NO_SHIFT_LEFT);
  lcd_i2c_write(lcd, 0, CLEAR_DISPLAY);
  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 DA BIBLIOTECA DO DHT -------------------------------------------

#define DHT_OK            0
#define DHT_TIMEOUT       1
#define DHT_CHECKSUM_FAIL 2
#define DHT_NOT_READY     3

typedef struct {
  uint8_t gpio;
  uint64_t time;
  uint64_t buffer;
} dht_handle_t;

uint8_t dht_get_raw(dht_handle_t * dht) {
  uint8_t count = 0;
  uint64_t pulse_start;
  uint8_t checksum;

  if (dht->time > esp_timer_get_time()) {
    dht->time = esp_timer_get_time();
  }

  if (esp_timer_get_time() - dht->time > 2e6) {
    dht->buffer = 0;

    gpio_set_direction(dht->gpio, GPIO_MODE_OUTPUT);
    gpio_set_level(dht->gpio, 0);
    vTaskDelay(20 / portTICK_PERIOD_MS);
    gpio_set_level(dht->gpio, 1);
    gpio_set_direction(dht->gpio, GPIO_MODE_INPUT);
    gpio_pullup_en(dht->gpio);

    dht->time = esp_timer_get_time();

    while (count < 41) {
      while (!gpio_get_level(dht->gpio)) {
        if (esp_timer_get_time() - dht->time > 12000) {
          return DHT_TIMEOUT;
        }
      }

      pulse_start = esp_timer_get_time();

      while (gpio_get_level(dht->gpio)) {
        if (esp_timer_get_time() - dht->time > 12000) {
          return DHT_TIMEOUT;
        }
      }

      if (count && (esp_timer_get_time() - pulse_start > 60)) {
        dht->buffer |= (1LL) << (40 - count);
      }

      count++;
    }

    checksum = 0;
    for (uint8_t i = 8; i < 40; i += 8) {
      checksum += (dht->buffer >> i) & 0xFF;
    }

    if (checksum == (dht->buffer & 0xFF)) {
      return DHT_OK;
    }

    else {
      return DHT_CHECKSUM_FAIL;
    }

    dht->time = esp_timer_get_time();
  }

  return DHT_NOT_READY;
}

float dht_get_temperature(dht_handle_t * dht) {
  if ((dht->buffer >> 16) & 0x80) {
    return ((dht->buffer >> 8) & 0x7FFF) / -10.0;
  }

  else {
    return ((dht->buffer >> 8) & 0x7FFF) / 10.0;
  }
}

float dht_get_humidity(dht_handle_t * dht) {
  dht_get_raw(dht);

  return ((dht->buffer >> 24) & 0xFFFF) / 10.0;
}

// FIM DA BIBLIOTECA DO DHT ----------------------------------------------

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

#define ADC_1 1
#define ADC_2 2

typedef struct {
  uint8_t adc_device;
  uint8_t adc_channel;
  float k1;
  float k2;
} ldr_handle_t;

float ldr_get_illumination(ldr_handle_t * ldr) {
  if (ldr->adc_device == ADC_2) {
    int buffer;
    adc2_get_raw(ldr->adc_channel, ADC_WIDTH_BIT_12, &buffer);
    return ldr->k1 + ldr->k2 * log((4096.0 / buffer) - 1);
  }

  else {
    return ldr->k1 + ldr->k2 * log((4096.0 / adc1_get_raw(ldr->adc_channel)) - 1);
  }
}

// FIM DA BIBLIOTECA DO LDR ----------------------------------------------

// INÍCIO DA BIBLIOTECA DO BOTÃO -----------------------------------------

#define SINGLE_CLICK 4
#define DOUBLE_CLICK 7
#define LONG_PRESS 9

typedef struct {
  uint8_t pin;
  uint8_t active_level;
  uint16_t debounce_time;
  uint16_t double_click_timeout;
  uint16_t long_press_time;

  uint8_t state;
  uint64_t time;
} button_handle_t;

void button_begin(button_handle_t * button,
                  uint8_t pin,
                  uint8_t active_level,
                  uint16_t debounce_time,
                  uint16_t double_click_timeout,
                  uint16_t long_press_time) {

  button->pin = pin;
  button->active_level = active_level;
  button->debounce_time = debounce_time;
  button->double_click_timeout = double_click_timeout;
  button->long_press_time = long_press_time;

  gpio_set_direction(button->pin, GPIO_MODE_INPUT);
  if (button->active_level) gpio_set_pull_mode(button->pin, GPIO_PULLDOWN_ONLY);
  else gpio_set_pull_mode(button->pin, GPIO_PULLUP_ONLY);
}

uint8_t button_tick(button_handle_t * button) {
  switch (button->state) {
    case 0:
      if (gpio_get_level(button->pin) == button->active_level) {
        button->time = esp_timer_get_time() / 1000;
        button->state = 1;
      }

      break;

    case 1:
      if (esp_timer_get_time() / 1000 - button->time > button->debounce_time) {
        if (gpio_get_level(button->pin) == button->active_level) {
          button->state = 2;
          button->time = esp_timer_get_time() / 1000;
        }
        else button->state = 0;
      }

      break;
    
    case 2:
      if (gpio_get_level(button->pin) != button->active_level) {
        button->state = 3;
        button->time = esp_timer_get_time() / 1000;
      }

      if (esp_timer_get_time() / 1000 - button->time > button->long_press_time) {
        button->state = 9;
        button->time = esp_timer_get_time() / 1000;
      };

      break;
    
    case 3:
      if ((esp_timer_get_time() / 1000 - button->time > button->debounce_time)) {
        if (gpio_get_level(button->pin) == button->active_level) {
          button->state = 5;
          button->time = esp_timer_get_time() / 1000;
        }
      }

      if (esp_timer_get_time() / 1000 - button->time > button->double_click_timeout) {
        if (gpio_get_level(button->pin) != button->active_level) button->state = 4;
      }

      break;
    
    case 4:
      button->state = 0;

      break;
    
    case 5:
      if (esp_timer_get_time() / 1000 - button->time > button->debounce_time) {
        if (gpio_get_level(button->pin) == button->active_level) button->state = 6;
        else button->state = 0;
      }

      break;
    
    case 6:
      if (gpio_get_level(button->pin) != button->active_level) {
        button->state = 7;
        button->time = esp_timer_get_time() / 1000;
      }

      break;
    
    case 7:
      button->state = 8;

      break;
    
    case 8:
      if ((esp_timer_get_time() / 1000 - button->time > button->debounce_time)) {
        button->state = 0;
      }

      break;
    
    case 9:
      button->state = 10;

      break;
    
    case 10:
      if ((esp_timer_get_time() / 1000 - button->time > button->debounce_time)) {
        if (gpio_get_level(button->pin) != button->active_level) button->state = 0;
      }

      break;

    default:
      break;
  }

  return button->state;
}

// FIM DA BIBLIOTECA DO BOTÃO --------------------------------------------

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

#define BTN_UP 0
#define BTN_DN 1

typedef struct {
    lcd_i2c_handle_t * display;
    dht_handle_t * dht;
    ldr_handle_t * ldr;
    button_handle_t * button;
    SemaphoreHandle_t semaphore;
    uint8_t count;
} task_struct;

void animate_task(void * parameters) {
  task_struct * data = (task_struct *)parameters;

  while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
  lcd_i2c_cursor_set(data->display, 0, 1);
  lcd_i2c_write(data->display, 1, BTN_UP);
  lcd_i2c_cursor_set(data->display, 10, 1);
  lcd_i2c_write(data->display, 1, BTN_UP);
  xSemaphoreGive(data->semaphore);

  while (1) {
    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
    lcd_i2c_cursor_set(data->display, 0, 1);
    lcd_i2c_write(data->display, 1, BTN_UP);
    xSemaphoreGive(data->semaphore);

    vTaskDelay(100 / portTICK_PERIOD_MS);

    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
    lcd_i2c_cursor_set(data->display, 0, 1);
    lcd_i2c_write(data->display, 1, BTN_DN);
    xSemaphoreGive(data->semaphore);

    vTaskDelay(100 / portTICK_PERIOD_MS);

    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
    lcd_i2c_cursor_set(data->display, 0, 1);
    lcd_i2c_write(data->display, 1, BTN_UP);
    xSemaphoreGive(data->semaphore);

    vTaskDelay(1100 / portTICK_PERIOD_MS);

    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
    lcd_i2c_cursor_set(data->display, 10, 1);
    lcd_i2c_write(data->display, 1, BTN_UP);
    xSemaphoreGive(data->semaphore);

    vTaskDelay(100 / portTICK_PERIOD_MS);

    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
    lcd_i2c_cursor_set(data->display, 10, 1);
    lcd_i2c_write(data->display, 1, BTN_DN);
    xSemaphoreGive(data->semaphore);

    vTaskDelay(100 / portTICK_PERIOD_MS);

    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
    lcd_i2c_cursor_set(data->display, 10, 1);
    lcd_i2c_write(data->display, 1, BTN_UP);
    xSemaphoreGive(data->semaphore);

    vTaskDelay(100 / portTICK_PERIOD_MS);

    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
    lcd_i2c_cursor_set(data->display, 10, 1);
    lcd_i2c_write(data->display, 1, BTN_DN);
    xSemaphoreGive(data->semaphore);

    vTaskDelay(100 / portTICK_PERIOD_MS);

    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));
    lcd_i2c_cursor_set(data->display, 10, 1);
    lcd_i2c_write(data->display, 1, BTN_UP);
    xSemaphoreGive(data->semaphore);

    vTaskDelay(1100 / portTICK_PERIOD_MS);
  }
}

void display_task(void * parameters) {
  task_struct * data = (task_struct *)parameters;

  uint8_t last_count = 0;

  while (1) {
    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));

    if (last_count != (data->count & 0x0F)) {
      lcd_i2c_cursor_set(data->display, 0, 0);
      if ((data->count & 0x0F) == 0) lcd_i2c_print(data->display, "Temp");
      else if ((data->count & 0x0F) == 1) lcd_i2c_print(data->display, "Umid");
      else if ((data->count & 0x0F) == 2) lcd_i2c_print(data->display, "Ilum");
      last_count = data->count & 0x0F;
    }

    dht_get_raw(data->dht);
    lcd_i2c_cursor_set(data->display, 5, 0);

    if ((data->count & 0x0F) == 0) {
      lcd_i2c_print(data->display, "%.1f%cC  ", dht_get_temperature(data->dht), 0xDF);
    }

    else if ((data->count & 0x0F) == 1) {
      lcd_i2c_print(data->display, "%.1f%%  ", dht_get_humidity(data->dht));
    }

    else if ((data->count & 0x0F) == 2) {
      lcd_i2c_print(data->display, "%.1f%%  ", ldr_get_illumination(data->ldr));
    }

    xSemaphoreGive(data->semaphore);

    vTaskDelay(500 / portTICK_PERIOD_MS);
  }
}

void changer_task(void * parameters) {
  task_struct * data = (task_struct *)parameters;

  while (1) {
    while (!xSemaphoreTake(data->semaphore, 100 / portTICK_PERIOD_MS));

    if (!(data->count & 0xF0)) {
      data->count++;
      if ((data->count & 0x0F) > 2) data->count &= 0xF0;
    }

    xSemaphoreGive(data->semaphore);
    
    vTaskDelay(10000 / portTICK_PERIOD_MS);
  }
}

void button_task(void * parameters) {
  task_struct * data = (task_struct *)parameters;

  uint8_t command;

  while (1) {
    while (!xSemaphoreTake(data->semaphore, 10 / portTICK_PERIOD_MS));

    command = button_tick(data->button);

    if (command == SINGLE_CLICK) {
      data->count++;
      if ((data->count & 0xF) > 2) data->count &= 0xF0;
    }

    else if (command == DOUBLE_CLICK) {
      data->count ^= (1 << 7);

      if (data->count & 0xF0) {
        lcd_i2c_cursor_set(data->display, 11, 1);
        lcd_i2c_print(data->display, "Segue");
      }

      else {
        lcd_i2c_cursor_set(data->display, 11, 1);
        lcd_i2c_print(data->display, "Pausa");
      }
    }

    xSemaphoreGive(data->semaphore);

    vTaskDelay(20 / portTICK_PERIOD_MS);
  }
}

void i2c_init() {
  i2c_config_t i2c_config = {
      .mode = I2C_MODE_MASTER,
      .sda_io_num = 23,
      .sda_pullup_en = 1,
      .scl_io_num = 22,
      .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 display_start(lcd_i2c_handle_t * display) {
  const char btn_up[8] = {0x00, 0x00, 0x0E, 0x0E, 0x0E, 0x1F, 0x00, 0x00};
  const char btn_dn[8] = {0x00, 0x00, 0x00, 0x00, 0x0E, 0x1F, 0x00, 0x00};

  lcd_i2c_init(display);
  lcd_i2c_custom_char(display, BTN_UP, btn_up);
  lcd_i2c_custom_char(display, BTN_DN, btn_dn);

  lcd_i2c_cursor_set(display, 4, 0);
  lcd_i2c_print(display, ":");

  lcd_i2c_cursor_set(display, 1, 1);
  lcd_i2c_print(display, "Troca");

  lcd_i2c_cursor_set(display, 11, 1);
  lcd_i2c_print(display, "Pausa");
}

void app_main() {
  esp_timer_early_init();

  i2c_init();

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

  display_start(&display);

  dht_handle_t dht22 = {.gpio = 21};

  ldr_handle_t ldr = {
    .adc_device = ADC_1,
    .adc_channel = ADC1_CHANNEL_7,
    .k1 = 49.95095,
    .k2 = 10.34034
  };

  adc1_config_width(ADC_WIDTH_BIT_12);
  adc1_config_channel_atten(ADC1_CHANNEL_7,  ADC_ATTEN_DB_11);

  button_handle_t button = {
    .pin = 2,
    .active_level = 0,
    .debounce_time = 10,
    .double_click_timeout = 300,
    .long_press_time = 750
  };

  task_struct sensor_data = {
    .display = &display,
    .dht = &dht22,
    .ldr = &ldr,
    .button = &button,
    .semaphore = xSemaphoreCreateMutex(),
    .count = 2
  };

  xSemaphoreGive(sensor_data.semaphore);
  
  xTaskCreatePinnedToCore(&animate_task, "Animate", 4096, &sensor_data, 2, NULL, 1);
  xTaskCreatePinnedToCore(&display_task, "Display", 8192, &sensor_data, 2, NULL, 1);
  xTaskCreatePinnedToCore(&changer_task, "Changer", 4096, &sensor_data, 1, NULL, 1);
  xTaskCreatePinnedToCore(&button_task, "Button", 4096, &sensor_data, 3, NULL, 1);

  while (1) vTaskDelay(1000 / portTICK_PERIOD_MS);
}

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