#pragma once

#include "wled.h"

#include <dht_nonblocking.h>

// USERMOD_DHT_DHTTYPE:
//   11   // DHT 11
//   21   // DHT 21
//   22   // DHT 22  (AM2302), AM2321 *** default
#ifndef USERMOD_DHT_DHTTYPE
#define USERMOD_DHT_DHTTYPE 22
#endif

#if USERMOD_DHT_DHTTYPE == 11
#define DHTTYPE DHT_TYPE_11
#elif USERMOD_DHT_DHTTYPE == 21
#define DHTTYPE DHT_TYPE_21
#elif USERMOD_DHT_DHTTYPE == 22
#define DHTTYPE DHT_TYPE_22
#endif

// Connect pin 1 (on the left) of the sensor to +5V
//   NOTE: If using a board with 3.3V logic like an Arduino Due connect pin 1
//   to 3.3V instead of 5V!
// Connect pin 2 of the sensor to whatever your DHTPIN is
//   NOTE: Pin defaults below are for QuinLed Dig-Uno's Q2 on the board
// Connect pin 4 (on the right) of the sensor to GROUND
//   NOTE: If using a bare sensor (AM*), Connect a 10K resistor from pin 2
//   (data) to pin 1 (power) of the sensor. DHT* boards have the pullup already

#ifdef USERMOD_DHT_PIN
#define DHTPIN USERMOD_DHT_PIN
#else
#ifdef ARDUINO_ARCH_ESP32
#define DHTPIN 21
#else //ESP8266 boards
#define DHTPIN 4
#endif
#endif

// the frequency to check sensor, 1 minute
#ifndef USERMOD_DHT_MEASUREMENT_INTERVAL
#define USERMOD_DHT_MEASUREMENT_INTERVAL 60000
#endif

// how many seconds after boot to take first measurement, 90 seconds
// 90 gives enough time to OTA update firmware if this crashses
#ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT
#define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000
#endif

// from COOLDOWN_TIME in dht_nonblocking.cpp
#define DHT_TIMEOUT_TIME  10000

DHT_nonblocking dht_sensor(DHTPIN, DHTTYPE);

class UsermodDHT : public Usermod {
  private:
    unsigned long nextReadTime = 0;
    unsigned long lastReadTime = 0;
    float humidity, temperature = 0;
    bool initializing = true;
    bool disabled = false;
    #ifdef USERMOD_DHT_STATS
    unsigned long nextResetStatsTime = 0;
    uint16_t updates = 0;
    uint16_t clean_updates = 0;
    uint16_t errors = 0;
    unsigned long maxDelay = 0;
    unsigned long currentIteration = 0;
    unsigned long maxIteration = 0;
    #endif

  public:
    void setup() {
      nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT;
      lastReadTime = millis();
      #ifdef USERMOD_DHT_STATS
      nextResetStatsTime = millis() + 60*60*1000;
      #endif
    }

    void loop() {
      if (disabled) {
        return;
      }
      if (millis() < nextReadTime) {
        return;
      }

      #ifdef USERMOD_DHT_STATS
      if (millis() >= nextResetStatsTime) {
        nextResetStatsTime += 60*60*1000;
        errors = 0;
        updates = 0;
        clean_updates = 0;
      }
      unsigned long dcalc = millis();
      if (currentIteration == 0) {
        currentIteration = millis();
      }
      #endif

      float tempC;
      if (dht_sensor.measure(&tempC, &humidity)) {
        #ifdef USERMOD_DHT_CELSIUS
        temperature = tempC;
        #else
        temperature = tempC * 9 / 5 + 32;
        #endif

        nextReadTime = millis() + USERMOD_DHT_MEASUREMENT_INTERVAL;
        lastReadTime = millis();
        initializing = false;
        
        #ifdef USERMOD_DHT_STATS
        unsigned long icalc = millis() - currentIteration;
        if (icalc > maxIteration) {
          maxIteration = icalc;
        }
        if (icalc > DHT_TIMEOUT_TIME) {
          errors += icalc/DHT_TIMEOUT_TIME;
        } else {
          clean_updates += 1;
        }
        updates += 1;
        currentIteration = 0;

        #endif
      }

      #ifdef USERMOD_DHT_STATS
      dcalc = millis() - dcalc;
      if (dcalc > maxDelay) {
        maxDelay = dcalc;
      } 
      #endif

      if (((millis() - lastReadTime) > 10*USERMOD_DHT_MEASUREMENT_INTERVAL)) {
        disabled = true;
      }
    }

    void addToJsonInfo(JsonObject& root) {
      if (disabled) {
        return;
      }
      JsonObject user = root["u"];
      if (user.isNull()) user = root.createNestedObject("u");

      JsonArray temp = user.createNestedArray("Temperature");
      JsonArray hum = user.createNestedArray("Humidity");

      #ifdef USERMOD_DHT_STATS
      JsonArray next = user.createNestedArray("next");
      if (nextReadTime >= millis()) {
        next.add((nextReadTime - millis()) / 1000);
        next.add(" sec until read");
      } else {
        next.add((millis() - nextReadTime) / 1000);
        next.add(" sec active reading");
      }

      JsonArray last = user.createNestedArray("last");
      last.add((millis() - lastReadTime) / 60000);
      last.add(" min since read");

      JsonArray err = user.createNestedArray("errors");
      err.add(errors);
      err.add(" Errors");

      JsonArray upd = user.createNestedArray("updates");
      upd.add(updates);
      upd.add(" Updates");

      JsonArray cupd = user.createNestedArray("cleanUpdates");
      cupd.add(clean_updates);
      cupd.add(" Updates");

      JsonArray iter = user.createNestedArray("maxIter");
      iter.add(maxIteration);
      iter.add(" ms");

      JsonArray delay = user.createNestedArray("maxDelay");
      delay.add(maxDelay);
      delay.add(" ms");
      #endif

      if (initializing) {
        // if we haven't read the sensor yet, let the user know
        // that we are still waiting for the first measurement
        temp.add((nextReadTime - millis()) / 1000);
        temp.add(" sec until read");
        hum.add((nextReadTime - millis()) / 1000);
        hum.add(" sec until read");
        return;
      }

      hum.add(humidity);
      hum.add("%");

      temp.add(temperature);
      #ifdef USERMOD_DHT_CELSIUS
      temp.add("°C");
      #else
      temp.add("°F");
      #endif
    }
   
    uint16_t getId()
    {
      return USERMOD_ID_DHT;
    }

};
