#include <UltrasonicSensor.h>
#include <NTPClient.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <ArduinoJson.h>
#include <LiquidCrystal_I2C.h>

QueueHandle_t xFila;
SemaphoreHandle_t  xSerialSemaphore;
SemaphoreHandle_t  xLcdSemaphore;
SemaphoreHandle_t  xPumpTwoSemaphore;

static void Producer( void *pvParameters );
static void Consumer( void *pvParameters );

int tamFila = 1;
int num_mensagens_recebidas_telegram = 0;
String resposta_msg_recebida;

#define PIN_TRIGGER_U1   14
#define PIN_ECHO_U1      12
#define PIN_TRIGGER_U2   26
#define PIN_ECHO_U2      27
#define PIN_RELAY_1      33
#define PIN_RELAY_2      32
#define PIN_LED          15


#define TELEGRAM_DEBUG 1
#define HEIGHT 150
#define FILLED_TURNOFF_THRESHOLD 15
#define EMPTY_HEIGHT_PERCENT_THRESHOLD 90
#define EMPTY_TWO_HEIGHT_PERCENT_THRESHOLD 65
#define EMPTY_TURNOFF_PERCENT_THRESHOLD_FIRST 90
#define EMPTY_HEIGHT_THRESHOLD (HEIGHT*EMPTY_HEIGHT_PERCENT_THRESHOLD)/100
#define EMPTY_TWO_HEIGHT_THRESHOLD (HEIGHT*EMPTY_TWO_HEIGHT_PERCENT_THRESHOLD)/100
#define TURNOFF_THRESHOLD_FIRST (HEIGHT*EMPTY_TURNOFF_PERCENT_THRESHOLD_FIRST)/100

#define CMD_STATUS "STATUS"
#define token_acesso_telegram "5437214532:AAHUb7bKjy6r5PF5E6D0Ty_Phod_Uxnn5mg"
/* Credenciais wi-fi */
#define ssid_wifi "Wokwi-GUEST"      
#define password_wifi ""

WiFiUDP ntpUDP;
const long utcOffsetInSeconds = 0;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);

UltrasonicSensor ultrasonic1(PIN_TRIGGER_U1, PIN_ECHO_U1);
UltrasonicSensor ultrasonic2(PIN_TRIGGER_U2, PIN_ECHO_U2);

LiquidCrystal_I2C lcd(0x27,16,2);

WiFiClientSecure client;
UniversalTelegramBot bot(token_acesso_telegram, client);

int distancePumpOne = 0;
int distancePumpTwo = 0;
int hour = 0;


static void init_wifi(){
   Serial.print("Conectando-se ao Wi-Fi");
   WiFi.begin(ssid_wifi, password_wifi, 6);
   while (WiFi.status() != WL_CONNECTED) {
      delay(100);
      Serial.print(".");
   }
   Serial.println(" Conectado!");
}

static void init_ultrasonics(){
   int temperature = 22;
   ultrasonic1.setTemperature(temperature);
   ultrasonic2.setTemperature(temperature);
}

static void setup_pins(){
   pinMode(PIN_RELAY_1, OUTPUT);
   pinMode(PIN_RELAY_2, OUTPUT);

   pinMode(PIN_LED, OUTPUT);
}

static void init_serial_semaphore(){
   if ( xSerialSemaphore == NULL )
   {
      xSerialSemaphore = xSemaphoreCreateMutex();
      if ( ( xSerialSemaphore ) != NULL )
      xSemaphoreGive(( xSerialSemaphore ));
   }
}

static void init_distance_lcd_semaphore(){
   if ( xLcdSemaphore == NULL )
   {
      xLcdSemaphore = xSemaphoreCreateMutex();
      if ( ( xLcdSemaphore ) != NULL )
      xSemaphoreGive(( xLcdSemaphore ));
   }
}

static void init_distance_pump_two_semaphore(){
   if ( xPumpTwoSemaphore == NULL )
   {
      xPumpTwoSemaphore = xSemaphoreCreateMutex();
      if ( ( xPumpTwoSemaphore ) != NULL )
      xSemaphoreGive(( xPumpTwoSemaphore ));
   }
}

static void init_queue(){
   while(!Serial) ;
   xFila = xQueueCreate( tamFila, sizeof( int ) );
   if(xFila == NULL){
      Serial.println("Erro criando fila");
   }
}

static void init_lcd(){
  lcd.init();                      
  lcd.backlight();
}


void setup() {
   Serial.begin(115200);

   init_wifi();
   init_ultrasonics();
   setup_pins();
   init_serial_semaphore();
   init_queue();
   timeClient.begin();
   init_lcd();


   xTaskCreate( updateDistanceWaterPumpOne, "updateDistanceWaterPumpOne", 1000, NULL, 5, NULL );
   xTaskCreate( updateDistanceWaterPumpTwo, "updateDistanceWaterPumpTwo", 1000, NULL, 5, NULL );
   xTaskCreate( waterPumpOne, "WaterPumpOne", 1000, NULL, 1, NULL );
   xTaskCreate( waterPumpTwo, "WaterPumpTwo", 1000, NULL, 1, NULL );
   xTaskCreate( showActiveLed, "ShowSystemActive", 1000, NULL, 1, NULL );
   xTaskCreate( showStatusWaterPumpOne, "showStatusWaterPumpOne", 50000, NULL, 1, NULL );
   xTaskCreate( showStatusWaterPumpTwo, "showStatusWaterPumpTwo", 50000, NULL, 2, NULL );
   xTaskCreate( updateHour, "updateHour", 10000, NULL, 5, NULL );

   //xTaskCreate( telegramTask, "telegramTask", 1000, NULL, 1, NULL );

}

void loop() {}

static void waterPumpOne( void *pvParameters ){
   portTickType xLastWakeTime;
   xLastWakeTime = xTaskGetTickCount();

   for(;;){
      int distance = distancePumpOne;
      bool inTimeBox = hour >= 11 && hour <= 17; // a hora está em utc, br 8:00 as 14:000

      
      if(inTimeBox && distance > EMPTY_HEIGHT_THRESHOLD ){
         activateRelay(PIN_RELAY_1);
      }
      
      if(distance <= FILLED_TURNOFF_THRESHOLD){
         deactivateRelay(PIN_RELAY_1);
      }

      xQueueSend(xFila, (void *)&distance, portMAX_DELAY);
     
      vTaskDelayUntil( &xLastWakeTime, 2000 / portTICK_RATE_MS);
   }
}

static void waterPumpTwo( void *pvParameters ){
   portTickType xLastWakeTime;
   xLastWakeTime = xTaskGetTickCount();

   
   for(;;){
      
      int distPumpOne;
      xQueueReceive(xFila, (void *)&distPumpOne, portMAX_DELAY);
      
      bool inTimeBox = hour >= 11 && hour <= 17; // a hora está em utc, br 8:00 as 14:000
      
      if(distPumpOne <= FILLED_TURNOFF_THRESHOLD && distancePumpTwo > EMPTY_TWO_HEIGHT_THRESHOLD){
         activateRelay(PIN_RELAY_2);
      }
   
      if(distancePumpOne > EMPTY_HEIGHT_THRESHOLD || distancePumpTwo < FILLED_TURNOFF_THRESHOLD){
         deactivateRelay(PIN_RELAY_2);
      }

      vTaskDelayUntil( &xLastWakeTime, 1000 / portTICK_RATE_MS);
   }
}

static String show_status(int pin_relay){
   if(digitalRead(pin_relay) == HIGH){
      return "ON";
   }
   return "OFF";
}

static void updateDistanceWaterPumpOne( void *pvParameters ){
   portTickType xLastWakeTime;
   xLastWakeTime = xTaskGetTickCount();
   for(;;){
      
      distancePumpOne = ultrasonic1.distanceInCentimeters();
      vTaskDelayUntil( &xLastWakeTime, 1000 / portTICK_RATE_MS);
   } 
}

static void updateDistanceWaterPumpTwo( void *pvParameters ){
   portTickType xLastWakeTime;
   xLastWakeTime = xTaskGetTickCount();
   for(;;){
      
      distancePumpTwo = ultrasonic2.distanceInCentimeters();
      vTaskDelayUntil( &xLastWakeTime, 1000 / portTICK_RATE_MS);
      
   } 
}

static void updateHour( void *pvParameters ){
   portTickType xLastWakeTime;
   xLastWakeTime = xTaskGetTickCount();
   for(;;){
      timeClient.update();
      hour = timeClient.getHours();
      vTaskDelayUntil( &xLastWakeTime, 60000 / portTICK_RATE_MS);
      
   } 
}

static void showStatusWaterPumpOne( void *pvParameters ){
   portTickType xLastWakeTime;
   xLastWakeTime = xTaskGetTickCount();
   
   for(;;){
      if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
      {  
         String message = "P-ONE--"
         + show_status(PIN_RELAY_1) + "-" 
         + get_percentege_full(distancePumpOne)
         + "%";
         lcd.clear();
         lcd.setCursor(0,0);
         lcd.print(message);

         xSemaphoreGive( xSerialSemaphore );
      }
      vTaskDelayUntil( &xLastWakeTime, 1000 / portTICK_RATE_MS);
   }
}

static void showStatusWaterPumpTwo( void *pvParameters ){
      portTickType xLastWakeTime;
      xLastWakeTime = xTaskGetTickCount();
      vTaskDelay(500 / portTICK_RATE_MS);
      for(;;){
      if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
      {
         String message = "P-TWO--"
         + show_status(PIN_RELAY_2) + "-" 
         + get_percentege_full(distancePumpTwo)
         + "%";

         lcd.setCursor(0,1);
         lcd.print(message);
         xSemaphoreGive( xSerialSemaphore );
      }
       vTaskDelayUntil( &xLastWakeTime, 1500 / portTICK_RATE_MS);
   }
}

static String get_percentege_full(int cm){
   int percent = 100 - (cm*100/HEIGHT);
   if(percent < 0){
      return "0";
   }
   if(percent > 100){
      return "100";
   }
   return String(percent);
}


static void deactivateRelay(int pinRelay){
   if(digitalRead(pinRelay) != LOW){
      digitalWrite(pinRelay, LOW);
   }
}

static void activateRelay(int pinRelay){
   
   if(digitalRead(pinRelay) != HIGH ){
      digitalWrite(pinRelay, HIGH);
   }

}

static void showActiveLed( void *pvParameters ){
   portTickType xLastWakeTime;
   xLastWakeTime = xTaskGetTickCount();
   for(;;){
      digitalWrite(PIN_LED, !digitalRead(PIN_LED));
      vTaskDelayUntil( &xLastWakeTime, 1000 / portTICK_RATE_MS);
   }
}

/* Função: conecta-se a rede wi-fi
 * Parametros: nenhum
 * Retorno: nenhum 
 */
void conecta_wifi(void) 
{   
   
    /* Se ja estiver conectado, nada é feito. */
    if (WiFi.status() == WL_CONNECTED)
        return;
 
    /* refaz a conexão */
   WiFi.begin(ssid_wifi, password_wifi, 6);
     
    while (WiFi.status() != WL_CONNECTED) 
    {        
        vTaskDelay( 100 / portTICK_PERIOD_MS );
        Serial.print(".");
    }
   
    Serial.println();
    Serial.print("Conectado com sucesso a rede wi-fi ");
    Serial.println(ssid_wifi);
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
}
 
/* Função: verifica se a conexao wi-fi está ativa 
 *         (e, em caso negativo, refaz a conexao)
 * Parametros: nenhum
 * Retorno: nenhum 
 */
void verifica_conexao_wifi(void)
{
    conecta_wifi(); 
}

/* Função: trata mensagens recebidas via Telegram
 * Parametros: mensagem recebida
 * Retorno: resposta da mensagem recebida 
 */
String trata_mensagem_recebida(String msg_recebida)
{
    String resposta = "";
    bool comando_valido = false;
    float temperatura_lida = 0.0;
    float umidade_lida = 0.0;
 
    /* Faz tratamento da mensagem recebida */
    if (msg_recebida.equals(CMD_STATUS))
    {

        int distancePumpTwo = ultrasonic2.distanceInCentimeters();
        int distancePumpOne = ultrasonic1.distanceInCentimeters();
            
        resposta = "Status das bombas: 1 = "+
                   String(distancePumpOne)+
                   ", 2 = "+
                   String(distancePumpTwo);   
                    
        comando_valido = true;   
    }
 
   
 
    if (comando_valido == false)
        resposta = "Comando invalido: "+msg_recebida;      
  
    return resposta;
}

static void telegramTask( void *pvParameters ){
   portTickType xLastWakeTime;
   xLastWakeTime = xTaskGetTickCount();
   int i; 
   for(;;){
      verifica_conexao_wifi();
      if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
      {
         Serial.println("[BOT] Verificando ");
         xSemaphoreGive( xSerialSemaphore );
         
      }
      /* Verifica se há mensagens a serem recebidas */
        num_mensagens_recebidas_telegram = bot.getUpdates(bot.last_message_received + 1);
 
        //if (num_mensagens_recebidas_telegram > 0)
        //{   
            if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
            {
               Serial.print("[BOT] Mensagens recebidas: ");
               Serial.println(num_mensagens_recebidas_telegram);
               xSemaphoreGive( xSerialSemaphore );
            }
        //}
         
        /* Recebe mensagem a mensagem, faz tratamento e envia resposta */
        while(num_mensagens_recebidas_telegram) 
        {
            for (i=0; i<num_mensagens_recebidas_telegram; i++) 
            {                
                resposta_msg_recebida = "";
                resposta_msg_recebida = trata_mensagem_recebida(bot.messages[i].text);
                bot.sendMessage(bot.messages[i].chat_id, resposta_msg_recebida, "");
            }
             
            num_mensagens_recebidas_telegram = bot.getUpdates(bot.last_message_received + 1);
        }
         
      vTaskDelay( 1500 / portTICK_RATE_MS);
   }
}


esp:VIN
esp:GND.2
esp:D13
esp:D12
esp:D14
esp:D27
esp:D26
esp:D25
esp:D33
esp:D32
esp:D35
esp:D34
esp:VN
esp:VP
esp:EN
esp:3V3
esp:GND.1
esp:D15
esp:D2
esp:D4
esp:RX2
esp:TX2
esp:D5
esp:D18
esp:D19
esp:D21
esp:RX0
esp:TX0
esp:D22
esp:D23
ultrasonic1:VCC
ultrasonic1:TRIG
ultrasonic1:ECHO
ultrasonic1:GND
ultrasonic2:VCC
ultrasonic2:TRIG
ultrasonic2:ECHO
ultrasonic2:GND
relay1:NO2
relay1:NC2
relay1:P2
relay1:COIL2
relay1:NO1
relay1:NC1
relay1:P1
relay1:COIL1
r1:1
r1:2
r2:1
r2:2
r3:1
r3:2
led1:A
led1:C
relay2:NO2
relay2:NC2
relay2:P2
relay2:COIL2
relay2:NO1
relay2:NC1
relay2:P1
relay2:COIL1
led2:A
led2:C
led3:A
led3:C
lcd1:GND
lcd1:VCC
lcd1:SDA
lcd1:SCL