//Define DEBUG to get the Output from DEBUG_PRINTLN
#define DEBUG 1

#include <Arduino.h>


/* Wiring (Heltec OLED Lora)
 *              GND O                    O GND
 *               5V O                    O 5V
 *              3V3 O                    O 3V3
 *              GND O                    < 36
 *               RX *                    * 17
 *               TX *                    < 38
 *              RST * BUTTON             < 39
 *                0 *                    < 34
 *               22 *                    < 35
 * BME280_SDO    19 * LoRa_MISO          * 32
 * BME280_CS     23 *                    * 33
 *               18 x LoRa_CS            * 25 MAX98_DIN
 * BME280_SCL/SCK 5 * LoRa_SCK  LoRa_IRQ * 26
 *               15 * OLED_SCL LoRa_MOSI * 27 BME280_SDA/SDI
 *                2 *           LoRa_RST * 14
 *                4 * OLED_SDA           * 12 MAX98_BCLK
 *               17 *                    * 13 MAX98_LRC
 *               16 * OLED_RST           * 21
 */

//Include Basecamp in this sketch
#include <Basecamp.hpp>
#include <Configuration.hpp>

#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

#include <WiFiUdp.h>
#include <NTPClient.h>

#include "main.h"
#include "mp3.h"
#include "BME280.h"
#include "image.h"

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16);

//Create a new Basecamp instance called iot
Basecamp iot;

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "de.pool.ntp.org", 3600, 60000);
char timeStr[20];

BME280 bme280;
MP3 mp3;

uint32_t lastButtonPress = 0;

//Variables for the sensor and the battery
static const int buttonPin = 0;
static const int ResetPin = 17;
static const int SensorPin = 32;
static const int BatteryPin = 34;
int sensorValue = 0;
//The batteryLimit defines the point at which the battery is considered empty.
int batteryLimit = 3300;

//This is used to control if the ESP should enter sleep mode or not
bool delaySleep = true;

//Variables for the mqtt packages and topics
uint16_t statusPacketIdSub = 0;
String delaySleepTopic;
String statusTopic;
String batteryTopic;
String batteryValueTopic;

void setup() {
//    #include "soc/rtc_io_reg.h"
//    #include "soc/sens_reg.h"
//   
//    SET_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32N_MUX_SEL | RTC_IO_X32P_MUX_SEL);
//    CLEAR_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32P_RDE | RTC_IO_X32P_RUE | RTC_IO_X32N_RUE | RTC_IO_X32N_RDE);
//    CLEAR_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32N_MUX_SEL | RTC_IO_X32P_MUX_SEL);
//    SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DAC_XTAL_32K, 1, RTC_IO_DAC_XTAL_32K_S);
//    SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DRES_XTAL_32K, 3, RTC_IO_DRES_XTAL_32K_S);
//    SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DBIAS_XTAL_32K, 0, RTC_IO_DBIAS_XTAL_32K_S);
//    SET_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_XPD_XTAL_32K);
//    REG_SET_BIT(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_MUX_SEL_M);
//    REG_CLR_BIT(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_RDE_M | RTC_IO_PDAC1_RUE_M);
//    REG_SET_FIELD(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_FUN_SEL, 1);
//    REG_SET_FIELD(SENS_SAR_DAC_CTRL1_REG, SENS_DEBUG_BIT_SEL, 0);
//    const uint8_t sel = 4; /* sel = 4 : 32k XTAL; sel = 5 : internal 150k RC */
//    REG_SET_FIELD(RTC_IO_RTC_DEBUG_SEL_REG, RTC_IO_DEBUG_SEL0, sel);
//


  //configuration of the battery and sensor pins
//  pinMode(ResetPin, INPUT_PULLDOWN);
//  pinMode(SensorPin, INPUT_PULLDOWN);
//  pinMode(BatteryPin, INPUT);
  pinMode(buttonPin, INPUT_PULLUP);

  pinMode(18, OUTPUT);
  digitalWrite(18, HIGH); // disable LoRa_CS

  //read the status of the doorsensor as soon as possible to determine the state that triggered it
//  sensorValue = digitalRead(SensorPin);

  //Initialize Basecamp
  iot.begin();

  bme280.begin();
  for (int i=0; i<5; i++) {
    delay(500);
    bme280.printValues();
  }

  u8g2.begin();
  delay(50);
  u8g2.clearBuffer();          // clear the internal memory
  u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
  u8g2.drawStr(2,10,"esp32-node by hendrik");  // write something to the internal memory
  if (iot.configuration.get("WifiConfigured") != "True") {
    u8g2.drawStr(2,30,"NOT CONFIGURED!");
    u8g2.drawStr(12,40,"SSID: \"ESP32\"");
    u8g2.drawStr(12,55,"http://192.168.4.1");
  } else {
    u8g2.drawStr(2,30,"Welcome!");
    u8g2.drawStr(12,40,"connecting..");
  }
  u8g2.drawFrame(0,0,u8g2.getDisplayWidth(),u8g2.getDisplayHeight() );
  u8g2.sendBuffer();          // transfer internal memory to the display

  //Configure the MQTT topics
  delaySleepTopic = "esp32-node/cmd/" + iot.hostname + "/delaysleep";
  statusTopic = "esp32-node/stat/" + iot.hostname + "/status";
  batteryTopic = "esp32-node/stat/" + iot.hostname + "/battery";
  batteryValueTopic = "esp32-node/stat/" + iot.hostname + "/batteryvalue";

  //Set up the Callbacks for the MQTT instance. Refer to the Async MQTT Client documentation
  // TODO: We should do this actually _before_ connecting the mqtt client...
  iot.mqtt.onConnect(onMqttConnect);
  iot.mqtt.onPublish(suspendESP);
  iot.mqtt.onMessage(onMqttMessage);

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

  IPAddress localIP = iot.wifi.getIP();
  char ipStr[16];
  localIP.toString().toCharArray(ipStr, 16);

  timeClient.begin();
  timeClient.update();
  timeClient.getFormattedTime().toCharArray(timeStr, 50);
  u8g2.clearBuffer();          // clear the internal memory
  u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
//  u8g2.setDrawColor(2);
  u8g2.drawStr(30, 30, timeStr);
  u8g2.drawStr(30, 50, ipStr);
  u8g2.sendBuffer();

  mp3.begin();

  u8g2.setContrast(127);
}


//This function is called when the MQTT-Server is connected
void onMqttConnect(bool sessionPresent) {
  DEBUG_PRINTLN(__func__);
  
  //Subscribe to the delay topic
  iot.mqtt.subscribe(delaySleepTopic.c_str(), 0);
  //Trigger the transmission of the current state.
  transmitStatus();
}


//This function transfers the state of the sensor. That includes the door status, battery status and level
void transmitStatus() {
  DEBUG_PRINTLN(__func__);

  if (sensorValue == 0) {
    DEBUG_PRINTLN("Door open");
    //Transfer the current state of the sensor to the MQTT broker
    statusPacketIdSub = iot.mqtt.publish(statusTopic.c_str(), 1, true, "open" );
    //Configure the wakeup pin to wake if the door is closed
    esp_sleep_enable_ext0_wakeup((gpio_num_t)SensorPin, 1);
  } else {
    DEBUG_PRINTLN("Door closed");
    //Transfer the current state of the sensor to the MQTT broker
    statusPacketIdSub = iot.mqtt.publish(statusTopic.c_str(), 1, true, "closed" );
    //Configure the wakeup pin to wake if the door is closed
    esp_sleep_enable_ext0_wakeup((gpio_num_t)SensorPin, 0);
  }

  //Read the current analog battery value
//  sensorValue = analogRead(BatteryPin);
  //sensorC stores the battery value as a char
  char sensorC[6];
  //convert the sensor value to a string
  sprintf(sensorC, "%04i", sensorValue);
  //Send the sensor value to the MQTT broker
  iot.mqtt.publish(batteryValueTopic.c_str(), 1, true, sensorC);
  //Check the battery level and publish the state
  if (sensorValue < batteryLimit) {
    DEBUG_PRINTLN("Battery empty");
    iot.mqtt.publish(batteryTopic.c_str(), 1, true, "empty" );
  } else {
    DEBUG_PRINTLN("Battery full");
    iot.mqtt.publish(batteryTopic.c_str(), 1, true, "full" );
  }
  DEBUG_PRINTLN("Data published");
}

//This topic is called if an MQTT message is received
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
  DEBUG_PRINTLN(__func__);

  //Check if the payload eqals "true" and set delaySleep correspondigly
  //Since we only subscribed to one topic, we only have to compare the payload
  if (strcmp(payload, "true") == 0)  {
    delaySleep = true;
  } else  {
    delaySleep = false;
  }
}

void suspendESP(uint16_t packetId) {
  DEBUG_PRINTLN(__func__);
  
  //Check if the published package is the one of the door sensor
  if (packetId == statusPacketIdSub) {
   
    if (delaySleep == true) {
      DEBUG_PRINTLN("Delaying Sleep");
      return;
    }
    DEBUG_PRINTLN("Entering deep sleep");
    //properly disconnect from the MQTT broker
    iot.mqtt.disconnect();
    //send the ESP into deep sleep
    esp_deep_sleep_start();
  }
}



void loop()
{
  timeClient.update();
  timeClient.getFormattedTime().toCharArray(timeStr, 50);

  if (digitalRead(buttonPin) == LOW) {
    if(millis() - lastButtonPress >= 1000) {
    Serial.println("Button pressed");
      lastButtonPress = millis();
      if (mp3.playing) {
        mp3.stop();
      } else {
        mp3.start();
      }
    }
  }

  u8g2.clearBuffer();          // clear the internal memory

  if (digitalRead(buttonPin) == HIGH) {
    u8g2.setFont(u8g2_font_inb19_mf);
    u8g2.drawStr(0, 20, timeStr);

    char title1[32];   char title2[32];
    strncpy(title1, titleStr, 22); title1[22] = '\0';
    strncpy(title2, titleStr+22, 32); title2[31] = '\0';
    char weather[32];
    u8g2.setFont(u8g2_font_profont12_mf); // choose a suitable font
    sprintf(weather, "%.1f°C  %.1f%%  %.0fhPa", bme280.readTemperature(), bme280.readHumidity(), bme280.readPressure());
    u8g2.drawUTF8(0, 30, weather);
    u8g2.setFont(u8g2_font_prospero_bold_nbp_tf); // choose a suitable font
    u8g2.drawUTF8(0, 54, title1);
    u8g2.drawUTF8(0, 64, title2);
  } else {
    u8g2.drawXBMP(0,0, IMG_1872_width, IMG_1872_height, IMG_1872_bits);
  }
  u8g2.sendBuffer();
//  bme280.printValues();

  delay(100);
}