#include <WiFi.h> // Pour se connecter au réseau wifi
    #include "esp_camera.h" // Pour capturer une image
    #include <esp_http_client.h> // Pour envoyer la photo au serveur
    #include "soc/sens_reg.h" // Pour la ruse ADC2

    // Les valeurs du cycle d'arrosage 
    #define NB_CYCLE_WATERING 1
    #define TIME_BTWN_WATERING 5000
    #define WATERING_TIME 3000

    // Le temps d'attente avant de lire de nouveau
    // le capteur d'humidité (1h en millisecondes)
    #define SLEEPING_TIME (3600*1000)

    // Les valeurs seuils du capteur d'humidité
    #define MAX_MOIST_VAL 2000
    #define MIN_MOIST_VAL 3000

    // Les pins de l'ESP-WROOM-32 pour la caméra OV2640 du modèle AI THINKER
    #define PWDN_GPIO_NUM     32
    #define RESET_GPIO_NUM    -1
    #define XCLK_GPIO_NUM      0
    #define SIOD_GPIO_NUM     26
    #define SIOC_GPIO_NUM     27
    #define Y9_GPIO_NUM       35
    #define Y8_GPIO_NUM       34
    #define Y7_GPIO_NUM       39
    #define Y6_GPIO_NUM       36
    #define Y5_GPIO_NUM       21
    #define Y4_GPIO_NUM       19
    #define Y3_GPIO_NUM       18
    #define Y2_GPIO_NUM        5
    #define VSYNC_GPIO_NUM    25
    #define HREF_GPIO_NUM     23
    #define PCLK_GPIO_NUM     22


    camera_fb_t * fb = NULL; // Camera buffer
    uint64_t reg_b; // valeur du registre pour analogread()

    // Les broches gpio
    int moist_pin = 14;
    int relay_pin = 2;
    int led_pin = 4;

    // Les états des broches on l'on écrit
    int relay_state = LOW;
    int led_state = LOW;

    // Le réseau wifi
    const char* ssid = "le_reseau_local";
    const char* password = "le_mot_de_passe_local";

    // AnalogRead alternatif
    int alt_analogRead(int pin) {
    WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, reg_b);
    SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
    return analogRead(pin);
    }

    // Fonction pour amorcer la caméra
    void init_camera() {
      camera_config_t config;
      config.ledc_channel = LEDC_CHANNEL_0;
      config.ledc_timer = LEDC_TIMER_0;
      config.pin_d0 = Y2_GPIO_NUM;
      config.pin_d1 = Y3_GPIO_NUM;
      config.pin_d2 = Y4_GPIO_NUM;
      config.pin_d3 = Y5_GPIO_NUM;
      config.pin_d4 = Y6_GPIO_NUM;
      config.pin_d5 = Y7_GPIO_NUM;
      config.pin_d6 = Y8_GPIO_NUM;
      config.pin_d7 = Y9_GPIO_NUM;
      config.pin_xclk = XCLK_GPIO_NUM;
      config.pin_pclk = PCLK_GPIO_NUM;
      config.pin_vsync = VSYNC_GPIO_NUM;
      config.pin_href = HREF_GPIO_NUM;
      config.pin_sscb_sda = SIOD_GPIO_NUM;
      config.pin_sscb_scl = SIOC_GPIO_NUM;
      config.pin_pwdn = PWDN_GPIO_NUM;
      config.pin_reset = RESET_GPIO_NUM;
      config.xclk_freq_hz = 20000000;
      config.pixel_format = PIXFORMAT_JPEG;

      if (psramFound()) {
        config.frame_size = FRAMESIZE_UXGA;
        config.jpeg_quality = 10;
        config.fb_count = 2;
      } else {
        config.frame_size = FRAMESIZE_SVGA;
        config.jpeg_quality = 12;
        config.fb_count = 1;
      }

      esp_err_t err = esp_camera_init(&config);
      if (err != ESP_OK) {
        Serial.printf("Erreur lors de l'initialisation de la caméra : 0x%x", err);
        return;
      }
    }

    void send_a_picture() {
      esp_http_client_config_t config = {
        .url = "http://192.168.1.254/plantbot/reception_image.php"
      };

      esp_http_client_handle_t client = esp_http_client_init(&config);
      esp_http_client_set_post_field(client, (const char *)fb->buf, fb->len);
      esp_http_client_set_method(client, HTTP_METHOD_POST);
      esp_http_client_set_header(client, "Content-type", "image/jpg");
      esp_err_t err = esp_http_client_perform(client);
      if (err == ESP_OK)
        Serial.println("Image envoyée");
      else
        Serial.printf("erreur %d lors de l'envoi de l'image\r\n", err);

      esp_http_client_cleanup(client);

      esp_camera_fb_return(fb);
    }

    void take_a_picture() {
      fb = esp_camera_fb_get();
        if(!fb) {
          Serial.println("Echec de la capture d'image :(");
          return;
        } else {
          Serial.println("Image enregistrée avec succès :)");
        }
    }

    void start_wifi() {

       WiFi.begin(ssid, password);
      delay(1);
      while(WiFi.status() != WL_CONNECTED) {
          delay(500);
          Serial.println("On attend la connection wifi...");
          yield(); // Pour éviter que le watchdog ne fasse redémarrer la carte.
        }

      Serial.println("");
      Serial.println("WiFi connecté");
      Serial.println(WiFi.localIP());
      wifi_status = true;
    }

    void end_wifi() {

      WiFi.disconnect();
      delay(1);
      WiFi.mode(WIFI_OFF);
      delay(1);
       while(WiFi.status() == WL_CONNECTED) {
          delay(500);
          Serial.println("On attend la déconnection du wifi");
          yield();
        }
      Serial.println("WiFi déconnecté");

    }

    // Allumer le flash
    void lights_on() {
      digitalWrite(led_pin, HIGH);
    }

    // Éteindre le flash
    void lights_off() {
      digitalWrite(led_pin, LOW);
    }

    // Arrosage
    void watering() {
      for(int i=0; i<NB_CYCLE_WATERING; ++i) {
        Serial.print(" -> Cycle numero ");
        Serial.println(i);

        relay_state = HIGH;
        digitalWrite(relay_pin, relay_state); // On arrose !

        delay(WATERING_TIME);
        relay_state = !relay_state;
        digitalWrite(relay_pin, relay_state); // On éteint l'arrosage

        delay(TIME_BTWN_WATERING); // On attend un peu que l'eau pénetre dans la terre
      }
    }


    void setup() {
      Serial.begin(115200);
      pinMode(moist_pin, INPUT);
      pinMode(relay_pin, OUTPUT);
      pinMode(led_pin, OUTPUT);

      digitalWrite(relay_pin, relay_state);
      digitalWrite(led_pin, led_state);
      init_camera();
      lights_off();

      reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG);
      
      delay(2000); // Pour s'assurer que tout est bien initialisé (capteurs, wifi, etc..)
    }


    void loop() {

      int v_moist = alt_analogRead(moist_pin);
      Serial.print("Taux d'humidité de la terre : ");
      Serial.println(v_moist);
      if(v_moist <= MAX_MOIST_VAL) { // tres mouillé
        Serial.print("C'est mouillé ! on va faire dodo pendant ");
        Serial.print(SLEEPING_TIME);
        Serial.println(" milli-secondes");
        delay(SLEEPING_TIME);

      } else if(v_moist >= MIN_MOIST_VAL) { // tres sec
        start_wifi();
        lights_on();
        take_a_picture();
        lights_off();
        send_a_picture();
        end_wifi();
        watering();
        Serial.println("Arrosage terminé");
        delay(2000);
      }
      
    }