#include <WiFi.h>
#include <ArduinoJson.h>
#include <PubSubClient.h>

// WiFi config
const char* SSID = "Wokwi-GUEST";
const char* PASSWORD = "";

// MQTT config
const char* MQTT_SERVER = "broker.emqx.io";
const int MQTT_PORT = 1883;
const char* TOPIC_APPOINTMENT = "fanta/appointment";
const char* TOPIC_PING_APPOINTMENT = "fanta/ping/appointment";
const char* TOPIC_LOG = "fanta/log";
const char* TOPIC_CLEAR = "fanta/clear";

// Pins config
const int LED_PINS[] = { 25, 33, 32 };
const int BUTTON_PINS[] = { 13, 12, 14, 27, 26 };

// Instance object
WiFiClient client;
PubSubClient mqtt(client);

String ptId = "";
String doctorId = "";

bool isFetching = false;

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

  setPinMode();
  connectWiFi();

  mqtt.setServer(MQTT_SERVER, MQTT_PORT);
  mqtt.setCallback(callback);
}

void loop() {
  if (!mqtt.connected()) {
    reconnectMQTT();
  }
  mqtt.loop();

  if (ptId == "" && doctorId == "") {
    if (!isFetching) {
      isFetching = true;
      digitalWrite(LED_PINS[1], HIGH);
      mqtt.publish(TOPIC_PING_APPOINTMENT, "");
    }
  } else {
    digitalWrite(LED_PINS[1], LOW);

    for (int i = 0; i < 5; i++) {
      int value = digitalRead(BUTTON_PINS[i]);

      if (!value) {
        publishLogLevel(i + 1);
        digitalWrite(LED_PINS[0], HIGH);
        digitalWrite(LED_PINS[2], LOW);
      } else {
        digitalWrite(LED_PINS[0], LOW);
        digitalWrite(LED_PINS[2], HIGH);
      }
    }
  }

  delay(1500);
}

void setPinMode() {
  // LEDs
  for (int i = 0; i < 3; i++) {
    pinMode(LED_PINS[i], OUTPUT);
    digitalWrite(LED_PINS[i], LOW);
  }

  // Buttons
  for (int i = 0; i < 5; i++) {
    pinMode(BUTTON_PINS[i], INPUT_PULLUP);
  }
}

void connectWiFi() {
  delay(10);

  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(SSID);

  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void reconnectMQTT() {
  while (!mqtt.connected()) {
    Serial.print("Attempting MQTT connection...");

    String clientId = "PatientTracker-";
    clientId += String(random(0xffff), HEX);

    if (mqtt.connect(clientId.c_str())) {
      Serial.println("connected");

      mqtt.subscribe(TOPIC_APPOINTMENT);
      mqtt.subscribe(TOPIC_CLEAR);
    } else {
      Serial.print("Failed, rc=");
      Serial.print(mqtt.state());
      Serial.println(" try again in 5 seconds");

      delay(5000);
    }
  }
}

void callback(char* topic, byte* message, unsigned int length) {
  String messageTemp;

  for (int i = 0; i < length; i++) {
    messageTemp += (char)message[i];
  }

  if ((String)topic == (String)TOPIC_APPOINTMENT) {
    char jsonString[256];
    messageTemp.toCharArray(jsonString, messageTemp.length() + 1);

    DynamicJsonDocument doc(1024);
    deserializeJson(doc, jsonString);

    const char* _ptId = doc["pt_id"];
    const char* _doctorId = doc["doctor_id"];

    ptId = String(_ptId);
    doctorId = String(_doctorId);
  }
}

void publishLogLevel(int logLevel) {
  DynamicJsonDocument doc(1024);
  char json[256];

  doc["pt_id"] = ptId;
  doc["doctor_id"] = doctorId;
  doc["log_level"] = logLevel;

  serializeJson(doc, json);
  mqtt.publish(TOPIC_LOG, json);
}