/******************  LIBRARY SECTION *************************************/
#include <Adafruit_NeoPixel.h>    //https://github.com/adafruit/Adafruit_NeoPixel
#include <SimpleTimer.h>          //https://github.com/thehookup/Simple-Timer-Library
#include <PubSubClient.h>         //https://github.com/knolleary/pubsubclient
#include <ESP8266WiFi.h>          //if you get an error here you need to install the ESP8266 board manager 
#include <ESP8266mDNS.h>          //if you get an error here you need to install the ESP8266 board manager 
#include <ArduinoOTA.h>           //ArduinoOTA is now included with the ArduinoIDE

/*****************  START USER CONFIG SECTION *********************************/
/*****************  START USER CONFIG SECTION *********************************/
/*****************  START USER CONFIG SECTION *********************************/
/*****************  START USER CONFIG SECTION *********************************/

#define USER_SSID                 "YOUR_WIFI_SSID"
#define USER_PASSWORD             "YOUR_WIFI_PASSWORD"
#define USER_MQTT_SERVER          "YOUR MQTT SERVER"
#define USER_MQTT_PORT            1883
#define USER_MQTT_USERNAME        "YOUR_MQTT_LOGIN"
#define USER_MQTT_PASSWORD        "YOUR_MQTT_PASSWORD"
#define USER_MQTT_CLIENT_NAME     "SunriseMCU"           //used to define MQTT topics, MQTT Client ID, and ArduinoOTA
#define LED_PIN 5                                        //pin where the led strip is hooked up
#define NUM_LEDS 136                                     //number of LEDs in the strip
#define BRIGHTNESS 255                                   //strip brightness 255 max
#define SUNSIZE 30                                       //percentage of the strip that is the "sun"

/*****************  END USER CONFIG SECTION *********************************/
/*****************  END USER CONFIG SECTION *********************************/
/*****************  END USER CONFIG SECTION *********************************/
/*****************  END USER CONFIG SECTION *********************************/
/*****************  END USER CONFIG SECTION *********************************/

/***********************  WIFI AND MQTT SETUP *****************************/
/***********************  DON'T CHANGE THIS INFO *****************************/

const char* ssid = USER_SSID ; 
const char* password = USER_PASSWORD ;
const char* mqtt_server = USER_MQTT_SERVER ;
const int mqtt_port = USER_MQTT_PORT ;
const char *mqtt_user = USER_MQTT_USERNAME ;
const char *mqtt_pass = USER_MQTT_PASSWORD ;
const char *mqtt_client_name = USER_MQTT_CLIENT_NAME ; 

/*****************  DECLARATIONS  ****************************************/
WiFiClient espClient;
PubSubClient client(espClient);
SimpleTimer timer;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRBW + NEO_KHZ800);

/*****************  GENERAL VARIABLES  *************************************/

bool boot = true;
int sun = (SUNSIZE * NUM_LEDS)/100;
int aurora = NUM_LEDS;
//int aurora = (SUNSIZE+40 * NUM_LEDS)/100;
int sunPhase = 100;
int whiteLevel = 100;
byte red = 127; 
byte green = 127;
byte blue = 127;
byte white = 127;
String effect = "off";
char charPayload[50];
int wakeDelay = 1000;
int fadeStep = 98;
int oldFadeStep = 0;
int currentAurora = 100;
int oldAurora = 0;
int currentSun = 100;
int oldSun = 0;
int sunFadeStep = 98;

void setup_wifi() 
{
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.hostname(USER_MQTT_CLIENT_NAME);
  WiFi.begin(ssid, password);

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

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

void reconnect() 
{
  // Loop until we're reconnected
  int retries = 0;
  while (!client.connected()) {
    if(retries < 150)
    {
        Serial.print("Attempting MQTT connection...");
      // Attempt to connect
      if (client.connect(mqtt_client_name, mqtt_user, mqtt_pass)) 
      {
        Serial.println("connected");
        // Once connected, publish an announcement...
        if(boot == true)
        {
          client.publish(USER_MQTT_CLIENT_NAME"/checkIn","Rebooted");
          boot = false;
        }
        if(boot == false)
        {
          client.publish(USER_MQTT_CLIENT_NAME"/checkIn","Reconnected"); 
        }
        client.subscribe(USER_MQTT_CLIENT_NAME"/command");
        client.subscribe(USER_MQTT_CLIENT_NAME"/effect");
        client.subscribe(USER_MQTT_CLIENT_NAME"/color");
        client.subscribe(USER_MQTT_CLIENT_NAME"/white");
        client.subscribe(USER_MQTT_CLIENT_NAME"/wakeAlarm");
      } 
      else 
      {
        Serial.print("failed, rc=");
        Serial.print(client.state());
        Serial.println(" try again in 5 seconds");
        retries++;
        // Wait 5 seconds before retrying
        delay(5000);
      }
    }
    else
    {
      ESP.restart();
    }
  }
}

/************************** MQTT CALLBACK ***********************/
/************************** MQTT CALLBACK ***********************/
/************************** MQTT CALLBACK ***********************/
/************************** MQTT CALLBACK ***********************/
/************************** MQTT CALLBACK ***********************/


void callback(char* topic, byte* payload, unsigned int length) 
{
  Serial.print("Message arrived [");
  String newTopic = topic;
  Serial.print(topic);
  Serial.print("] ");
  payload[length] = '\0';
  String newPayload = String((char *)payload);
  int intPayload = newPayload.toInt();
  Serial.println(newPayload);
  Serial.println();
  newPayload.toCharArray(charPayload, newPayload.length() + 1); 
  
  if (newTopic == USER_MQTT_CLIENT_NAME"/command") 
  {
    effect = newPayload;
    client.publish(USER_MQTT_CLIENT_NAME"/state", charPayload);
  }
  if (newTopic == USER_MQTT_CLIENT_NAME"/wakeAlarm")
  {
    whiteLevel = 0;
    sunPhase = 0;
    fadeStep = 0;
    sunFadeStep = 0;
    effect = "sunrise";
    wakeDelay = intPayload*10;
    timer.setTimeout(wakeDelay, increaseSunPhase);
    timer.setTimeout(wakeDelay, increaseWhiteLevel);
    timer.setTimeout((wakeDelay/80), increaseFadeStep);
    timer.setTimeout((wakeDelay/80), increaseSunFadeStep);
    client.publish(USER_MQTT_CLIENT_NAME"/command", "sunrise", true); //this sets a retained value to restore the sunrise in case of reconnect
    client.publish(USER_MQTT_CLIENT_NAME"/state", "mqttRGB"); //this is needed for the state in home assistant
  }
  if (newTopic == USER_MQTT_CLIENT_NAME"/white")
  {
    white = intPayload;
    client.publish(USER_MQTT_CLIENT_NAME"/whiteState", charPayload);
  }
  if (newTopic == USER_MQTT_CLIENT_NAME "/color")
  {
    client.publish(USER_MQTT_CLIENT_NAME "/colorState", charPayload); 
    // get the position of the first and second commas
    uint8_t firstIndex = newPayload.indexOf(',');
    uint8_t lastIndex = newPayload.lastIndexOf(',');
    
    uint8_t rgb_red = newPayload.substring(0, firstIndex).toInt();
    if (rgb_red < 0 || rgb_red > 255) 
    {
      return;
    } 
    else 
    {
      red = rgb_red;
    }
    
    uint8_t rgb_green = newPayload.substring(firstIndex + 1, lastIndex).toInt();
    if (rgb_green < 0 || rgb_green > 255) 
    {
      return;
    } 
    else 
    {
      green = rgb_green;
    }
    
    uint8_t rgb_blue = newPayload.substring(lastIndex + 1).toInt();
    if (rgb_blue < 0 || rgb_blue > 255) 
    {
      return;
    } 
    else 
    {
      blue = rgb_blue;
    }
  }
}

/*****************  GLOBAL LIGHT FUNCTIONS  *******************************/
/*****************  GLOBAL LIGHT FUNCTIONS  *******************************/
/*****************  GLOBAL LIGHT FUNCTIONS  *******************************/
/*****************  GLOBAL LIGHT FUNCTIONS  *******************************/
/*****************  GLOBAL LIGHT FUNCTIONS  *******************************/


void increaseSunPhase()
{
  if (sunPhase < 100)
  {
    sunPhase++;
    timer.setTimeout(wakeDelay, increaseSunPhase);
    Serial.print("sunPhase: ");
    Serial.println(sunPhase);
    timer.setTimeout((wakeDelay/80), increaseFadeStep);
    timer.setTimeout((wakeDelay/80), increaseSunFadeStep);
  }
}

void increaseSunFadeStep()
{
  if (sunFadeStep < 98)
  {
    sunFadeStep++;
    timer.setTimeout((wakeDelay/80), increaseSunFadeStep);
  }
}

void increaseFadeStep()
{
  if (fadeStep < 98)
  {
    fadeStep++;
    timer.setTimeout((wakeDelay/80), increaseFadeStep);
  }
}

void increaseWhiteLevel()
{
  if(whiteLevel < 100)
  {
    whiteLevel++;
    timer.setTimeout(wakeDelay, increaseWhiteLevel);
  }
}

void drawSun()
{
  currentSun = map(sunPhase, 0, 100, 0, sun);
  if(currentSun % 2 != 0)
  {
    currentSun--;
  }
  if (currentSun != oldSun)
  {
    sunFadeStep = 0;
  }
  
  int sunStart = (NUM_LEDS/2)-(currentSun/2);
  int newSunLeft = sunStart-1;
  int newSunRight = sunStart+currentSun;
  if(newSunLeft >= 0 && newSunRight <= NUM_LEDS && sunPhase > 0)
  {
   int redValue =  map(sunFadeStep, 0, 100, 127, 255);
   int whiteValue = map(sunFadeStep, 0, 100, 0, whiteLevel);
   strip.setPixelColor(newSunLeft, redValue, 25,0,whiteValue);
   strip.setPixelColor(newSunRight, redValue, 25,0,whiteValue);
  }
  for(int i = sunStart; i < sunStart+currentSun; i++)
  {
    strip.setPixelColor(i, 255, 64,0,whiteLevel); 
  }
  oldSun = currentSun;
}


void drawAurora()
{
  currentAurora = map(sunPhase, 0, 100, 0, aurora);
  if(currentAurora % 2 != 0)
  {
    currentAurora--;
  }
  if (currentAurora != oldAurora)
  {
    fadeStep = 0;
  }
  int sunStart = (NUM_LEDS/2)-(currentAurora/2);
  int newAuroraLeft = sunStart-1;
  int newAuroraRight = sunStart+currentAurora;
  if(newAuroraLeft >= 0 && newAuroraRight <= NUM_LEDS)
  {
   int redValue =  map(fadeStep, 0, 100, whiteLevel, 127);
   int greenValue =  map(fadeStep, 0, 100, 0, 25);
   strip.setPixelColor(newAuroraRight, redValue, greenValue,0,0);
   strip.setPixelColor(newAuroraLeft, redValue, greenValue,0,0);
  }
  for(int i = sunStart; i < sunStart+currentAurora; i++)
  {
    strip.setPixelColor(i, 127, 25,0,0); 
  }
  oldFadeStep = fadeStep;
  oldAurora = currentAurora;
}

void drawAmbient()
{
  for(int i = 0; i < NUM_LEDS; i++)
  {
    strip.setPixelColor(i, whiteLevel, 0,0,0); 
  }
}

void sunRise()
{
  drawAmbient();
  drawAurora();
  drawSun();
}

void off()
{
  for(int i = 0; i < NUM_LEDS; i++)
  {
    strip.setPixelColor(i, 0,0,0,0); 
  }
}

void mqttRGB()
{
  for(int i = 0; i < NUM_LEDS; i++)
  {
    strip.setPixelColor(i, red,green,blue,white); 
  }
}

void selectEffect()
{
  if(effect == "sunrise")
  {
    sunRise();
  }
  if(effect == "mqttRGB")
  {
    mqttRGB();
  }
  if(effect == "off")
  {
    off();
  }
}

void setup() 
{
  Serial.begin(115200);
  WiFi.setSleepMode(WIFI_NONE_SLEEP);
  WiFi.mode(WIFI_STA);
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
  ArduinoOTA.setHostname(USER_MQTT_CLIENT_NAME);
  ArduinoOTA.begin(); 
  strip.setBrightness(BRIGHTNESS);
  strip.begin();
}

void loop() 
{
  if (!client.connected()) 
  {
    reconnect();
  }
  client.loop();
  ArduinoOTA.handle();
  timer.run();
  selectEffect();
  if(boot == false)
  {
    strip.show(); 
  }
}