#include <Arduino.h>

// LED
#define FASTLED_ESP8266_NODEMCU_PIN_ORDER
#include <FastLED.h>

// WIFI
#include <ESP8266WiFi.h>

// MQTT
#include <PubSubClient.h>

// OTA
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

// RemoteDebug
#include <RemoteDebug.h>

#include "alarmclock.h"

void setup()
{
  Serial.begin(SERIAL_SPEED);
  Serial.println("\nresetting");

  // WIFI
  setupWifi();

  // OTA
  setupOTA();

  // RemoteDebug
  Debug.begin("alarmclock");

  // LED
  maxBrightness = LED_MAXBRIGHTNESS_DEFAULT;
  curbrightness = LED_BRIGHTNESS_DEFAULT;
  color = LED_COLOR_DEFAULT;
  ledState = false;

  LEDS.addLeds<LED_CHIPSET,LED_PIN, LED_COLOR_ORDER>(leds, LED_NUM).setCorrection(TypicalSMD5050);
  ledBlackAll();

  // MQTT
  client.setServer(MQTT_SERVER, MQTT_PORT);
  client.setCallback(callbackMQTT);
  testConnectMQTT();

  fps = 0;
  Debug.println("Ready");

  /* MQTT
  * Il est important de faire un loop avant toute chose,
  * afin de récupérer les valeurs provenant du broker mqtt
  * et pas démarrer avec de vieilles infos.
  * Il faut un certains nombres de tentative pour tout récuperer.
  */
  for (short int i = 0; i < 10; i++) {
    delay(200);
    client.loop();
  }

  Debug.println("End of setup");
}

// OTA
void setupOTA()
{
  ArduinoOTA.setHostname("alarmclock"); // on donne une petit nom a notre module
  ArduinoOTA.setPassword(OTA_PASSWORD);
  ArduinoOTA.onStart([]() {
    Serial.println("OTA Starting");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nOTA End");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("OTA Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("OTA Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      Serial.println("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      Serial.println("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      Serial.println("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      Serial.println("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      Serial.println("End Failed");
    }
  });
  ArduinoOTA.begin();
}

// WIFI
void setupWifi()
{
  Debug.print("Connexion a ");
  Debug.print(WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Debug.print(".");
  }
  Debug.println(" OK");
  Debug.print("IP : ");
  Debug.println(WiFi.localIP());
}

// MQTT
void testConnectMQTT()
{
  while (!client.connected()) {
    Debug.print("Connexion au serveur MQTT... ");
    if (client.connect("ESP8266Client", MQTT_USER, MQTT_PASS)) {
      Debug.print("OK\nSend Current State");
      mqttSendState();
      mqttSendBrightnessState();
      mqttSendColorState();

      Debug.print("OK\nSubscribe");
      client.subscribe(MQTT_LED_COMMAND);
      client.subscribe(MQTT_LED_BRIGHTNESS_COMMAND);
      client.subscribe(MQTT_LED_COLOR_COMMAND);

      Debug.println(" OK");
    } else {
      Debug.print("KO, erreur : ");
      Debug.print(client.state());
      Debug.println(", on attend 5 secondes avant de recommencer");
      delay(5000);
    }
  }
}

// Déclenche les actions à la réception d'un message
void callbackMQTT(char* topic, byte* payload, unsigned int length)
{
  String stopic = String(topic);

  unsigned int i = 0;
  for(i = 0; i < length; i++) {
    message_buff[i] = payload[i];
  }
  message_buff[i] = '\0';
  String msgString = String(message_buff);

  Debug.print("Received [" + stopic + "] : ");
  Debug.println(msgString);

  if (stopic == MQTT_LED_COMMAND) {
    if (msgString == "ON") {
      ledState = true;
      curbrightness = LED_BRIGHTNESS_DEFAULT;
      startTime = millis();
    } else {
      ledState = false;
      ledBlackAll();
    }
    mqttSendState();
  } else if (stopic == MQTT_LED_BRIGHTNESS_COMMAND) {
    maxBrightness = msgString.toInt();
    mqttSendBrightnessState();
  } else if (stopic == MQTT_LED_COLOR_COMMAND) {
    // Sample : 134,168,255
    int red = msgString.substring(0, msgString.indexOf(',')).toInt();
    int green = msgString.substring(msgString.indexOf(',') + 1, msgString.lastIndexOf(',')).toInt();
    int blue = msgString.substring(msgString.lastIndexOf(',') + 1).toInt();

    color=((red <<16)|(green <<8)|blue);
    mqttSendColorState();
  }
}

void mqttSendState()
{
  client.publish(MQTT_LED_STATE, (ledState) ? "ON": "OFF", true);
}

void mqttSendBrightnessState()
{
  char buff[4];
  itoa(maxBrightness, buff, 10);
  client.publish(MQTT_LED_BRIGHTNESS_STATE, buff, true);
}

void mqttSendColorState()
{
  int red = color>>16 & 0xFF;
  int green = color>>8 & 0xFF;
  int blue = color & 0xFF;
  char buff[12];

  sprintf(buff, "%i,%i,%i", red, green, blue);
  client.publish(MQTT_LED_COLOR_STATE, buff, true);
}

// LED
/**
* Coupe tout le strip de led.
*/
void ledBlackAll()
{
  FastLED.clear();
  FastLED.show();
}

/**
 * Utilise pour indiquer une erreur sur la reception de l'effet.
 */
void ledError()
{
  for (int i = 0; i < LED_NUM; i++) {
    if ((i % 2) == 0) {
      leds[i] = CRGB::Black;
    } else {
      leds[i] = CRGB::Red;
    }
  }

  FastLED.delay(1000);
}

/**
 * Affiche une couleur de manière uniforme sur le strip.
 * Pour éviter un éclairage basique, on applique un breath qui permet
 * de faire respirer la couleur (brightness).
 */
void ledDisplay()
{
  /* Natural Breathing LED
   * Source : http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
   * La formule : (exp(sin(x / freq)) - (1/e)) * (maxBrightness/(e-(1/e))
   * freq = 2000.0 * PI (-> interval de 4sec)
   * Fréquence respiratoire : https://fr.wikipedia.org/wiki/Fr%C3%A9quence_respiratoire
   * Test perso rapide révéillé : 18 cycles par minutes -> interval de 3.33sec.
   *
   * En remplaçant 1/e par 0.349 on arrive à un minimal de 1.00x ce qui permet d'avoir les leds qui ne s'éteignent pas
   * Il faut alors remplacer maxBrightness par maxBrightness - 1 dans la dernière partie de l'équation
   */
   // J'ai essayé en précalculant la dernière partie à chaque changement de brightness.
   // Cela ne change rien on nombre de frames traitées (400).
  float breath = (exp(sin(millis() / 4500.0 * PI)) + 1.2) * ((curbrightness - 1) / (EULER - (1 / EULER)));

  fill_solid(leds, LED_NUM, color);
  FastLED.setBrightness(breath);
  /* Debug
  EVERY_N_SECONDS(1) {
    Debug.print(breath);
    Debug.print(" | ");
    Debug.print(curbrightness);
    Debug.print("/");
    Debug.println(maxBrightness);
  }
  */
  fps++;
  if (SHOW_FPS) {
    EVERY_N_SECONDS(1) {
      Debug.print("FPS : ");
      Debug.println(fps);
      fps=0;
    }
  }
  FastLED.show();
}

void loop() {
  // OTA
  ArduinoOTA.handle();

  // RemoteDebug
  Debug.handle();

  // MQTT
  testConnectMQTT();
  client.loop();

  // LED
  if (!ledState) {
    FastLED.delay(1000);
  } else {
    if (startTime + LED_DURATION <= millis()) {
      Debug.println("END");
      ledState = false;
      ledBlackAll();
      mqttSendState();
    } else {
      EVERY_N_SECONDS(10) {
        if (curbrightness <= maxBrightness) {
          curbrightness++;
        }
      }

      ledDisplay();
    }
  }
}
