#include <Arduino.h>
#include <WiFi.h>
#include <WiFiAP.h>
#include <Thread.h> // https://github.com/ivanseidel/ArduinoThread
#include <ThreadController.h>
#include <AsyncMqttClient.h>
#include <Ticker.h>
#include "Streaming.h"
#include "FileOperations.h"
#include <AsyncTCP.h>
#include "ESPAsyncWebServer.h"
#include "ArduinoJson.h"
#include "SensorSM30Controller.h"
#include <ESP32Time.h>
#include <Wire.h>
#include "mbedtls/aes.h"
#include <AsyncElegantOTA.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include "main.h"

// #define MQTT_BROKER
// #define TEST_SENSOR

// #define NTP   // get time from NTP
#define MQTT_TIME // get time from MQTT
#ifdef NTP
// NTP
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
#endif

// Variables to save date and time
String formattedDate;
String dayStamp;
String timeStamp;

const char *host = "ol_update";
const char *version = "Retro_V_1.3.0";
const char *ttime;

AsyncWebServer server(80);

#define QOS1 1
#define QOS2 2
#define QOS3 3
#define OPEN_TIME 500

#define DEBUG
#define PRIVATE 36

// #define SLEEPLESS
#define OLOCK
// #define PERIODIC_WAKEUP
#define CLONE_FREE
#define USE_RTC

#define OFFLINE // Tarsis - wakeup on update-sleep card and sends logs

// #define NO_RECORDS

#define INVERSE_MOTOR

#define FACTORY_RESET 39 // needs to be defined
#define EVENT_TYPE "access"

#define DND 36 // privacy
#ifdef OLOCK
#define ZUMER 22
#define MOTOR_ENABLE 27
#define MOTOR_DIRECTION 12
#define MOTOR_DELAY 140
// #define RELAY 27 // door open
// #define BUTTON 36 // ?
#define PROXIMITY 34
#define LED_RED 4
#define LED_GREEN 21
#define FORMAT_SPIFFS_IF_FAILED true
#define ENABLE_PERIPHERY 25
#define DEVICE_TYPE "olock"
#endif
#define BATTERY_IN 35     // battery measurements
#define BATTERY_ENABLE 26 // enable measurement
// #define SENSOR_POWER  // for CAMA reset

#define SOUND_OPEN 100
#define CONNFIG_FILE "/configjson.txt"
#define AWAKE_TIME 4000               // 2 sec
#define HOLD_TIME_FACTORY_RESET 50000 // 50 sec
#define WIFI_TIME 8000                // 8 sec
#define BUFF_FILE "/buffer.txt"

#define PARAM_AUTOLEARN true
#define PARAM_DUPLICATION_CHECK false
#define PARAM_SECURITY_LEVEL 1
// #define SDA_PROXY 22
// #define SCL_PROXY 21

// --- RFID
#include <SPI.h>
#include <MFRC522.h>
#define RST_PIN 14 // Configurable, see typical pin layout above
// #define RST_PIN 35                // New hardware
#define SS_PIN 5                  // Configurable, see typical pin layout above
#define IRQ_PIN 2                 // Configurable, depends on hardware
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance.
MFRC522::MIFARE_Key key;
MFRC522::StatusCode status;
// Init array that will store new NUID
byte nuidPICC[4];
byte card_block1[18];

byte card_block2[18];
byte card_block4[18];
byte card_block5[18];
byte card_block6[18];
// byte card_block4[16];
// byte card_block5[16];
//  --- end RFID
//  TIME_TO_LIVE
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR int time_for_sleep = 60; // 300;
RTC_DATA_ATTR uint64_t cards[100];
RTC_DATA_ATTR uint64_t users[100];

WiFiClient espClient;
AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;
IPAddress IP;

ThreadController threadControl = ThreadController();
Thread thread = Thread();
FileOperations filesys;

char MAC_char[18];
uint8_t MAC_array[6];
// const char *mymac = "240ac40457d0";
const char *type = DEVICE_TYPE;
char clientName[20] = "Olock_240ac40457d0";
char out_message_buffer[2048];
char operations_buffer[2048];
const String s = "";
uint32_t startSound = 0;
uint32_t startRelay = 0;
uint16_t relay_count = 1000;
uint16_t sound_count = 200;
uint64_t awake_timer = 0; // awake state
uint16_t battery = 0;

bool to_send_late = false;
uint64_t access_late_user = 0;
uint64_t access_late_card = 0;
bool factory_mode = false;
bool insomnia = false; // newer sleep
bool door_opened = false;
bool open_once = false;
String topic_late;
String message_late;
String error_late = "unknown";
long timeMeasure;
bool ready_to_sleep = true;

MyTools tools = MyTools();
bool awake_update = false;
bool master_access = false;

#ifdef FINGERPRINT_SM30
SensorSM30Controller camaSM30 = SensorSM30Controller(&Serial2);
#endif

String ledState;
static bool block_finger_loop = false;
bool factory_reset_state = false;
bool skip_sensor = false;
uint8_t proxy_counter = 0;    // counts the proximity tryes in update mode
bool proxy_old_state = false; // for counting the rise edge

// CONFIG PARAMETERS
const char *PARAM_HOTEL = "hotel";
const char *PARAM_NAME = "name";
const char *PARAM_SSID = "ssid";
const char *PARAM_PASS = "pass";
const char *PARAM_AP_SSID = "ap_ssid";
const char *PARAM_AP_PASS = "ap_pass";
const char *PARAM_MQTT = "mqtt";
const char *PARAM_MQTT_PORT = "mqtt_port";
const char *PARAM_MQTT_USER = "mqtt_user";
const char *PARAM_MQTT_USER_PASS = "mqtt_user_pass";
const char *PARAM_IP = "ip_addres";
const char *PARAM_MASK = "mask";
const char *PARAM_GATEWAY = "gateway";
const char *PARAM_DHCP = "dhcp";
const char *PARAM_TYPE = "type";
const char *PARAM_RELAY = "relay_time";
const char *PARAM_EVENT = "event_type";
const char *PARAM_SOUND = "sound";
const char *PARAM_GROUP = "group";
const char *PARAM_ROOM = "room";

struct Config
{
  uint16_t hotel_code;
  uint16_t room;
  uint64_t group;
  uint16_t guest_count;
  uint16_t staff_count;
  uint16_t master_count;
  char aes_key[16];
  char name[16];
  char ssid[30];
  char pass[30];
  char mqtt[30];
  char mqtt_user[16];
  char mqtt_user_pass[16];
  int mqtt_port;
  int dhcp;
  char ip_address[16];
  char ip_mask[16];
  char gateway[16];
  char type[16];
  int relay_time = 3;
  char event[16];
  int sound;
  char apssid[16];
  char appass[16];
  char update_code[16]; // code for activating update card
  uint8_t today;        // getting time once a day after event or timer wakeup
};

RTC_DATA_ATTR Config config;

// WiFi Static config
// Set your Static IP address
IPAddress local_IP(192, 168, 1, 14);
// Set your Gateway IP address
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);   // optional
IPAddress secondaryDNS(8, 8, 4, 4); // optional

// TIME
ESP32Time time_clock;

// PROXIMITY
// VCNL4040 proximitySensor;

/**
 * @brief Clear RTC cards memory
 *
 */
void clearCards()
{
  for (size_t i = 0; i < 100; i++)
  {
    cards[i] = 0;
    users[i] = 0;
  }
}

/**
 * @brief Print Cards
 *
 */
void printCards()
{
  String message(MAC_char);
  message += "/";
  char buffer[100];
  for (size_t i = 1; i < 100; i++)
  {
    if (users[i] == 0)
      continue;
    Serial.println(cards[i]);

    sprintf(buffer, "%i:%llu:%llu,", i, users[i], cards[i]);
    message += buffer;
  }
  Serial.println(message); // print users
  // int len = message.length();
  // char msg[len];
  // message.toCharArray(msg, len);
  // mqttClient.publish("users", 0, false, msg, len);
  mqttClient.publish("users", 0, false, message.c_str());
}

/**
 * @brief Insert card number in given position
 *
 * @param card
 * @param position
 */
void insertCard(uint64_t card, uint8_t position)
{
  if (position == 0 || position > 99)
    return; // limit reached
  cards[position] = card;
}

/**
 * @brief On subscription
 *
 * @param packetId
 * @param qos
 */
void onMqttSubscribe(uint16_t packetId, uint8_t qos)
{
  Serial.println(F("Subscribe acknowledged."));
  // Serial.print(F("  packetId: "));
  // Serial.println(packetId);
  // Serial.print(F("  qos: "));
  // Serial.println(qos);
}

/**
 * @brief For time measurements
 *
 */
void measureTime()
{
  Serial.print(F("Response time:"));
  Serial.println(millis() - timeMeasure);
}


/**
 * @brief  MQTT message receiver
 *
 * @param topic
 * @param payload
 * @param properties
 * @param len
 * @param index
 * @param total
 */
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
  timeMeasure = millis();
  awake_timer = millis(); // wait after each message before sleep
  Serial.print(F("Received: "));
  // mqttClient.disconnect();
  // mqttClient.unsubscribe(mymac);
  String message = "";
  for (size_t i = 0; i < len; i++)
  {
    message += payload[i];
  }

  Serial << topic << ":" << message << endl;
  // Serial << "len:" << len << " index:" << index << " total:" << total << endl;
  topic_late = topic;
  message_late = message;

  // mqttClient.subscribe(mymac, 1);
  //  connectBroker();
  // confirmOkReceiving();
}

/**
 * @brief Connect to MQTT brocker
 *
 */
void connectBroker()
{
  if (mqttClient.connected())
    return;
  uint8_t tries = 15;
  while (!mqttClient.connected())
  {
    mqttClient.onConnect(onMqttConnect);
    mqttClient.onDisconnect(onMqttDisconnect);
    mqttClient.onSubscribe(onMqttSubscribe);
    mqttClient.onUnsubscribe(onMqttUnsubscribe);
    mqttClient.onPublish(onMqttPublish);
    // mqttClient.setServer(MQTT_HOST, MQTT_PORT);

    mqttClient.setCleanSession(true);
    mqttClient.setClientId(clientName);
    mqttClient.setServer(config.mqtt, config.mqtt_port);
    char will[30];
    sprintf(will, "olock/disconnect/%s", MAC_char);
    mqttClient.setWill("system", 0, false, will, 30);
    mqttClient.onMessage(onMqttMessage);
    mqttClient.connect();
    delay(200);
    Serial << "." << endl;
    tries--;
    if (tries < 1)
      break;
  }
  awake_timer = millis();
  while (!mqttClient.subscribe(MAC_char, 2))
  {
    relayLoop();
    Serial << "sub ..." << endl;
    cardLoop();
    // delay(300);
    if (millis() - awake_timer > WIFI_TIME)
    {
      goSleep();
    }
  };
}

/**
 * @brief  RESPONSE ALIVE
 */
void responseAlive()
{
  String message(MAC_char);
  message += "/";
  message += type;
  message += "/";
  message += IP.toString().c_str();
  message += "/";
  message += version;
  message += "/";
  message += config.name;
  message += config.room;
  message += "/";
  message += tools.calc_battery_percentage(analogRead(BATTERY_IN));
  message += "/";
  message += WiFi.RSSI();
  message += "/";
  // dd-MM-yyyy HH:mm:ss EET
  message += time_clock.getTime("%d-%m-%Y %H:%M:%S");
  //
  // measure battery

  // int len = message.length() + 1;
  // char msg[len];
  // message.toCharArray(msg, len);
  // mqttClient.publish("alive", 1, false, msg, len);
  if (awake_update)
  {
    mqttClient.publish("update", 1, false, message.c_str());
  }
  else
  {
    mqttClient.publish("alive", 1, false, message.c_str());
  }
}

/**
 * @brief  Set relay in open state. Closed by main loop=
 *
 * @param time
 */
void open_relay(word time)
{

#ifdef OLOCK
  digitalWrite(MOTOR_DIRECTION, 1); // TODO chose direction
#ifdef INVERSE_MOTOR
  digitalWrite(MOTOR_DIRECTION, 0); // TODO chose direction
#endif
  digitalWrite(MOTOR_ENABLE, 1); // start motor
  delay(MOTOR_DELAY);
  digitalWrite(MOTOR_ENABLE, 0);

  digitalWrite(MOTOR_DIRECTION, 0);
#ifdef INVERSE_MOTOR
  digitalWrite(MOTOR_DIRECTION, 1); // TODO chose direction
#endif
#endif

  digitalWrite(LED_GREEN, 1);
  startRelay = millis() + time;
}

/**
 * @brief Start sound. Sound should be set off from main loop.
 *
 * @param time
 */
void zumerMillis(byte time)
{
  digitalWrite(ZUMER, 1);
  startSound = millis() + time;
}

/**
 * @brief Indicate errors
 *
 */
void errorSound()
{
  int interval = 20;
  for (uint8_t i = 0; i < 5; i++)
  {
    digitalWrite(ZUMER, 1);
    digitalWrite(LED_RED, 1);
    delay(interval);
    digitalWrite(ZUMER, 0);
    digitalWrite(LED_RED, 0);
    delay(interval);
  }
}

/**
 * @brief Time for sleep in seconds
 *
 * @param sl_time
 */
void goSleep()
{
  if (door_opened)
  {
    closedoor();
  }

  mfrc522.PCD_SoftPowerDown();

// Periodic wakeup for receiving finger messages
#ifdef PERIODIC_WAKEUP
  uint64_t tm = time_for_sleep * 1000000; // timer in microseconds
  if (time_for_sleep > 1)
  {
    esp_sleep_enable_timer_wakeup(tm);
  }
  Serial.print(tm);

#endif
  digitalWrite(ENABLE_PERIPHERY, LOW); // disable periphery

  Serial.println(F(" sleeping..."));

  delay(300);
  esp_deep_sleep_start();
}

/*
##     ##  #######  ######## ########
###   ### ##     ##    ##       ##
#### #### ##     ##    ##       ##
## ### ## ##     ##    ##       ##
##     ## ##  ## ##    ##       ##
##     ## ##    ##     ##       ##
##     ##  ##### ##    ##       ##
*/

void confirmOkReceiving()
{
  //[MAC]/OK
  if (!mqttClient.connected())
    connectBroker();
  sprintf(out_message_buffer, "%s/OK", MAC_char);
  Serial << "Confirm message:" << out_message_buffer << endl;
  mqttClient.publish("confirm", 0, false, out_message_buffer);
}

void confirmNokReceiving(const char *message)
{
  //[MAC]/NOK/[message]
  if (!mqttClient.connected())
    connectBroker();
  sprintf(out_message_buffer, "%s/NOK/%s", MAC_char, message);
  Serial << "Confirm message:" << out_message_buffer << endl;
  mqttClient.publish("confirm", 0, false, out_message_buffer);
}

/**
 * @brief Response on topic own MAC
 *
 * @param topic
 * @param payload
 * @param length
 *
 */
void mac_subscriber(String topic, String payload)
{
  String msg[7] = {"", "", "", "", "", "", ""};
  int chunks = tools.parseMessage(msg, payload);
  if (topic.equals("all"))
  {
    if (msg[0].equals("sleep"))
    {
      uint64_t ts = msg[1].toInt();
      if ((ts > 110 && ts < 86400) || ts == 0)
      {
        time_for_sleep = ts;
      }
      goSleep();
    }
    else
    {
      if (msg[0].equals("timesync"))
      {
        setClock(msg[1]);
      }
    }
    return;
  }

  if (payload.equals("open"))
  {
    opendoor(config.relay_time);
    return;
  }
  else if (payload.equals("start_web"))
  {
    startServer();
  }
  else if (payload.equals("restart"))
  {
    restart();
  }
  else if (payload.equals("enable_sensor"))
  {
    block_finger_loop = false;
    confirmOkReceiving();
  }
  else if (payload.equals("disable_sensor"))
  {
    block_finger_loop = true;
    confirmOkReceiving();
  }
  else if (payload.equals("list"))
  {
    for (size_t i = 0; i < 100; i++)
    {
      if (cards[i] != 0)
      {
        Serial.println(cards[i]);
      }
    }
    filesys.listDir(SPIFFS, "/", 0);
    measureTime();
    return;
  }
  else if (payload.equals("get_users"))
  {
    // listUsers();
    printCards();
    measureTime();
    readFile(SPIFFS, "/users.txt");
    payload = "";
    return;
  }
  // else if (payload.equals("writecard"))
  // {
  //   writeCard();
  //   return;
  // }
  else if (payload.equals("getconfig"))
  {
    // Only serial print - sensitive informatin
    filesys.printFile(SPIFFS, CONNFIG_FILE);
    measureTime();
    return;
  }
  else if (payload.indexOf("mqtt_port") > 0)
  {
    // SET CONFIG
    setConfig(payload);
    measureTime();
    return;
  }
  else if (payload.indexOf("group_H") > 0)
  {
    // SET CONFIG
    setConfig(payload);
    measureTime();
    return;
  }
  else if (payload.equals("clear"))
  {
    bool test = true;

#ifdef FINGERPRINT_SM30
    test = camaSM30.deleteAllTemplates();
#endif

    block_finger_loop = true;
    if (test)
    {
      clearCards();
      deleteFile(SPIFFS, "/users.txt");
      confirmOkReceiving();
    }
    else
    {
      confirmNokReceiving("Error in deleting");
    }
    // measureTime();
    block_finger_loop = false;
    return;
  }
  else if (payload.equals("ping"))
  {
    block_finger_loop = false;
    responseAlive();
    return;
  }

  if (msg[0].equals("timesync"))
  {
    setClock(msg[1]);
  }

  if (msg[0].equals("sleep"))
  {
    uint16_t tim = msg[1].toInt();
    time_for_sleep = tim;
    goSleep();
  }
  else if (msg[0].equals("time"))
  {
    // time_clock.setTime(00, 18, 13, 10, 1, 2022);  // 10th Jan 2022 13:18:00
    time_clock.setTime(00, msg[5].toInt(), msg[4].toInt(), msg[1].toInt(), msg[2].toInt(), msg[3].toInt());
  }
  else if (msg[0].equals("insert"))
  {
    block_finger_loop = true;
    // insert/[slot]/[user_id]/[user_pin]/[template]/[card]
    //  demo insert/0/111/0/0/3064257667/
    if (chunks < 5)
    {
      Serial.println(F("Bad formatted insert message"));
      confirmNokReceiving("Bad formated insert message");
      return;
    }

    uint64_t num = tools.StringTo64(msg[5]);
    uint8_t slot = tools.StringTo64(msg[1]);
    uint64_t userId = tools.StringTo64(msg[2]);
    if (slot < 1 || slot > 99)
    {
      Serial.println(F("invalid slot:"));
      Serial.println(slot);
      confirmNokReceiving("invalid slot");
      return;
    }
    else
    {
      Serial << "card:" << num << endl;
      cards[slot] = num;
      users[slot] = userId;
      writeUsersCardsInFile(SPIFFS, "/users.txt");
      if (msg[4].length() > 500)
      {
        bool test = true;
#ifdef FINGERPRINT_SM30
        test = camaSM30.saveTemplate(slot, msg[4], SM30_TEMPLATE_DATA_PACKET_LENGTH);
#endif
        if (test)
        {
          Serial << "Tempalte saved" << endl;
          confirmOkReceiving();
        }
        else
        {
          Serial << "Template not saved" << endl;
          confirmNokReceiving("Template not saved");
        }
      }
    }

    measureTime();
  }
  else if (msg[0].charAt(0) == '{')
  {
    // SET Room Hotel
    setRoomHotelGroup(payload);
  }
}

/*
 ######  ######## ######## ##     ## ########
##    ## ##          ##    ##     ## ##     ##
##       ##          ##    ##     ## ##     ##
 ######  ######      ##    ##     ## ########
      ## ##          ##    ##     ## ##
##    ## ##          ##    ##     ## ##
 ######  ########    ##     #######  ##
*/

/**
 * @brief SETUP
 */
void setup()
{
#ifdef SLEEPLESS
  insomnia = true;
#endif
  awake_timer = millis();

  // SET PINS
  initGPIO();
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_34, 1); // wakeup on proximity enable

  // Init serial ports
  Serial.begin(115200);

  // Init buffer
  for (byte i = 0; i < 6; i++)
  {
    key.keyByte[i] = 0xFF;
  }

  initSPI();

  // WAKEUP REASON
  int w_reason = print_wakeup_reason();
  if (w_reason == 0)
  {
    wakeup_on_reset();
  }
  else if (w_reason == 2)
  {
    wakeup_by_proxy();
  }
  else if (w_reason == 4)
  {
    wakeup_by_time();
    digitalWrite(ENABLE_PERIPHERY, LOW); // disable periphery
  }

  awake_timer = millis();
  //  STOP HERE
}

//    ###    ##      ##    ###    ##    ## ########     #######  ##    ##    ########  ########  ######  ######## ########
//   ## ##   ##  ##  ##   ## ##   ##   ##  ##          ##     ## ###   ##    ##     ## ##       ##    ## ##          ##
//  ##   ##  ##  ##  ##  ##   ##  ##  ##   ##          ##     ## ####  ##    ##     ## ##       ##       ##          ##
// ##     ## ##  ##  ## ##     ## #####    ######      ##     ## ## ## ##    ########  ######    ######  ######      ##
// ######### ##  ##  ## ######### ##  ##   ##          ##     ## ##  ####    ##   ##   ##             ## ##          ##
// ##     ## ##  ##  ## ##     ## ##   ##  ##          ##     ## ##   ###    ##    ##  ##       ##    ## ##          ##
// ##     ##  ###  ###  ##     ## ##    ## ########     #######  ##    ##    ##     ## ########  ######  ########    ##

void wakeup_on_reset()
{
  if (!SPIFFS.begin())
  {
    Serial.println(F("File system mount failed after reset!"));
    errorSound();
    goSleep();
  }
  loadConfiguration();

  Serial.println(F("Initialize after reset"));
  // Initial clock set.
  // TODO set time to last update
  if (config.today == 0)
  {
    time_clock.setTime(30, 18, 13, 3, 1, 2023); // 3th January 2023 13:18:30
  }

// Initialize for communication
// wakeup_by_time();
#ifndef OFFLINE
  activateTimeUpdate();
#endif
  goSleep(); // ??
}

//    ###    ##      ##    ###    ##    ## ########    ########  ##    ##    ######## #### ##     ## ########
//   ## ##   ##  ##  ##   ## ##   ##   ##  ##          ##     ##  ##  ##        ##     ##  ###   ### ##
//  ##   ##  ##  ##  ##  ##   ##  ##  ##   ##          ##     ##   ####         ##     ##  #### #### ##
// ##     ## ##  ##  ## ##     ## #####    ######      ########     ##          ##     ##  ## ### ## ######
// ######### ##  ##  ## ######### ##  ##   ##          ##     ##    ##          ##     ##  ##     ## ##
// ##     ## ##  ##  ## ##     ## ##   ##  ##          ##     ##    ##          ##     ##  ##     ## ##
// ##     ##  ###  ###  ##     ## ##    ## ########    ########     ##          ##    #### ##     ## ########

void wakeup_by_time()
{
  Serial << "Wakeup by time" << endl;
// check and align clock time
// corectTime();
// connectWiFi();
// update time
#ifdef NTP
  if (!corectTime())
  {
    activateTimeUpdate(); // Using NTP
  }
#endif
  // Alive - battery status
  connectWiFi();
  connectBroker();
  responseAlive();

  // Send event messages TODO

  // // Connect to MQTT
  // Serial.print(s + "Connecting to MQTT: " + config.mqtt + " ... ");
  // // TODO clientName.getBytes()
  // // client.connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage)
  // connectBroker();
  // sendLateMessage();
  awake_timer = millis();
  printLocalTime(); // for TEST
  // goSleep();        // ??
}

//    ###    ##      ##    ###    ##    ## ########     #######  ##    ##    ########   #######  ##     ## #### ##     ## #### ######## ##    ##
//   ## ##   ##  ##  ##   ## ##   ##   ##  ##          ##     ## ###   ##    ##     ## ##     ##  ##   ##   ##  ###   ###  ##     ##     ##  ##
//  ##   ##  ##  ##  ##  ##   ##  ##  ##   ##          ##     ## ####  ##    ##     ## ##     ##   ## ##    ##  #### ####  ##     ##      ####
// ##     ## ##  ##  ## ##     ## #####    ######      ##     ## ## ## ##    ########  ##     ##    ###     ##  ## ### ##  ##     ##       ##
// ######### ##  ##  ## ######### ##  ##   ##          ##     ## ##  ####    ##        ##     ##   ## ##    ##  ##     ##  ##     ##       ##
// ##     ## ##  ##  ## ##     ## ##   ##  ##          ##     ## ##   ###    ##        ##     ##  ##   ##   ##  ##     ##  ##     ##       ##
// ##     ##  ###  ###  ##     ## ##    ## ########     #######  ##    ##    ##         #######  ##     ## #### ##     ## ####    ##       ##

void wakeup_by_proxy()
{

  bool card_found = false;
  timeMeasure = millis();
  while (!card_found)
  {
    relayLoop();
    card_found = readCard();
    if (millis() - timeMeasure > 1000) // 1 seccond waiting for a card
      break;
  }
  awake_timer = millis();
  // Read card and open before wifi connection
  if (card_found)
  {
    if (tools.compareByteArrays(16, card_block2, config.update_code))
    {
      if (card_block6[15] == 0xFF)
      {
        activateAP();
      }
      else if (card_block6[15] == 0x00)
      {
        activateUpdate();
      }
      else if (card_block6[15] == 0x55)
      {
        activateTimeUpdate();
      }
      else if (card_block6[15] == 0xA5)
      {
        changeUpdateKey();
      }

      else if (card_block6[15] == 0xAA)
      {
        /* RESET COUNTERS */
        config.guest_count = 1;
        config.staff_count = 1;
        config.master_count = 1;
        saveConfiguration();
        setSound(200);
        delay(200);
        goSleep();
      }

      return;
    }
    else if (checkAccess())
    {
      // card number is in buffer (employee cards)
      Serial.println(F("open door "));
      // set bufer to send later
      to_send_late = true;
      // no need finger reading
      skip_sensor = true;
    }
    else
    {
      Serial.println(F("publish access denied"));
#ifndef SLEEPLESS
      goSleep();
#endif
    }
  }
  else
  {
    prepare_and_sleep();
  }

  // File System
  if (!SPIFFS.begin())
  {
    Serial.println(F("File system mount failed"));
    errorSound();
    return;
  }
}

//  ######  ##     ##    ###    ##    ##  ######   ########    ##    ## ######## ##    ##
// ##    ## ##     ##   ## ##   ###   ## ##    ##  ##          ##   ##  ##        ##  ##
// ##       ##     ##  ##   ##  ####  ## ##        ##          ##  ##   ##         ####
// ##       ######### ##     ## ## ## ## ##   #### ######      #####    ######      ##
// ##       ##     ## ######### ##  #### ##    ##  ##          ##  ##   ##          ##
// ##    ## ##     ## ##     ## ##   ### ##    ##  ##          ##   ##  ##          ##
//  ######  ##     ## ##     ## ##    ##  ######   ########    ##    ## ########    ##

void changeUpdateKey()
{
  Serial << "Changed Update Key to:";
  for (size_t i = 0; i < 16; i++)
  {
    // New update key from card
    config.update_code[i] = card_block1[i];
  }
  Serial << "Changed Update Key" << endl;
  if (!SPIFFS.begin(true))
  {
    Serial.println("An Error has occurred while mounting SPIFFS");
    errorSound();
    errorSound();
    esp_restart();
    return;
  }
  saveConfiguration();
  setSound(200);
  delay(200);
  goSleep();
}

void prepare_and_sleep()
{
  if ((config.today == 0) || (config.today != time_clock.getDay()))
  {
    uint64_t tm = time_for_sleep * 1000000; // timer in microseconds
    if (time_for_sleep > 1)
    {
      esp_sleep_enable_timer_wakeup(tm);
    }
  }
  goSleep();
}

/**
 * @brief Send buffered message
 *
 */
void sendLateMessage()
{
#ifdef MQTT_BROKER
  if (!to_send_late)
  {
    responseAlive();
    return;
  }
  //[MAC]/access/[slot]/[user_id]/[time]/[shift]
  if (!mqttClient.connected())
    connectBroker();
  if (access_late_user != 0)
  {
    publishUserAcces(access_late_user, access_late_card);
  }
  else
  {
    publishErrorCard(access_late_card);
  }
#endif
  to_send_late = false;
}

/**
 * @brief Set the Sound in milliseconds
 *
 * @param time
 */
void setSound(uint16_t time)
{
  digitalWrite(ZUMER, 1);
  startSound = millis() + time;
}

/**
 * @brief Open door
 *
 * @param milis
 */
void opendoor(int seconds)
{
  if (!digitalRead(DND))
  {
    if (!master_access)
    {
      errorSound();
      Serial.println("DND is set!");
      return;
    }
  }
  if (open_once)
    return;
  digitalWrite(LED_GREEN, 1);
  setSound(SOUND_OPEN);
  open_relay(seconds * 1000);
  door_opened = true;
  open_once = true;
}

/**
 * @brief Close door
 *
 * @param milis
 */
void closedoor()
{
  Serial.println("close door");
  door_opened = false;

#ifdef OLOCK
  digitalWrite(MOTOR_DIRECTION, 0);
#ifdef INVERSE_MOTOR
  digitalWrite(MOTOR_DIRECTION, 1);
#endif
  digitalWrite(MOTOR_ENABLE, 1);
  delay(MOTOR_DELAY);
  digitalWrite(MOTOR_ENABLE, 0);
  digitalWrite(MOTOR_DIRECTION, 0);
#ifdef INVERSE_MOTOR
  digitalWrite(MOTOR_DIRECTION, 1);
#endif
#endif
  digitalWrite(LED_GREEN, 0);
  digitalWrite(LED_RED, 0);
  digitalWrite(ZUMER, 0);

  if (ready_to_sleep)
  {
    Serial.println("sleep...");
    if (!insomnia)
    {
      prepare_and_sleep();
    }
  }
}

/**
 * @brief Sends error event to broker
 *
 */
void publishErrorCard(uint64_t card)
{
  //[MAC]/access/[slot]/[user_id]/[time]/[shift]
  ttime = time_clock.getTime("%d-%m-%Y %H:%M:%S").c_str();
  card = access_late_card;
  sprintf(out_message_buffer, "%s/error/%010llu/%s/%s", MAC_char, card, error_late.c_str(), ttime); // unknown user
  Serial << "Send message:" << out_message_buffer << endl;
  mqttClient.publish("event", 0, false, out_message_buffer);
}

/**
 * @brief Send success open to broker
 *
 * @param userId
 * @param card
 */
void publishUserAcces(uint64_t userId, uint64_t card)
{
#ifdef MQTT_BROKER
  //[MAC]/access/[slot]/[user_id]/[time]/[shift]
  if (userId == 0)
    return;
  if (!mqttClient.connected())
  {
    connectBroker();
  }

  // ttime = time_clock.getTime("%d-%m-%Y %H:%M:%S").c_str();
  String tm = time_clock.getTime("%d-%m-%Y %H:%M:%S");
  if (card > 200)
  {
    sprintf(out_message_buffer, "%s/%s/%010llu/%llu/%s/%u", MAC_char, EVENT_TYPE, card, userId, tm.c_str(), tools.calc_battery_percentage(analogRead(BATTERY_IN)));
  }
  else
  {
    sprintf(out_message_buffer, "%s/%s/%llu/%llu/%s/%u", MAC_char, EVENT_TYPE, card, userId, tm.c_str(), tools.calc_battery_percentage(analogRead(BATTERY_IN))); // unknown user
  }
  Serial << "Send message:" << out_message_buffer << endl;
  mqttClient.publish("event", 0, false, out_message_buffer);
#endif
  return;
}

/**
 * @brief Reading CARD and store its number in buffer
 *
 */
boolean readCard()
{
  // Clear buffer
  for (size_t i = 0; i < 16; i++)
  {
    card_block1[i] = 0;
    card_block2[i] = 0;
    card_block4[i] = 0;
    card_block5[i] = 0;
    card_block6[i] = 0;
  }
  // https://biodit.atlassian.net/wiki/spaces/OLOCK/pages/851969/Records+in+the+card
  // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle.
  if (!mfrc522.PICC_IsNewCardPresent())
  {
    // Serial << "-";
    mfrc522.PICC_HaltA();
    mfrc522.PCD_StopCrypto1();
    return false;
  }

  // Verify if the NUID has been readed
  if (!mfrc522.PICC_ReadCardSerial())
  {
    Serial << "no UID" << endl;
    mfrc522.PICC_HaltA();
    mfrc522.PCD_StopCrypto1();
    return false;
  }

  MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
  Serial.println(mfrc522.PICC_GetTypeName(piccType));
  if (piccType != MFRC522::PICC_TYPE_MIFARE_1K && piccType != MFRC522::PICC_TYPE_MIFARE_4K)
  {
    Serial.println(F("This reader only works with MIFARE Classic cards."));
    errorSound();
    // Halt PICC
    mfrc522.PICC_HaltA();
    // Stop encryption on PCD
    mfrc522.PCD_StopCrypto1();

    goSleep();

    return false;
  }

  // Store NUID into nuidPICC array
  for (byte i = 0; i < 4; i++)
  {
    nuidPICC[i] = mfrc522.uid.uidByte[i];
  }
  readBlock(1, card_block1);
  readBlock(2, card_block2);
  readBlock(4, card_block4);
  readBlock(5, card_block5);
  readBlock(6, card_block6);
  // readSector();
  // Halt PICC
  mfrc522.PICC_HaltA();
  // Stop encryption on PCD
  mfrc522.PCD_StopCrypto1();
  Serial << "find card" << endl;
  return true;
}

/**
 * @brief Validate CARD. Fast opening if card is valid
 *
 * @return boolean
 */
boolean checkAccess()
{
  uint64_t num;
  num = nuidPICC[3];
  num <<= 8;
  num += nuidPICC[2];
  num <<= 8;
  num += nuidPICC[1];
  num <<= 8;
  num += nuidPICC[0];
  sprintf(out_message_buffer, "%010llu", num);
  Serial << "Access card:" << num << endl;

  if (!corectTime())
  {
    // set time wakeup
    uint64_t tm = time_for_sleep * 1000000; // timer in microseconds
    if (time_for_sleep > 1)
    {
      esp_sleep_enable_timer_wakeup(tm);
    }
  }

  // Check offline
  uint64_t user_id = checkOfflineCardAccess();
  if (user_id != 0)
  {
    opendoor(config.relay_time);
#ifdef OFFLINE
    saveEvent(user_id);
#endif
#ifndef OFFLINE
    Serial << "need send late" << endl;
    access_late_user = user_id;
    access_late_card = num;
    to_send_late = true;
    saveEvent(user_id);
    return true;
#endif
    ready_to_sleep = true;
    return true;
  }

  access_late_card = num;
  errorSound();
  return false;
}

/**
 * @brief Stops sound after given period
 *
 */
void soundLoop()
{
  if (startSound > millis())
    return;
  // timer
  digitalWrite(ZUMER, 0);
}

/**
 * @brief Close relay after given period
 *
 */
void relayLoop()
{
  if (startRelay > millis())
    return;
  if (door_opened)
  {
    closedoor();
  }
}

/*
##     ##    ###    #### ##    ##    ##        #######   #######  ########
###   ###   ## ##    ##  ###   ##    ##       ##     ## ##     ## ##     ##
#### ####  ##   ##   ##  ####  ##    ##       ##     ## ##     ## ##     ##
## ### ## ##     ##  ##  ## ## ##    ##       ##     ## ##     ## ########
##     ## #########  ##  ##  ####    ##       ##     ## ##     ## ##
##     ## ##     ##  ##  ##   ###    ##       ##     ## ##     ## ##
##     ## ##     ## #### ##    ##    ########  #######   #######  ##
*/
/**
 * @brief Main loop
 *
 */
void loop()
{
  // if (isTimeToSleep() && !digitalRead(PROXIMITY))
  if (isTimeToSleep())
  {
    goSleep();
  }
  // if (insomnia)
  // {
  //   if (digitalRead(PROXIMITY))
  //   {
  //     digitalWrite(LED_RED, 1);
  //   }
  //   else
  //   {
  //     digitalWrite(LED_RED, 0);
  //   }

  //   // if (digitalRead(PRIVATE))
  //   // {
  //   //   digitalWrite(LED_GREEN, 1);
  //   // }
  //   // else
  //   // {
  //   //   digitalWrite(LED_GREEN, 0);
  //   // }
  // }
  cardLoop();
  soundLoop();
  relayLoop();
  messageLoop();
}

void cardLoop()
{
  if (readCard())
  {
    if (!checkAccess())
    {
      if (tools.compareByteArrays(16, card_block2, config.update_code))
      {
        activateUpdate();
        return;
      }
    }
    awake_timer = millis();
  }
}

/**
 * @brief Message loop
 *
 */
void messageLoop()
{
#ifdef MQTT_TIME
  if (topic_late.length() == 0 || message_late.length() == 0)
    return;
  mac_subscriber(topic_late, message_late);
#endif
  topic_late.clear();
  message_late.clear();
}

/**
 * @brief Write all users and cards to file
 *
 * @param fs
 * @param path
 * @param message
 */
void writeUsersCardsInFile(fs::FS &fs, const char *path)
{
  Serial.printf("Writing file: %s\n", path);

  File file = fs.open(path, FILE_WRITE);
  if (!file)
  {
    Serial.println(F("Failed to open file for writing"));
    return;
  }
  for (uint8_t i = 0; i < 100; i++)
  {
    /* code */
    if (cards[i] != 0)
    {
      sprintf(operations_buffer, "%i:%010llu:%llu", i, cards[i], users[i]);
      if (file.print(operations_buffer))
      {
        Serial.println("File written");
        file.println();
      }
      else
      {
        Serial.println("Write failed");
      }
    }
  }
  file.close();
}

/**
 * @brief Read file
 *
 * @param fs
 * @param path
 */
void readFile(fs::FS &fs, const char *path)
{
  Serial.printf("Reading file: %s\n", path);

  File file = fs.open(path);
  if (!file)
  {
    Serial.println("Failed to open file for reading");
    return;
  }
  awake_timer = millis();
  Serial.println("Read from file: ");
  while (file.available())
  {
    relayLoop();
    Serial.write(file.read());
  }
  Serial.println();
  file.close();
}

/**
 * @brief Read users and cards from file system to RTC_RAM
 * for spead up card recognition
 *
 */
void readUsers()
{
  Serial.printf("Restore from file: %s\n", "/users.txt");
  File file = SPIFFS.open("/users.txt");
  if (!file)
  {
    Serial.println("Failed to open file for reading");
    return;
  }
  Serial.println("Restore users");
  char buffer[64];
  while (file.available())
  {
    relayLoop();
    int l = file.readBytesUntil('\n', buffer, sizeof(buffer));
    buffer[l] = 0;
    writeUserToRAM(buffer);
  }
  Serial.println();
  file.close();
}

/**
 * @brief Parse line from file and set user and card in RTC_RAM
 *
 * @param buffer
 */
void writeUserToRAM(char *buffer)
{
  char *parsed;
  char *num;
  char *crd;
  char *usr;
  parsed = strtok(buffer, ":");
  if (parsed == NULL)
    return;
  num = parsed;
  parsed = strtok(NULL, ":");
  if (parsed == NULL)
    return;
  crd = parsed;
  parsed = strtok(NULL, ":");
  if (parsed == NULL)
    return;
  usr = parsed;

  uint8_t number = tools.StringTo64(num);
  uint64_t card = tools.StringTo64(crd);
  uint64_t user = tools.StringTo64(usr);
  if (number != 0)
  {
    cards[number] = card;
    users[number] = user;
  }
}

/**
 * @brief Delete file
 *
 * @param fs
 * @param path
 */
void deleteFile(fs::FS &fs, const char *path)
{
  Serial.printf("Deleting file: %s\n", path);
  if (fs.remove(path))
  {
    Serial.println("File deleted");
  }
  else
  {
    Serial.println("Delete failed");
  }
}

/**
 * @brief Print wake up reason
 *
 */
uint8_t print_wakeup_reason()
{
  uint8_t reason = 0;
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  // Serial << "Wakeup reason number:" << wakeup_reason << endl;
  // Serial << "Factory button state:" << digitalRead(FACTORY_RESET) << endl;
  switch (wakeup_reason)
  {
  case 1:
    reason = 1;
    Serial.println("Wakeup caused by external signal using RTC_IO");
    break;
  case 2:
    reason = 2;
    Serial.println("Wakeup caused by PROXIMITY");
    break;
  case 3:
    reason = 3;
    Serial.println("Wakeup caused by timer");
    break;
  case 4:
    reason = 4;
    Serial.println("Wakeup caused by TIME");
    break;
  case 5:
    reason = 5;
    Serial.println("Wakeup caused by ULP program");
    break;
  default:
    reason = 0;
    Serial.println("Wakeup on RESET");
    break;
  }
  return reason;
}

/*
   ###    ########
  ## ##   ##     ##
 ##   ##  ##     ##
##     ## ########
######### ##
##     ## ##
##     ## ##
*/

void activate_AP()
{
  factory_mode = true;
  // configure AP mode
  // activate watchdog for 3 minutes

  Serial.begin(115200);
  Serial.println();
  Serial.println("Factory reset ...");
  insomnia = true;
  if (!SPIFFS.begin(true))
  {
    Serial.println("An Error has occurred while mounting SPIFFS");
    return;
  }

  // You can remove the password parameter if you want the AP to be open.

  WiFi.softAP(config.apssid, config.appass);
  IPAddress myIP = WiFi.softAPIP();
  Serial.print(F("AP IP address: "));
  Serial.println(myIP);
  Serial.println("Server started");
  WiFi.macAddress(MAC_array);
  for (int i = 0; i < sizeof(MAC_array); ++i)
  {
    sprintf(MAC_char, "%s%02x", MAC_char, MAC_array[i]);
  }
  Serial.println(MAC_char);
  startServer();
}

/*
##      ## ######## ########
##  ##  ## ##       ##     ##
##  ##  ## ##       ##     ##
##  ##  ## ######   ########
##  ##  ## ##       ##     ##
##  ##  ## ##       ##     ##
 ###  ###  ######## ########
*/

/**
 * @brief Start WEB server
 *
 */
void startServer()
{
  insomnia = true;
  setSound(400);
  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/index.html", String(), false, processor); });

  server.on("/list", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/list.html", String(), false, processor); });

  server.on("/events", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/log.txt", String(), false, processor); });

  server.on("/log.txt", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/log.txt", "text/css"); });

  // Route to load style.css file
  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/style.css", "text/css"); });

  // Send a POST request to <IP>/post with a form field message set to <message>
  server.on("/config", HTTP_POST, [](AsyncWebServerRequest *request)
            {
			  String message = "";
			  String param;
			  config.dhcp=1;
			  config.sound=1;
			  if (request->hasParam(PARAM_NAME, true))
			  {
				param = request->getParam(PARAM_NAME, true)->value();
				message += "NAME:";
				message += param;
				message += "\r\n";
				strlcpy(config.name, param.c_str(), sizeof(config.name));
			  }
			  if(request->hasParam(PARAM_AP_PASS,true))
			  {
				param = request->getParam(PARAM_AP_PASS, true)->value();
				message += "AP_PASS:";
				message += param;
				message += "\r\n";
				strlcpy(config.appass, param.c_str(), sizeof(config.appass));
			  }
			  if (request->hasParam(PARAM_SSID, true))
			  {
				param = request->getParam(PARAM_SSID, true)->value();
				message += "SSID:";
				message += param;
				message += "\r\n";
				strlcpy(config.ssid, param.c_str(), sizeof(config.ssid));
			  }
			  if (request->hasParam(PARAM_PASS, true))
			  {
				param = request->getParam(PARAM_PASS, true)->value();
				message += "PASS:";
				message += param;
				message += "\r\n";
				strlcpy(config.pass, param.c_str(), sizeof(config.pass));
			  }
			  if (request->hasParam(PARAM_AP_SSID, true))
			  {
				param = request->getParam(PARAM_AP_SSID, true)->value();
				message += "AP_SSID:";
				message += param;
				message += "\r\n";
				strlcpy(config.apssid, param.c_str(), sizeof(config.apssid));
			  }
			  if (request->hasParam(PARAM_MQTT, true))
			  {
				param = request->getParam(PARAM_MQTT, true)->value();
				message += "MQTT:";
				message += param;
				message += "\r\n";
				strlcpy(config.mqtt, param.c_str(), sizeof(config.mqtt));
			  }
			  if (request->hasParam(PARAM_MQTT_PORT, true))
			  {
				param = request->getParam(PARAM_MQTT_PORT, true)->value();
				message += "MQTT_PORT:";
				message += param;
				message += "\r\n";
				config.mqtt_port = tools.StringTo64(param);
			  }
			   if (request->hasParam(PARAM_MQTT_USER, true))
			  {
				param = request->getParam(PARAM_MQTT_USER, true)->value();
				message += "MQTT_USER:";
				message += param;
				message += "\r\n";
				strlcpy(config.mqtt_user, param.c_str(), sizeof(config.mqtt_user));
			  }
			  if (request->hasParam(PARAM_MQTT_USER_PASS, true))
			  {
				param = request->getParam(PARAM_MQTT_USER_PASS, true)->value();
				message += "MQTT_USER_PASS:";
				message += param;
				message += "\r\n";
				strlcpy(config.mqtt_user_pass, param.c_str(), sizeof(config.mqtt_user_pass));
			  }
			  if (request->hasParam(PARAM_DHCP, true))
			  {
				param = request->getParam(PARAM_DHCP, true)->value();
				message += "DHCP:";
				message += param;
				message += "\r\n";
				config.dhcp = 1;
			  }              
			  if (request->hasParam(PARAM_IP, true))
			  {
				param = request->getParam(PARAM_IP, true)->value();
				message += "IP:";
				message += param;
				message += "\r\n";
				strlcpy(config.ip_address, param.c_str(), sizeof(config.ip_address));
			  }
			  if (request->hasParam(PARAM_MASK, true))
			  {
				param = request->getParam(PARAM_MASK, true)->value();
				message += "MASK:";
				message += param;
				message += "\r\n";
				strlcpy(config.ip_mask, param.c_str(), sizeof(config.ip_mask));
			  }
			  if (request->hasParam(PARAM_GATEWAY, true))
			  {
				param = request->getParam(PARAM_GATEWAY, true)->value();
				message += "GATEWAY:";
				message += param;
				message += "\r\n";
				strlcpy(config.gateway, param.c_str(), sizeof(config.gateway));
			  }
			  if (request->hasParam(PARAM_TYPE, true))
			  {
				param = request->getParam(PARAM_TYPE, true)->value();
				message += "TYPE:";
				message += param;
				message += "\r\n";
				strlcpy(config.type, param.c_str(), sizeof(config.type));
			  }
			  if (request->hasParam(PARAM_RELAY, true))
			  {
				param = request->getParam(PARAM_RELAY, true)->value();
				message += "RELAY:";
				message += param;
				message += "\r\n";
				config.relay_time = tools.StringTo64(param);
			  }
			  if (request->hasParam(PARAM_EVENT, true))
			  {
				param = request->getParam(PARAM_EVENT, true)->value();
				message += "EVENT:";
				message += param;
				message += "\r\n";
				strlcpy(config.event, param.c_str(), sizeof(config.event));
			  }
			  if (request->hasParam(PARAM_AP_SSID, true))
			  {
				param = request->getParam(PARAM_AP_SSID, true)->value();
				message += "APSSID:";
				message += param;
				message += "\r\n";
				strlcpy(config.apssid, param.c_str(), sizeof(config.apssid));
			  }
			   if (request->hasParam(PARAM_GROUP, true))
			  {
				param = request->getParam(PARAM_GROUP, true)->value();
				message += "GROUP:";
				message += param;
				message += "\r\n";
				config.group = (uint64_t) tools.StringTo64(param);
			  }
			  if (request->hasParam(PARAM_HOTEL, true))
			  {
				param = request->getParam(PARAM_HOTEL, true)->value();
				message += "HOTEL:";
				message += param;
				message += "\r\n";
				config.hotel_code = tools.StringTo64(param);
			  }
			  if (request->hasParam(PARAM_ROOM, true))
			  {
				param = request->getParam(PARAM_ROOM, true)->value();
				message += "ROOM:";
				message += param;
				message += "\r\n";
				config.room = tools.StringTo64(param);
			  }
			  if (request->hasParam(PARAM_SOUND, true))
			  {
				param = request->getParam(PARAM_SOUND, true)->value();
				message += "SOUND:";
				message += param;
				message += "\r\n";
				config.sound = 1;
			  }
			 
			  Serial << message << endl;
			  request->send(200, "text/plain", message);

			  saveConfiguration(); 
			  restart(); });

  server.on("/clear", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/confirm.html", String(), false, processor); 
  clearLog();
  soundExit();
  restart(); });

  server.on("/exit", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/confirm.html", String(), false, processor); 
            soundExit();
            restart(); });

  // server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request)
  //           { update(request); });

  // MDNS.begin(host);
  // MDNS.addService("http", "tcp", 80);

  // Start server
  AsyncElegantOTA.begin(&server);
  server.begin();
}

void soundExit()
{
  digitalWrite(ZUMER, 1);
  delay(100);
  digitalWrite(ZUMER, 0);
}

/**
 * @brief Updates state in html page
 *
 * @param var
 * @return String
 */
String processor(const String &var)
{
  Serial.println(var);
  if (var == "ap_ssid")
  {
    return config.apssid;
  }
  else if (var == "ssid")
  {
    return config.ssid;
  }
  else if (var == "pass")
  {
    return config.pass;
  }
  else if (var == "ip_address")
  {
    return config.ip_address;
  }
  else if (var == "event")
  {
    return config.event;
  }
  else if (var == "ip_mask")
  {
    return config.ip_mask;
  }
  else if (var == "name")
  {
    return config.name;
  }
  else if (var == "relay_time")
  {
    return String(config.relay_time, DEC);
  }
  else if (var == "mqtt")
  {
    return config.mqtt;
  }
  else if (var == "mqtt_port")
  {
    return String(config.mqtt_port, DEC);
  }
  else if (var == "sound")
  {
    if (config.sound)
    {
      return "true";
    }
    return "false";
  }
  else if (var == "dhcp")
  {
    if (config.dhcp == 1)
    {
      return "true";
    }
    return "false";
  }
  return String();
}

// ##        #######     ###    ########      ######   #######  ##    ## ######## ####  ######   ##     ## ########     ###    ######## ####  #######  ##    ##
// ##       ##     ##   ## ##   ##     ##    ##    ## ##     ## ###   ## ##        ##  ##    ##  ##     ## ##     ##   ## ##      ##     ##  ##     ## ###   ##
// ##       ##     ##  ##   ##  ##     ##    ##       ##     ## ####  ## ##        ##  ##        ##     ## ##     ##  ##   ##     ##     ##  ##     ## ####  ##
// ##       ##     ## ##     ## ##     ##    ##       ##     ## ## ## ## ######    ##  ##   #### ##     ## ########  ##     ##    ##     ##  ##     ## ## ## ##
// ##       ##     ## ######### ##     ##    ##       ##     ## ##  #### ##        ##  ##    ##  ##     ## ##   ##   #########    ##     ##  ##     ## ##  ####
// ##       ##     ## ##     ## ##     ##    ##    ## ##     ## ##   ### ##        ##  ##    ##  ##     ## ##    ##  ##     ##    ##     ##  ##     ## ##   ###
// ########  #######  ##     ## ########      ######   #######  ##    ## ##       ####  ######    #######  ##     ## ##     ##    ##    ####  #######  ##    ##

/**
 * @brief Load configuration file
 *
 */
void loadConfiguration()
{
  File file = SPIFFS.open(CONNFIG_FILE);
  if (!file)
  {
    Serial.println("Failed to open file for reading");
    return;
  }
  Serial.println("Config file loaded:");

#ifdef DEBUG
  filesys.printFile(SPIFFS, CONNFIG_FILE);
#endif

  StaticJsonDocument<2048> doc;
  // Deserialize the JSON document
  DeserializationError error = deserializeJson(doc, file);
  file.close();
  if (error == error.IncompleteInput)
  {
    Serial << "incomplete" << endl;
  }
  if (error == error.InvalidInput)
  {
    Serial << "invaliv input" << endl;
  }
  if (error == error.EmptyInput)
  {
    Serial << "empty input" << endl;
  }
  if (error == error.NoMemory)
  {
    Serial << "no memory" << endl;
  }
  if (error == error.TooDeep)
  {
    Serial << "too deep" << endl;
  }
  if (error == error.Ok)
  {
    Serial << "OK" << endl;
  }

  if (error)
  {
    Serial.println(F("Deserialization Error"));
    // factory_reset();
    setSound(150);
    return;
  }

  // Copy values from the JsonDocument to the Config
  config.dhcp = doc["dhcp"] | 1;
  strlcpy(config.event,                                                                           // <- destination
          doc["event"] | "access",                                                                // <- source
          sizeof(config.event));                                                                  // <- destination's capacity
  strlcpy(config.ip_address, doc["ip_address"] | "192.168.1.100", sizeof(config.ip_address) + 1); // AP
  strlcpy(config.ip_mask, doc["ip_mask"] | "255.255.255.0", sizeof(config.ip_mask) + 1);
  strlcpy(config.mqtt, doc["mqtt"] | "192.168.1.15", sizeof(config.mqtt) + 1);
  config.mqtt_port = doc["mqtt_port"] | 1883;
  strlcpy(config.pass, doc["pass"] | "Biodit234", sizeof(config.pass) + 1);
  config.relay_time = doc["relay_time"] | 3;
  config.sound = doc["sound"] | 1;
  strlcpy(config.ssid, doc["ssid"] | "BIODIT", sizeof(config.ssid) + 1);
  strlcpy(config.apssid, doc["apssid"] | "Olock", sizeof(config.apssid) + 1);
  strlcpy(config.type, doc["type"] | "olock", sizeof(config.type) + 1);
  strlcpy(config.name, doc["name"] | "Room", sizeof(config.name) + 1);
  strlcpy(config.appass, doc["appass"] | "biodit2021", sizeof(config.pass) + 1);
  strlcpy(config.mqtt_user, doc["mqtt_user"] | "olock", sizeof(config.mqtt_user) + 1);
  strlcpy(config.mqtt_user_pass, doc["mqtt_user_pass"] | "pass", sizeof(config.mqtt_user_pass) + 1);
  strlcpy(config.aes_key, doc["aes_key"] | "xX17y#y%qhvMjt&9", sizeof(config.aes_key) + 1);
  config.hotel_code = doc["hotel_code"] | 1;
  config.room = doc["room"] | 1;
  // uint32_t groupl = doc["group_L"] | 1;
  // uint32_t grouph = doc["group_H"] | 0;
  // Serial << "gH:" << grouph << endl;
  // Serial << "gL:" << groupl << endl;
  // config.group = grouph;
  // config.group <<= 32;
  // config.group += groupl;
  char grps[20];
  strlcpy(grps, doc["group"] | "1", sizeof(grps) + 1);
  config.group = tools.StringTo64(grps);
  Serial << "Group in file:" << config.group << endl;
  // if (config.group == 0)
  // {
  //   config.group = doc["group"] | 1;
  // }

  config.guest_count = doc["guest_count"] | 1;
  config.staff_count = doc["staff_count"] | 1;
  config.master_count = doc["master_count"] | 1;
  strlcpy(config.update_code, doc["update_code"] | "t3o6z7fryx3lrt1c", sizeof(config.update_code) + 1);
}

/**
 * @brief Saves the configuration to a file
 *
 * @param filename
 * @param config
 */
void saveConfiguration()
{
  if (!SPIFFS.begin(true))
  {
    Serial.println("An Error has occurred while mounting SPIFFS");
    errorSound();
    errorSound();
    esp_restart();
    return;
  }
  // Delete existing file, otherwise the configuration is appended to the file
  SPIFFS.remove(CONNFIG_FILE);

  // Open file for writing
  File file = SPIFFS.open(CONNFIG_FILE, FILE_WRITE);
  if (!file)
  {
    Serial.println(F("Failed to create file"));
    return;
  }

  // Allocate a temporary JsonDocument
  // Don't forget to change the capacity to match your requirements.
  // Use arduinojson.org/assistant to compute the capacity.
  StaticJsonDocument<1024> doc;

  // Set the values in the document
  doc["dhcp"] = config.dhcp;
  doc["event"] = config.event;
  doc["ip_address"] = config.ip_address;
  doc["ip_mask"] = config.ip_mask;
  doc["mqtt"] = config.mqtt;
  doc["mqtt_port"] = config.mqtt_port;
  doc["pass"] = config.pass;
  doc["relay_time"] = config.relay_time;
  doc["sound"] = config.sound;
  doc["ssid"] = config.ssid;
  doc["type"] = config.type;
  doc["apssid"] = config.apssid;
  doc["appass"] = config.appass;
  doc["name"] = config.name;
  doc["device_mac"] = MAC_char;
  doc["mqtt_user"] = config.mqtt_user;
  doc["mqtt_user_pass"] = config.mqtt_user_pass;
  doc["hotel_code"] = config.hotel_code;
  doc["gateway"] = config.gateway;
  doc["room"] = config.room;
  char grps[20];
  sprintf(grps, "%llu", config.group);
  doc["group"] = grps;
  Serial << "for save group:" << grps << endl;
  // uint32_t groupl = (uint32_t)config.group;
  // uint32_t grouph = (uint32_t)(config.group >> 32);
  // Serial << "for save gH:" << grouph << endl;
  // Serial << "for save gL:" << groupl << endl;
  // doc["group_L"] = groupl;
  // doc["group_H"] = grouph;
  doc["guest_count"] = config.guest_count;
  doc["staff_count"] = config.staff_count;
  doc["master_count"] = config.master_count;
  doc["update_code"] = config.update_code;

  // Serialize JSON to file
  if (serializeJson(doc, file) == 0)
  {
    Serial.println(F("Failed to write to file"));
  }
  else
  {
    Serial.println("Config file saved");
  }
  // Close the file
  file.close();
  filesys.printFile(SPIFFS, CONNFIG_FILE);
}

/**
 * @brief Check if time for sleep
 *
 * @return true
 * @return false
 */
bool isTimeToSleep()
{
  if (insomnia)
  {
    return false;
  }
  return (millis() - awake_timer > AWAKE_TIME);
}

// ######## #### ##    ##  ######   ######## ########     ##        #######   #######  ########
// ##        ##  ###   ## ##    ##  ##       ##     ##    ##       ##     ## ##     ## ##     ##
// ##        ##  ####  ## ##        ##       ##     ##    ##       ##     ## ##     ## ##     ##
// ######    ##  ## ## ## ##   #### ######   ########     ##       ##     ## ##     ## ########
// ##        ##  ##  #### ##    ##  ##       ##   ##      ##       ##     ## ##     ## ##
// ##        ##  ##   ### ##    ##  ##       ##    ##     ##       ##     ## ##     ## ##
// ##       #### ##    ##  ######   ######## ##     ##    ########  #######   #######  ##

void fingerLoop()
{
  if (door_opened)
  {
    return;
  }
  timeMeasure = millis();
#ifdef FINGERPRINT_SM30
  uint16_t detect = camaSM30.detectFinger();
  if (detect == 0)
  {
    return;
  }
  else if (detect == 0xFF)
  {
    Serial << "communication problem" << endl;
  }
  digitalWrite(ZUMER, 1);
  delay(10);
  digitalWrite(ZUMER, 0);

  uint16_t found = camaSM30.identify();

  Serial << "detect:" << found << endl;
  if (found == 0xFFFF)
  {
    Serial.println(F("Sensor error. Device needs restart"));
    camaSM30.cancel();
    digitalWrite(ZUMER, 1);
    // TODO If no response - RESTART SENSOR
    // digitalWrite(ENABLE_PERIPHERY, 0);
    delay(300);
    // digitalWrite(ENABLE_PERIPHERY, 1);
    digitalWrite(ZUMER, 0);
  }
  else if (found > 0)
  {
    // Serial.println(F("Detect"));
    setSound(5);
    Serial << "found:" << found << endl;
    opendoor(config.relay_time);
    publishUserAcces(users[found], found);
    setSound(20);
    delay(200);
  }
  else if (found == 0)
  {
    publishUserAcces(0, 0);
    errorSound();
  }

#endif

#ifdef FINGERPRINT_SM25
  camaSM30.ledON();
  // Serial.println("Finger detect");
  //  int found = camaSM30.identify();
  //  if (found != 0)
  //  {
  //    Serial << "found:" << found << "user:" << users[found] << endl;
  //    if (users[found] != 0)
  //    {
  //      opendoor(config.relay_time);
  //    }
  //    Serial << "relay time:" << config.relay_time << endl;
  //    if (mqttClient.connected())
  //    {
  //      publishUserAcces(users[found], found);
  //    }
  //    else
  //    {
  //      access_late_user = users[found];
  //      access_late_card = found;
  //    }
  //  }
  //  else
  //  {
  //    errorSound();
  //    if (mqttClient.connected())
  //    {
  //      publishErrorCard(0);
  //    }
  //  }
#endif
// TODO
#ifdef FINGERPRINT_AFM31
  if (camaSM30.detectFinger())
  {
    digitalWrite(ZUMER, 1);
    delay(20);
    digitalWrite(ZUMER, 0);
    // Serial.println("Finger detect");
    int found = camaSM30.identify();
    if (found != 0)
    {
      Serial << "found:" << found << "user:" << users[found] << endl;
      if (users[found] != 0)
      {
        opendoor(config.relay_time);
      }
      Serial << "relay time:" << config.relay_time << endl;
      if (mqttClient.connected())
      {
        publishUserAcces(users[found], found);
      }
      else
      {
        access_late_user = users[found];
        access_late_card = found;
      }
    }
    else
    {
      errorSound();
      if (mqttClient.connected())
      {
        publishErrorCard(0);
      }
    }
  }
#endif
}

void setConfig(const String &var)
{
  // Allocate a temporary JsonDocument
  // Don't forget to change the capacity to match your requirements.
  // Use arduinojson.org/assistant to compute the capacity.
  StaticJsonDocument<1024> doc;
  DeserializationError error = deserializeJson(doc, var.c_str());

  if (error)
  {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return;
  }

  config.hotel_code = doc["hotel_code"];
  config.room = doc["room"];
  uint32_t groupl = doc["group_L"];
  uint32_t grouph = doc["group_H"];
  Serial << "for save gH:" << grouph << endl;
  Serial << "for save gL:" << groupl << endl;
  config.group = grouph;
  config.group <<= 32;
  config.group += groupl;
  Serial << "Group in message:" << config.group << endl;
  saveConfiguration();
}

// ########    ###     ######  ########  #######  ########  ##    ##    ########  ########  ######  ######## ########
// ##         ## ##   ##    ##    ##    ##     ## ##     ##  ##  ##     ##     ## ##       ##    ## ##          ##
// ##        ##   ##  ##          ##    ##     ## ##     ##   ####      ##     ## ##       ##       ##          ##
// ######   ##     ## ##          ##    ##     ## ########     ##       ########  ######    ######  ######      ##
// ##       ######### ##          ##    ##     ## ##   ##      ##       ##   ##   ##             ## ##          ##
// ##       ##     ## ##    ##    ##    ##     ## ##    ##     ##       ##    ##  ##       ##    ## ##          ##
// ##       ##     ##  ######     ##     #######  ##     ##    ##       ##     ## ########  ######  ########    ##

/**
 * @brief Factory reset
 *
 */
void factory_reset()
{
  Serial << "Full reset" << endl;
  strlcpy(config.name, "name", sizeof(config.name));
  config.room = 1;
  config.group = 1;
  config.hotel_code = 1;
  strlcpy(config.update_code, "t3o6z7fryx3lrt1c", sizeof(config.update_code));
  strlcpy(config.apssid, "Olock", sizeof(config.apssid));
  strlcpy(config.appass, "biodit2022", sizeof(config.appass));
  strlcpy(config.pass, "4ETj5dPy", sizeof(config.pass));
  strlcpy(config.ssid, "biodit_sys", sizeof(config.ssid));
  strlcpy(config.event, "access", sizeof(config.event));
  strlcpy(config.ip_address, "192.168.1.19", sizeof(config.ip_address));
  strlcpy(config.ip_mask, "255.255.255.0", sizeof(config.ip_mask));
  strlcpy(config.mqtt, "192.168.1.15", sizeof(config.mqtt));
  strlcpy(config.gateway, "192.168.1.1", sizeof(config.mqtt));
  strlcpy(config.mqtt_user, "olock", sizeof(config.mqtt_user));
  strlcpy(config.mqtt_user_pass, "pass", sizeof(config.mqtt_user_pass));
  strlcpy(config.aes_key, "xX17y#y%qhvMjt&9", sizeof(config.aes_key));
  config.dhcp = 1;
  config.sound = 1;
  config.mqtt_port = 1885;
  config.relay_time = 3;
  saveConfiguration();
  setSound(200);
  delay(200);
  time_clock.setTime(30, 18, 13, 10, 1, 2022); // 10th Jan 2022 13:18:30
  // restart();
}

void restart()
{
  insomnia = false;
  goSleep();
}

// MQTT TESTS
void onMqttPublish(uint16_t packetId)
{
  Serial.println("Publish acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}

void connectToMqtt()
{
  Serial.println("Connecting to MQTT...");
  if (!mqttClient.connected())
  {
    connectBroker();
  }
}

void onMqttUnsubscribe(uint16_t packetId)
{
  Serial.println("Unsubscribe acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
  Serial.println("Disconnected from MQTT.");

  if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT)
  {
    Serial.println("Bad server fingerprint.");
  }
  else if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED)
  {
    Serial.println("TCP_DISCONNECTED");
  }
  else if (reason == AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION)
  {
    Serial.println("MQTT_UNACCEPTABLE_PROTOCOL_VERSION");
  }
  else if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED)
  {
    Serial.println("MQTT_IDENTIFIER_REJECTED");
  }
  else if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE)
  {
    Serial.println("MQTT_SERVER_UNAVAILABLE");
  }
  else if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS)
  {
    Serial.println("MQTT_MALFORMED_CREDENTIALS");
  }
  else if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED)
  {
    Serial.println("MQTT_NOT_AUTHORIZED");
  }
  else if (reason == AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE)
  {
    Serial.println("ESP8266_NOT_ENOUGH_SPACE");
  }
  // goSleep();
  //  if (WiFi.isConnected())
  //  {
  //      mqttReconnectTimer.once(2, connectToMqtt);
  //  }
}

void onMqttConnect(bool sessionPresent)
{
  mqttClient.subscribe("all", 0);
  awake_timer = millis();
  Serial.println("Connected to MQTT.");
}

void initSPI()
{
  // Init SPI bus
  SPI.begin();
  mfrc522.PCD_Init(); // Init MFRC522 card
  mfrc522.PCD_SoftPowerUp();
}

//  ######   ########  ####  #######
// ##    ##  ##     ##  ##  ##     ##
// ##        ##     ##  ##  ##     ##
// ##   #### ########   ##  ##     ##
// ##    ##  ##         ##  ##     ##
// ##    ##  ##         ##  ##     ##
//  ######   ##        ####  #######

void initGPIO()
{
  pinMode(DND, INPUT);
  pinMode(ZUMER, OUTPUT);

#ifdef OLOCK
  pinMode(MOTOR_DIRECTION, OUTPUT);
  pinMode(MOTOR_ENABLE, OUTPUT);
  pinMode(ENABLE_PERIPHERY, OUTPUT);
  pinMode(PRIVATE, INPUT);
#endif
  pinMode(PROXIMITY, INPUT);
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(FACTORY_RESET, INPUT);
  pinMode(BATTERY_IN, ANALOG);
  pinMode(BATTERY_ENABLE, OUTPUT);
  digitalWrite(BATTERY_ENABLE, HIGH); // enable measuring

  // closedoor();
#ifdef OLOCK
  digitalWrite(ENABLE_PERIPHERY, HIGH); // enable periphery
  // digitalWrite(ENABLE_PERIPHERY, LOW); // disable for tests periphery
#endif
}

// ##      ## #### ######## ####
// ##  ##  ##  ##  ##        ##
// ##  ##  ##  ##  ##        ##
// ##  ##  ##  ##  ######    ##
// ##  ##  ##  ##  ##        ##
// ##  ##  ##  ##  ##        ##
//  ###  ###  #### ##       ####

void connectWiFi()
{
  if (WiFi.isConnected())
    return;
  WiFi.disconnect();
  // Connect to WiFi
  char host[16];
  sprintf(host, "room_%d", config.room);
  WiFi.setHostname(host);
  Serial.print(s + "Connecting to WiFi: " + config.ssid + " ");
  // if (config.dhcp == 1)
  // {
  WiFi.begin(config.ssid, config.pass);
  // }
  // else
  // {
  //   local_IP.fromString(config.ip_address);
  //   gateway.fromString(config.gateway);
  //   subnet.fromString(config.ip_mask);
  //   if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS))
  //   {
  //     Serial.println("STA Failed to configure");
  //   }
  //   else
  //   {
  //     Serial << "Static IP" << endl;
  //     Serial << local_IP << ", " << gateway << ", " << subnet << ", " << primaryDNS << ", " << secondaryDNS << endl;
  //     Serial << WiFi.localIP() << ", " << WiFi.gatewayIP() << ", " << WiFi.subnetMask() << ", " << WiFi.dnsIP() << endl;
  //     WiFi.begin(config.ssid, config.pass);
  //   }
  // }

  awake_timer = millis();
  while (WiFi.status() != WL_CONNECTED)
  {
    relayLoop();
    cardLoop();
    // TODO set timeout to go sleep
    delay(100);
    digitalWrite(ZUMER, 0); // stop sound
    Serial.print(".");
    // restarts if no Wifi connection
    if (millis() - awake_timer > WIFI_TIME)
    {
      Serial.println(F("Can't connect to WiFi"));
      goSleep();
    }
  }

  Serial.println(F("connected"));
  // GET MAC
  WiFi.macAddress(MAC_array);
  for (int i = 0; i < sizeof(MAC_array); ++i)
  {
    sprintf(MAC_char, "%s%02x", MAC_char, MAC_array[i]);
  }

  // mymac = MAC_char;
  // Set client name
  // Olock_240ac40457d0
  // sprintf(clientName, "Olock_%s", MAC_char);
  // Serial << "set MQTT client name:" << clientName << endl;
  IP = WiFi.localIP();
}

void goTest()
{
  printLocalTime();
  measureTime();
  // TEST
  time_for_sleep = 10; // 3 sec for test
  goSleep();
}

void printLocalTime()
{
  Serial << time_clock.getDateTime(false) << endl;
  Serial << time_clock.getEpoch() << endl;
}

void readBlock(byte block, byte *buffer)
{
  byte len = 18;
  MFRC522::StatusCode status;
  // byte buffer1[18];
  // Serial<<"block: "<<block<<endl;
  status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block, &key, &(mfrc522.uid)); // line 834 of MFRC522.cpp file
  if (status != MFRC522::STATUS_OK)
  {
    Serial.print(F("Authentication failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  status = mfrc522.MIFARE_Read(block, buffer, &len);
  if (status != MFRC522::STATUS_OK)
  {
    Serial.print(F("Reading failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }
}

/**
 * @brief Reads all users slot
 *
 * @param user
 * @param slots
 * @return true
 * @return false
 */
bool readUserSlots(uint64_t user, uint8_t *slots)
{
  Serial.printf("Read from file: %s\n", "/users.txt");
  File file = SPIFFS.open("/users.txt");
  if (!file)
  {
    Serial.println("Failed to open file for reading");
    return false;
  }

  uint8_t slot_num = 0;
  char buffer[64];
  while (file.available())
  {
    relayLoop();
    int l = file.readBytesUntil('\n', buffer, sizeof(buffer));
    buffer[l] = 0;
    int slt = getUserSlot(buffer, user);
    if (slt != 0)
    {
      slots[slot_num++] = slt;
    }
  }
  Serial.println();
  file.close();
  return true;
}

/**
 * @brief Get the User Slot object
 *
 * @param buffer
 * @param user
 * @return uint16_t
 */
uint16_t getUserSlot(char *buffer, uint64_t user)
{
  char *parsed;
  char *num;
  // char *crd;
  char *usr;
  parsed = strtok(buffer, ":");
  if (parsed == NULL)
    return 0;
  num = parsed;
  parsed = strtok(NULL, ":");
  if (parsed == NULL)
    return 0;
  // crd = parsed;
  parsed = strtok(NULL, ":");
  if (parsed == NULL)
    return 0;
  usr = parsed;
  uint8_t number = tools.StringTo64(num);
  // uint64_t card = tools.StringTo64(crd);
  uint64_t n_user = tools.StringTo64(usr);
  Serial << "Found user " << user << " in slot:" << number << endl;
  Serial << n_user << endl;
  if (user == n_user)
  {
    return number;
  }
  return 0;
}

void test()
{
  // TEST AES

  // char *input = "Tech tutorials x";
  unsigned char output[16];
  decrypt((char *)card_block1, config.aes_key, output);
  for (size_t i = 0; i < 16; i++)
  {
    /* code */
    Serial.print(tools.byteToHex(output[i]));
  }
  Serial.println();
  decrypt((char *)card_block2, config.aes_key, output);
  for (size_t i = 0; i < 16; i++)
  {
    /* code */
    Serial.print(tools.byteToHex(output[i]));
  }
  Serial.println();
  Serial.println("key:");
  for (size_t j = 0; j < 16; j++)
  {
    Serial.print(tools.byteToHex(config.aes_key[j]));
  }
  Serial.println();

  // unsigned char decrypted[16];
  // decrypt(output,aes_key,decrypted);
  // for (size_t i = 0; i < 16; i++)
  // {
  //   /* code */
  //   Serial.print(tools.byteToHex(decrypted[i]));
  // }
  // Serial.println();
}

void encrypt(char *plainText, const char *key, unsigned char *outputBuffer)
{

  mbedtls_aes_context aes;

  mbedtls_aes_init(&aes);
  mbedtls_aes_setkey_enc(&aes, (const unsigned char *)key, strlen(key) * 8);
  mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, (const unsigned char *)plainText, outputBuffer);
  mbedtls_aes_free(&aes);
}

void decrypt(char *chipherText, const char *key, unsigned char *outputBuffer)
{

  mbedtls_aes_context aes;

  mbedtls_aes_init(&aes);
  mbedtls_aes_setkey_dec(&aes, (const unsigned char *)key, strlen(key) * 8);
  mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_DECRYPT, (const unsigned char *)chipherText, outputBuffer);
  mbedtls_aes_free(&aes);
}

//  #######  ######## ######## ##       #### ##    ## ########     ######     ###    ########  ########
// ##     ## ##       ##       ##        ##  ###   ## ##          ##    ##   ## ##   ##     ## ##     ##
// ##     ## ##       ##       ##        ##  ####  ## ##          ##        ##   ##  ##     ## ##     ##
// ##     ## ######   ######   ##        ##  ## ## ## ######      ##       ##     ## ########  ##     ##
// ##     ## ##       ##       ##        ##  ##  #### ##          ##       ######### ##   ##   ##     ##
// ##     ## ##       ##       ##        ##  ##   ### ##          ##    ## ##     ## ##    ##  ##     ##
//  #######  ##       ##       ######## #### ##    ## ########     ######  ##     ## ##     ## ########

/**
 * @brief Check offline card access
 *
 * @return uint16_t
 */

uint64_t checkOfflineCardAccess()
{
  for (byte i = 0; i < 4; i++)
  {
    nuidPICC[i] = mfrc522.uid.uidByte[i];
  }
  Serial << tools.byteToHex(nuidPICC[0]) << tools.byteToHex(nuidPICC[1]) << tools.byteToHex(nuidPICC[2]) << tools.byteToHex(nuidPICC[3]) << endl;
  // tools.dump_byte_array(card_block1, 16);
  // tools.dump_byte_array(card_block2, 16);
  // tools.dump_byte_array(card_block4, 16);
  // tools.dump_byte_array(card_block5, 16);
  // tools.dump_byte_array(card_block6, 16);
  Serial << endl;
  // Decrypting
  unsigned char card_block1_dec[16];
  decrypt((char *)card_block1, config.aes_key, card_block1_dec);
  unsigned char card_block2_dec[16];
  decrypt((char *)card_block2, config.aes_key, card_block2_dec);
  unsigned char card_block4_dec[16];
  decrypt((char *)card_block4, config.aes_key, card_block4_dec);
  unsigned char card_block5_dec[16];
  decrypt((char *)card_block5, config.aes_key, card_block5_dec);
  unsigned char card_block6_dec[16];
  decrypt((char *)card_block6, config.aes_key, card_block6_dec);
  // tools.dump_byte_array(card_block1_dec, 16);
  // tools.dump_byte_array(card_block2_dec, 16);
  // tools.dump_byte_array(card_block4_dec, 16);
  // tools.dump_byte_array(card_block5_dec, 16);
  // tools.dump_byte_array(card_block6_dec, 16);

#ifdef USE_RTC
  // Check valid Time
  if (!tools.isTimeValid(card_block2_dec))
  {
    return 0;
  }
#endif
  for (size_t i = 0; i < 16; i++)
  {
    card_block1[i] = card_block1_dec[i];
    card_block2[i] = card_block2_dec[i];
    card_block4[i] = card_block4_dec[i];
    card_block5[i] = card_block5_dec[i];
    card_block6[i] = card_block6_dec[i];
  }

#ifndef CLONE_FREE
  // 1. Check valid card number (An intrusion warning may be sent)
  if (nuidPICC[0] != card_block1[12] || nuidPICC[1] != card_block1[13] || nuidPICC[2] != card_block1[14] || nuidPICC[3] != card_block1[15])
  {
    Serial << "Fake card. The decrypted number doesn't match!" << endl;
    error_late = "Fake card. The decrypted number doesn't match!";
    return 0;
  }
#endif

  // 2. Check hotel number
  uint16_t hotel;
  hotel = card_block1[0];
  hotel <<= 8;
  hotel += card_block1[1];
  if (config.hotel_code != hotel)
  {
    Serial << "hotel:" << config.hotel_code << endl;
    Serial << "Hotel number doesn't match! - found " << hotel << endl;
    error_late = "Hotel number doesn't match!";
    return 0;
  }
  // 3. Check room or group matching. If bouth are zero check if user is employee. In this case card have full system access.
  // TODO - bit check
  uint64_t group = 0;
  // [0 - 7]
  group += card_block5[0];
  group <<= 8;
  group += card_block5[1];
  group <<= 8;
  group += card_block5[2];
  group <<= 8;
  group += card_block5[3];
  group <<= 8;
  group += card_block5[4];
  group <<= 8;
  group += card_block5[5];
  group <<= 8;
  group += card_block5[6];
  group <<= 8;
  group += card_block5[7];

  uint16_t room;
  room = card_block1[2];
  room <<= 8;
  room += card_block1[3];
  uint8_t user_type = card_block1[11];

  uint32_t c_guest_count = 0;
  c_guest_count += card_block4[0];
  c_guest_count <<= 8;
  c_guest_count += card_block4[1];
  c_guest_count <<= 8;
  c_guest_count += card_block4[2];
  c_guest_count <<= 8;
  c_guest_count += card_block4[3];

  uint32_t c_staff_count = 0;
  c_staff_count += card_block4[4];
  c_staff_count <<= 8;
  c_staff_count += card_block4[5];
  c_staff_count <<= 8;
  c_staff_count += card_block4[6];
  c_staff_count <<= 8;
  c_staff_count += card_block4[7];

  uint32_t c_master_count = 0;
  c_master_count += card_block4[8];
  c_master_count <<= 8;
  c_master_count += card_block4[9];
  c_master_count <<= 8;
  c_master_count += card_block4[10];
  c_master_count <<= 8;
  c_master_count += card_block4[11];

  uint32_t c_crc_count = 0;
  c_crc_count += card_block4[12];
  c_crc_count <<= 8;
  c_crc_count += card_block4[13];
  c_crc_count <<= 8;
  c_crc_count += card_block4[14];
  c_crc_count <<= 8;
  c_crc_count += card_block4[15];

  // Check CRC
  uint32_t countersCRC = 0;
  // for (size_t i = 0; i < 12; i++)
  // {
  //   countersCRC += card_block4[i];
  // }
  countersCRC = c_guest_count + c_staff_count + c_master_count;

  if (c_crc_count != countersCRC)
  {
    Serial << "Wrong CRC " << endl;
    error_late = "Guests must have a room!";
    Serial << " Calculated CRC vs CRC in card:" << countersCRC << " - " << c_crc_count << endl;
    return 0;
  }
  Serial << " Calculated CRC vs CRC in card:" << countersCRC << " - " << c_crc_count << endl;

#ifdef USE_RTC
  // 4. Check end time
  if (!compareTime())
  {
    return 0;
  }
#endif
  // USER CARD
  if (user_type == 0)
  {
    Serial << "Guest card" << endl;
    // check guest room
    if (room == 0)
    {
      Serial << "Guests must have a room!" << endl;
      error_late = "Guests must have a room!";
      return 0;
    }
    else if (room != config.room)
    {
      // Check group access for user
      if (!groupAcces(group))
      {
        Serial << "Wrong group!" << endl;
        error_late = "Wrong group!";
        return 0;
      }
      else
      {
        Serial << "Group access for user!" << endl;
      }
    }
    else if (c_guest_count < config.guest_count)
    {
      Serial << "Expired guest counter!" << endl;
      error_late = "Expired guest counter!";
      return 0;
    }
    else if (c_guest_count > config.guest_count)
    {
      config.guest_count = c_guest_count;

      Serial << "Update guest count!" << endl;
      saveConfiguration();
    }
  }

  // Serial << "Group in memory vs card:" << config.group << " " << group << endl;
  Serial << "Guest counts in memory vs card:" << config.guest_count << " " << c_guest_count << endl;
  Serial << "Staff counts in memory vs card:" << config.staff_count << " " << c_staff_count << endl;
  Serial << "Master counts in memory vs card:" << config.master_count << " " << c_master_count << endl;
  // tools.dump_byte_array(card_block4, 16);
  // tools.dump_byte_array(card_block4_dec, 16);

  // MASTER CARD
  if (group == 0 && user_type == 255)
  {
    Serial << "Global administrator access!" << endl;
    if (c_master_count < config.master_count)
    {
      Serial << "Expired global administrator card!" << endl;
      error_late = "Expired global administrator card!";
      return 0;
    }
    else if (c_master_count > config.master_count)
    {
      config.master_count = c_master_count;
      Serial << "Update administrator count!" << endl;
      saveConfiguration();
    }
    master_access = true; // Only the master has access when DND is set
  }

  // STAFF CARD
  if (group != 0 && user_type == 255)
  {
    Serial << "Staff access! Group in card vs group in memo:" << group << " - " << config.group << endl;
    if (!groupAcces(group))
    {
      Serial << "Wrong group staff card!" << endl;
      error_late = "Wrong group staff card!";
      return 0;
    }
    if (c_staff_count < config.staff_count)
    {
      Serial << "Expired staff card!" << endl;
      error_late = "Expired staff card!";
      return 0;
    }
    else if (c_staff_count > config.staff_count)
    {
      config.staff_count = c_staff_count;
      Serial << "Update staff count!" << endl;
      saveConfiguration();
    }
    // Check group access
  }
  Serial << "User type:" << user_type << endl;

  if (user_type == 0)
  {
    Serial << "Guest user type!" << endl;
  }
  else if (user_type == 255)
  {
    Serial << "Staff or master user type!" << endl;
  }
  else
  {
    Serial << "Unknown user type!" << endl;
    error_late = "Unknown user type!";
    return 0;
  }

  measureTime();
  digitalWrite(LED_RED, 0);
  // 5. Return user id from card
  uint32_t userid = 0;
  userid = card_block1[7];
  userid <<= 8;
  userid += card_block1[8];
  userid <<= 8;
  userid += card_block1[9];
  userid <<= 8;
  userid += card_block1[10];
  Serial << "Found user: " << userid << endl;
  return userid;
}

bool groupAcces(uint64_t group)
{
  if (group == 0)
    return false;
  return (group & config.group) > 0;
}

// ######## #### ##     ## ########    ##     ## ########  ########     ###    ######## ########
//    ##     ##  ###   ### ##          ##     ## ##     ## ##     ##   ## ##      ##    ##
//    ##     ##  #### #### ##          ##     ## ##     ## ##     ##  ##   ##     ##    ##
//    ##     ##  ## ### ## ######      ##     ## ########  ##     ## ##     ##    ##    ######
//    ##     ##  ##     ## ##          ##     ## ##        ##     ## #########    ##    ##
//    ##     ##  ##     ## ##          ##     ## ##        ##     ## ##     ##    ##    ##
//    ##    #### ##     ## ########     #######  ##        ########  ##     ##    ##    ########

// NTP

void activateTimeUpdate()
{
  setSound(100);
  // SSID and PASS
  for (size_t i = 0; i < 16; i++)
  {
    config.ssid[i] = card_block4[i];
    config.pass[i] = card_block5[i];
    // AES key from card
    config.aes_key[i] = card_block1[i];
  }

  // MQTT from card
  sprintf(config.mqtt, "%d.%d.%d.%d", card_block6[0], card_block6[1], card_block6[2], card_block6[3]);
  Serial << "mqtt_server:" << config.mqtt << endl;
  config.mqtt_port = card_block6[4] * 256 + card_block6[5];
  Serial << "mqtt_port:" << config.mqtt_port << endl;

#ifdef NTP
  timeClient.begin();
  // timeClient.setTimeOffset(10800);

  int awake_timer = millis();
  while (!timeClient.update())
  {
    timeClient.forceUpdate();
    if (millis() - awake_timer > WIFI_TIME)
    {
      Serial << "NTP failure" << endl;
      goSleep();
    }
  }

  digitalWrite(ZUMER, 1);
  delay(100);
  digitalWrite(ZUMER, 0);
  formattedDate = timeClient.getFormattedDate();
  Serial.println(formattedDate);

  // 2022-11-08T18:04:17Z
  int mt = formattedDate.substring(5, 7).toInt();
  int hr = formattedDate.substring(11, 13).toInt();
  int dy = formattedDate.substring(8, 10).toInt();
  int mn = formattedDate.substring(14, 16).toInt();
  int yr = formattedDate.substring(0, 4).toInt();
  int ss = formattedDate.substring(17, 19).toInt();
  // Serial << dy << "-" << mt << "-" << yr << " " << hr << ":" << mn << ":" << ss << endl;

  time_clock.setTime(ss, mn, hr, dy, mt, yr);
  Serial << time_clock.getTime("%d-%m-%Y %H:%M:%S") << endl;
  config.today = (uint8_t)dy; // Once a day time update
#endif
  if (!SPIFFS.begin(true))
  {
    Serial.println("An Error has occurred while mounting SPIFFS");
    errorSound();
    errorSound();
    esp_restart();
    return;
  }
  saveConfiguration();

  digitalWrite(ZUMER, 1);
  delay(50);
  digitalWrite(ZUMER, 0);

  connectWiFi();

  digitalWrite(ZUMER, 1);
  delay(100);
  digitalWrite(ZUMER, 0);

#ifdef MQTT_TIME
  connectBroker();
  sendLastEvent();
  responseAlive();
#endif
  setSound(50);
  delay(100);
}

void sendLastEvent()
{
  publishUserAcces(access_late_user, access_late_card);
  to_send_late = false;
}

// ##     ## ########  ########     ###    ######## ########       ###    ########
// ##     ## ##     ## ##     ##   ## ##      ##    ##            ## ##   ##     ##
// ##     ## ##     ## ##     ##  ##   ##     ##    ##           ##   ##  ##     ##
// ##     ## ########  ##     ## ##     ##    ##    ######      ##     ## ########
// ##     ## ##        ##     ## #########    ##    ##          ######### ##
// ##     ## ##        ##     ## ##     ##    ##    ##          ##     ## ##
//  #######  ##        ########  ##     ##    ##    ########    ##     ## ##

/**
 * @brief Activate update and config
 *
 */
void activateUpdate()
{

  awake_update = true;
  setSound(100);
  tools.dump_byte_array(card_block4, 16);
  tools.dump_byte_array(card_block5, 16);
  tools.dump_byte_array(card_block6, 16);

  // SSID and PASS
  for (size_t i = 0; i < 16; i++)
  {
    config.ssid[i] = card_block4[i];
    config.pass[i] = card_block5[i];
    // AES key from card
    config.aes_key[i] = card_block1[i];
  }

  Serial << "SSID:" << config.ssid << endl;
  Serial << "PASS:" << config.pass << endl;
  // MQTT from card
  sprintf(config.mqtt, "%d.%d.%d.%d", card_block6[0], card_block6[1], card_block6[2], card_block6[3]);
  Serial << "mqtt_server:" << config.mqtt << endl;
  config.mqtt_port = card_block6[4] * 256 + card_block6[5];
  Serial << "mqtt_port:" << config.mqtt_port << endl;

  bool update = true;
  if (card_block6[15] == 0xFF)
  {
    update = false;
    awake_update = false;
  }

  // connectWiFi();
  factory_mode = true;
  Serial.println("Update process ...");
  if (update)
    insomnia = true;
  if (!SPIFFS.begin(true))
  {
    Serial.println("An Error has occurred while mounting SPIFFS");
    errorSound();
    errorSound();
    esp_restart();
    return;
  }
  saveConfiguration();
  connectWiFi();
  connectBroker();
  responseAlive();
  sendLog();
  if (update)
  {
    // You can remove the password parameter if you want the AP to be open.
    WiFi.softAP(config.apssid, config.appass);
    IPAddress myIP = WiFi.softAPIP();
    Serial.print(F("AP IP address: "));
    Serial.println(myIP);
    Serial.println("Server started");
    WiFi.macAddress(MAC_array);
    for (int i = 0; i < sizeof(MAC_array); ++i)
    {
      sprintf(MAC_char, "%s%02x", MAC_char, MAC_array[i]);
    }
    Serial.println(MAC_char);
    startServer();
  }
  setSound(200);
}

//    ###     ######  ######## #### ##     ##    ###    ######## ########       ###    ########
//   ## ##   ##    ##    ##     ##  ##     ##   ## ##      ##    ##            ## ##   ##     ##
//  ##   ##  ##          ##     ##  ##     ##  ##   ##     ##    ##           ##   ##  ##     ##
// ##     ## ##          ##     ##  ##     ## ##     ##    ##    ######      ##     ## ########
// ######### ##          ##     ##   ##   ##  #########    ##    ##          ######### ##
// ##     ## ##    ##    ##     ##    ## ##   ##     ##    ##    ##          ##     ## ##
// ##     ##  ######     ##    ####    ###    ##     ##    ##    ########    ##     ## ##

void activateAP()
{
  awake_update = true;
  factory_mode = true;
  Serial.println("Update process ...");
  insomnia = true;
  if (!SPIFFS.begin(true))
  {
    Serial.println("An Error has occurred while mounting SPIFFS");
    errorSound();
    errorSound();
    esp_restart();
    return;
  }
  WiFi.softAP(config.apssid, config.appass);
  IPAddress myIP = WiFi.softAPIP();
  Serial.print(F("AP IP address: "));
  Serial.println(myIP);
  Serial.println("Server started");
  WiFi.macAddress(MAC_array);
  for (int i = 0; i < sizeof(MAC_array); ++i)
  {
    sprintf(MAC_char, "%s%02x", MAC_char, MAC_array[i]);
  }
  Serial.println(MAC_char);
  startServer();
  setSound(200);
}

//  ######  ######## ########     ######  ##        #######   ######  ##    ##
// ##    ## ##          ##       ##    ## ##       ##     ## ##    ## ##   ##
// ##       ##          ##       ##       ##       ##     ## ##       ##  ##
//  ######  ######      ##       ##       ##       ##     ## ##       #####
//       ## ##          ##       ##       ##       ##     ## ##       ##  ##
// ##    ## ##          ##       ##    ## ##       ##     ## ##    ## ##   ##
//  ######  ########    ##        ######  ########  #######   ######  ##    ##

bool corectTime()
{
  Serial << "Check clock. Now is " << time_clock.getDay() << " Config is " << config.today << endl;

  if ((config.today == 0) || (config.today != time_clock.getDay()))
  {
    // IF MQTT
    // Serial << "today is " << config.today << " time clock is " << time_clock.getDay() << endl;
    // awake_timer = millis();
    // connectWiFi();
    // awake_timer = millis();
    // connectBroker();
    // awake_timer = millis();
    // responseAlive();
    // awake_timer = millis();
    // sendLog();
    // ready_to_sleep = false;
    // return false;
    // activateTimeUpdate(); // Using NTP
    return false;
  }
  return true;
}

//  ######  ######## ##    ## ########     ##        #######   ######
// ##    ## ##       ###   ## ##     ##    ##       ##     ## ##    ##
// ##       ##       ####  ## ##     ##    ##       ##     ## ##
//  ######  ######   ## ## ## ##     ##    ##       ##     ## ##   ####
//       ## ##       ##  #### ##     ##    ##       ##     ## ##    ##
// ##    ## ##       ##   ### ##     ##    ##       ##     ## ##    ##
//  ######  ######## ##    ## ########     ########  #######   ######

void saveEvent(uint64_t user_id)
{
#ifdef NO_RECORDS
  return;
#endif
  if (!SPIFFS.begin())
  {
    Serial.println(F("Card Mount Failed"));
    errorSound();
    return;
  }
  String time(MAC_char);
  // dd-MM-yyyy HH:mm:ss EET
  time += time_clock.getTime("%d-%m-%Y %H:%M:%S");
  sprintf(operations_buffer, "%llu %s \n", user_id, time.c_str());
  filesys.appendFile(SPIFFS, "/log.txt", operations_buffer);
  // filesys.listDir(SPIFFS, "/", 0);
  filesys.printFile(SPIFFS, "/log.txt");
}

/**
 * @brief Clear log.txt
 *
 */
void clearLog()
{
  if (!SPIFFS.begin())
  {
    Serial.println(F("Card Mount Failed"));
    errorSound();
    return;
  }
  filesys.writeFile(SPIFFS, "/log.txt", " "); // Empty file
}

/**
 * @brief Send log to broker and delete from file system
 *
 */
void sendLog()
{
  if (!SPIFFS.begin())
  {
    Serial.println(F("Card Mount Failed"));
    errorSound();
    return;
  }
  char topic[50];
  filesys.readFile(operations_buffer, "/log.txt");
  Serial << operations_buffer << endl;
  sprintf(topic, "log/%s", MAC_char);
  Serial << "topic:" << topic << endl;
  if (mqttClient.connected())
  {
    mqttClient.publish(topic, 0, false, operations_buffer);
    filesys.writeFile(SPIFFS, "/log.txt", " "); // Empty file
  }
}

void setRoomHotelGroup(String payload)
{
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, payload.c_str());
  if (error)
  {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return;
  }
  config.hotel_code = doc["hotel_code"];
  config.room = doc["room"];
  config.group = doc["group"];
  saveConfiguration();
  Serial << "new room:" << config.room << endl;
  Serial << "new hotel code:" << config.hotel_code << endl;
  Serial << "new group:" << config.group << endl;
}

// #### ##    ## #### ########    ######## #### ##    ##  ######   ######## ########
//  ##  ###   ##  ##     ##       ##        ##  ###   ## ##    ##  ##       ##     ##
//  ##  ####  ##  ##     ##       ##        ##  ####  ## ##        ##       ##     ##
//  ##  ## ## ##  ##     ##       ######    ##  ## ## ## ##   #### ######   ########
//  ##  ##  ####  ##     ##       ##        ##  ##  #### ##    ##  ##       ##   ##
//  ##  ##   ###  ##     ##       ##        ##  ##   ### ##    ##  ##       ##    ##
// #### ##    ## ####    ##       ##       #### ##    ##  ######   ######## ##     ##

/**
 * @brief Init fingerprint sensor
 *
 */
void initFingerprint()
{
// Serial << "count:" << camaSM30.templatesCount() << endl;
#ifdef FINGERPRINT_SM30
  camaSM30.begin();
  // Set CAMA sensor paramerers on restart
  if (esp_sleep_get_wakeup_cause() == 0)
  {
    camaSM30.setDuplicationCheck(PARAM_DUPLICATION_CHECK);
    camaSM30.setSecurityLevel(PARAM_SECURITY_LEVEL);
  }
#endif
#ifdef FINGERPRINT_SM25
  camaSM30.ledON();
#endif

#ifdef FINGERPRINT_SM30
  if (!camaSM30.camaTest())
  {
    Serial << "Problem detecting sensor" << endl;
    skip_sensor = true;
    return;
  }
  camaSM30.getTemplatesCount();
  // camaSM30.setFingerTimeout(5);
  // Serial<<"timeout:"<<camaSM30.getFingerTimeout()<<endl;
  // camaSM30.enroll(2);
  // camaSM30.deleteAllTemplates();
#endif
}

/**
 * @brief Check presented finger
 *
 */
bool checkFinger()
{
  timeMeasure = millis();
#ifdef FINGERPRINT_SM30
  uint16_t detect = camaSM30.detectFinger();
  if (detect == 0)
  {
    return false;
  }
  else if (detect == 0xFF)
  {
    Serial << "communication problem" << endl;
  }
  digitalWrite(ZUMER, 1);
  delay(10);
  digitalWrite(ZUMER, 0);

  uint16_t found = camaSM30.identify();

  Serial << "detect:" << found << endl;
  if (found == 0xFFFF)
  {
    Serial.println(F("Sensor error. Device needs restart"));
    camaSM30.cancel();
    digitalWrite(ZUMER, 1);
    delay(300);
    digitalWrite(ZUMER, 0);
    return false;
  }
  else if (found > 0)
  {
    Serial << "found:" << found << endl;
    opendoor(config.relay_time);
    to_send_late = true;
    access_late_user = users[found];
    access_late_card = found;
    return true;
  }
  else if (found == 0)
  {
    to_send_late = true;
    access_late_user = 0;
    access_late_card = 0;
    errorSound();
    return false;
  }
#endif
  return false;
}

void setClock(String msg)
{
  // TODO check if valid time
  Serial << "Clock" << endl;
  // timesync/13-01-2022 08:03:26
  int mn = msg.substring(14, 16).toInt();
  int hr = msg.substring(11, 13).toInt();
  int dy = msg.substring(0, 2).toInt();
  int mt = msg.substring(3, 5).toInt();
  int yr = msg.substring(6, 10).toInt();
  int ss = msg.substring(17, 19).toInt();
  // Serial<<msg[1].substring(0, 2)<<" "<<msg[1].substring(3, 5).toInt()<<endl;
  // Serial<<msg[1].substring(6, 10).toInt()<<" "<<msg[1].substring(11, 13).toInt()<<endl;
  // Serial<<msg[1].substring(14, 16).toInt()<<" "<<msg[1].substring(17, 19).toInt()<<endl;

  // setTime(int sc, int mn, int hr, int dy, int mt, int yr, int ms)
  time_clock.setTime(ss, mn, hr, dy, mt, yr, 0);
  config.today = (uint8_t)dy; // Once a day time update
  Serial << "The clock is set" << endl;
}

bool compareTime()
{
  uint16_t end_year = tools.bcd2bin(card_block2[0]) * 100 + tools.bcd2bin(card_block2[1]);

  digitalWrite(LED_RED, 1);
  timeMeasure = millis();
  if (time_clock.getYear() > end_year)
  {
    Serial << "Expired year!" << endl;
    error_late = "Expired year!";
    return false;
  }
  else if (time_clock.getYear() < end_year)
  {
    return true;
  }

  if (time_clock.getMonth() + 1 > tools.bcd2bin(card_block2[2]))
  {
    Serial << "Expired month!" << endl;
    error_late = "Expired month!";
    return false;
  }
  else if (time_clock.getMonth() + 1 < tools.bcd2bin(card_block2[2]))
  {
    return true;
  }

  if (time_clock.getDay() > tools.bcd2bin(card_block2[3]))
  {
    Serial << "Expired day!" << endl;
    error_late = "Expired day!";
    return false;
  }
  else if (time_clock.getDay() < tools.bcd2bin(card_block2[3]))
  {
    return true;
  }

  // .getHour(true) - 24H
  if (time_clock.getHour(true) > tools.bcd2bin(card_block2[4]))
  {
    Serial << "Expired hour!" << endl;
    error_late = "Expired hour!";
    return false;
  }
  else if (time_clock.getHour(true) < tools.bcd2bin(card_block2[4]))
  {
    return true;
  }

  if (time_clock.getMinute() > tools.bcd2bin(card_block2[5]))
  {
    Serial << "Expired minutes!" << endl;
    error_late = "Expired minutes!";
    return false;
  }
  return true;
}