#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif

#include "ThingsBoard.h"
#include "DHTesp.h"


#define CURRENT_FIRMWARE_TITLE    "TEST"
#define CURRENT_FIRMWARE_VERSION  "1.0.0"

#define WIFI_SSID           "Wokwi-GUEST"
#define WIFI_PASSWORD       ""

// See https://thingsboard.io/docs/getting-started-guides/helloworld/
// to understand how to obtain an access token
#define TOKEN               "IM7n11TLV8BdBp4U6Bao"
#define THINGSBOARD_SERVER  "thingsboard.cloud"

#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))

// Array with LEDs that should be controlled from ThingsBoard, one by one
uint8_t leds_control[] = { 33, 25, 26 };

// Baud rate for debug serial
#define SERIAL_DEBUG_BAUD   115200

const int DHT_PIN = 15;

DHTesp dhtSensor;

// Initialize ThingsBoard client
WiFiClient espClient;
// Initialize ThingsBoard instance
ThingsBoard tb(espClient);
// the Wifi radio's status
int status = WL_IDLE_STATUS;

// Main application loop delay
int quant = 20;
// Period of sending a temperature/humidity data.
int send_delay = 2000;

// Time passed after LED was turned ON, milliseconds.
int led_passed = 0;
// Time passed after temperature/humidity data was sent, milliseconds.
int send_passed = 0;

// Set to true if application is subscribed for the RPC messages.
bool subscribed = false;
// LED number that is currenlty ON.
int current_led = 0;

float temperature = 0;
float humidity = 0;

int led_delay = 0;

char buffer[50];

RPC_Response processDelayChange(const RPC_Data &data)
{
  Serial.println("Received the set delay RPC method");

  // Process data

  led_delay = data;

  Serial.print("Set new delay: ");
  Serial.println(led_delay);

  return String(led_delay);
}

RPC_Response processGetDelay(const RPC_Data &data)
{
  Serial.println("Received the get value method");
  return String(led_delay);
}

RPC_Response processSetGpioState(const RPC_Data &data)
{
  Serial.println("Received the set GPIO RPC method");

  int pin = data["pin"];
  bool enabled = data["enabled"];
  int pinLed = pin-1;
  

  if (pinLed < COUNT_OF(leds_control)) {
    Serial.print("Setting LED ");
    Serial.print(pin);
    Serial.print(" to state ");
    Serial.println(enabled);

    digitalWrite(leds_control[pinLed], enabled);
  }

  return String("{\"" + String(pin) + "\": " + String(enabled?"true":"false") + "}");
}

RPC_Response processGetGpioState(const RPC_Data &data)
{
  Serial.println("Received the get GPIO RPC method");
  String respStr = "{";
  
  for (size_t i = 0; i < COUNT_OF(leds_control); ++i) {
    int pin = leds_control[i];
    Serial.print("Getting LED ");
    Serial.print(pin);
    Serial.print(" state ");
    bool ledState = digitalRead(pin);
    Serial.println(ledState);

    respStr += String("\"" + String(i) + "\": " + String(ledState?"true":"false") + ", ");
  }  
  respStr = respStr.substring(0, respStr.length() - 2);
  respStr += "}";
  return respStr;
}


// RPC handlers
RPC_Callback callbacks[] = {
  { "setValue",         processDelayChange },
  { "getValue",         processGetDelay },  
  { "setGpioStatus",    processSetGpioState },
  { "getGpioStatus",    processGetGpioState },
};


void InitWiFi()
{
  Serial.println("Connecting to AP ...");
  // attempt to connect to WiFi network

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {

    delay(500);
    Serial.print(".");
  }
  Serial.println("Connected to AP");
}

void reconnect() {
  // Loop until we're reconnected
  status = WiFi.status();
  if ( status != WL_CONNECTED) {
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    Serial.println("Connected to AP");
  }
}

void setup() {
  // initialize serial for debugging
  Serial.begin(SERIAL_DEBUG_BAUD);
  Serial.println();
  InitWiFi();
  for (size_t i = 0; i < COUNT_OF(leds_control); ++i) {
    pinMode(leds_control[i], OUTPUT);
  }
  dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
}

void loop() {
   delay(quant);

  led_passed += quant;
  send_passed += quant;

  // Reconnect to WiFi, if needed
  if (WiFi.status() != WL_CONNECTED) {
    reconnect();
    return;
  }

  // Reconnect to ThingsBoard, if needed
  if (!tb.connected()) {
    subscribed = false;

    // Connect to the ThingsBoard
    Serial.print("Connecting to: ");
    Serial.print(THINGSBOARD_SERVER);
    Serial.print(" with token ");
    Serial.println(TOKEN);
    if (!tb.connect(THINGSBOARD_SERVER, TOKEN)) {
      Serial.println("Failed to connect");
      return;
    }
  }

  // Subscribe for RPC, if needed
  if (!subscribed) {
    Serial.println("Subscribing for RPC... ");

    // Perform a subscription. All consequent data processing will happen in
    // callbacks as denoted by callbacks[] array.
    if (!tb.RPC_Subscribe(callbacks, COUNT_OF(callbacks))) {
      Serial.println("Failed to subscribe for RPC");
      return;
    }

    Serial.println("Subscribe done");
    subscribed = true;
  }  

  // Check if it is a time to send DHT22 temperature and humidity
  if (send_passed > send_delay) {
    Serial.println();
    Serial.print("Sending data... ");
    TempAndHumidity lastValues = dhtSensor.getTempAndHumidity();    

    if (isnan(lastValues.humidity) || isnan(lastValues.temperature)) {
      Serial.println("Failed to read from DHT sensor!");
    } else {
      temperature = lastValues.temperature;
      Serial.print("temperature: ");
      Serial.print(temperature);
      humidity = lastValues.humidity;
      Serial.print(" humidity: ");
      Serial.print(humidity);
      tb.sendTelemetryFloat("temperature", temperature);
      tb.sendTelemetryFloat("humidity", humidity);
    }
    send_passed = 0;
  }

  tb.loop();
}