#include <WiFi.h>
#include "time.h"
#include "RTClib.h"
#include "structs.h"
#include <Wire.h>
#include <math.h>
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
RTC_DS3231 rtc;
#define NIGHT 0  //FLAG Nacht
#define SUNRISE 1 //FLAG Sonnenaufgang
#define DAY 2 //FLAG Tag
#define SUNSET 3  //FLAG Sonnuntergang
#define CLOUD_ON 4  //FLAG Wolke kommt
#define CLOUD_DELAY 5 // FLAG Wolke verweilt
#define CLOUD_OFF 6 //FLAG Wolke geht
#define OFF 7
#define DAYLIGHT 8 //FLAG Tageslicht
#define REDLIGHT 9 //FLAG Rotlicht
#define BLUELIGHT 10  //FLAG Blaulicht
#define LIGHTCHANALS 16 //Anzahl der Lichtkanäle
#define MAXBRIGHTNESS_DAYLIGHT 10  //Maximale Helligkeit in Prozent
#define MAXBRIGHTNESS_REDLIGHT 10  //Maximale Helligkeit in Prozent
#define MAXBRIGHTNESS_BLUELIGHT 10  //Maximale Helligkeit in Prozent
#define CLOUD_SPEED 1           // max 0-100 sek Zeit für Dimmvorgang in Sek.2
#define CLOUD_AMOUNT 5000       // max 0-65535  Bereich für Zufallszahl je kleiner um so häufiger 5000
#define CLOUD_MAXBRIGHTNESS 5  // max 0-100 % Maximale Helligkeit bei Wolke in Prozent
#define CLOUD_MINBRIGHTNESS 1  // max 0-100 % Minimale Helligkeit bei Wolke in Prozent
#define CLOUD_DLYMIN 5        // max 0-500 msek  Minimale Länge der Wolke ohne dimmen 500
#define CLOUD_DLY 5000        // max 0-65535 msek  Bereich für Zufallszahl je größer um so länger 5000
#define SERIAL_OUT 2 // 1 Debug 2 Log 3 PWM Out CH 1
const char* ssid       = "*";
const char* password   = "*";
unsigned long last_calc = 0;
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;
int wifi_set = 1;
CHANAL chanals[] = {  {0, DAYLIGHT, get_ts(0, 1,0), get_ts(0, 6, 0), 2, 2, OFF, 0, 0, 0, 0, 0,0},
                      {1, DAYLIGHT, get_ts(0, 1,0), get_ts(0, 6, 0), 2, 2, OFF, 0, 0, 0, 0, 0,0},
                      {4, DAYLIGHT, get_ts(0, 1,0), get_ts(0, 6, 0), 2, 2, OFF, 0, 0, 0, 0, 0,0},
                      {5, DAYLIGHT, get_ts(0, 1,0), get_ts(0, 6, 0), 2, 2, OFF, 0, 0, 0, 0, 0,0},
                      {8, DAYLIGHT, get_ts(0, 1,0), get_ts(0, 6, 0), 2, 2, OFF, 0, 0, 0, 0, 0,0},                     
                      {9, DAYLIGHT, get_ts(0, 1,0), get_ts(0, 6, 0), 2, 2, OFF, 0, 0, 0, 0, 0,0},
                      {12, DAYLIGHT, get_ts(0, 1,0), get_ts(0, 6, 0), 2, 2, OFF, 0, 0, 0, 0, 0,0},                     
                      {13, DAYLIGHT, get_ts(0, 1,0), get_ts(0, 6, 0), 2, 2, OFF, 0, 0, 0, 0, 0,0},
                      
                      {3, BLUELIGHT,get_ts(0, 1, 10), get_ts(0, 6,0), 1, 3, OFF, 0, 0, 0, 0, 0,0},   
                      {7, BLUELIGHT,get_ts(0, 1, 10), get_ts(0, 6,0), 1, 3, OFF, 0, 0, 0, 0, 0,0},                     
                      {11,BLUELIGHT,get_ts(0, 1, 10), get_ts(0, 6,0), 1, 3, OFF, 0, 0, 0, 0, 0,0},
                      {15,BLUELIGHT,get_ts(0, 1, 10), get_ts(0, 6,0), 1, 3, OFF, 0, 0, 0, 0, 0,0},
                      
                      {2,REDLIGHT, get_ts(0, 0, 10), get_ts(0, 6,0), 3, 1, OFF, 0, 0, 0, 0, 0,0},
                      {6,REDLIGHT, get_ts(0, 0, 10), get_ts(0, 6,0), 3, 1, OFF, 0, 0, 0, 0, 0,0},                     
                      {10,REDLIGHT, get_ts(0, 0, 10), get_ts(0, 6,0), 3, 1, OFF, 0, 0, 0, 0, 0,0},                     
                      {14,REDLIGHT, get_ts(0, 0, 10), get_ts(0, 6,0), 3, 1, OFF, 0, 0, 0, 0, 0,0}};   
                      
DateTime now;
void setup()
{
  Serial.begin(115200);
  randomSeed(analogRead(0));
  if (! rtc.begin())
  {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }
  setupClock(23,59,10,1,2,20); // HH,MM,SS,DD,MM;YY setupClock(10,10,10,3,2,20)
  Serial.print(printTime());
  pwm.begin(); 
  pwm.setPWMFreq(400);  // This is the maximum PWM frequency 
  delay(3000);
  for (unsigned char i = 0; i < 16; i++)
  {
    pwm.setPWM(i, 0, 0);
  }
  for (unsigned char i = 0; i < LIGHTCHANALS; i++)
  {
    Serial.print(printTime());
    Serial.print("  PIN: ");
    Serial.print(chanals[i].pin);
    Serial.print(" Status:");
    Serial.println(chanals[i].status);
  
  }
}
void loop()
{
  if (millis() > last_calc+50)
  {
    now = rtc.now();
    // RTC.getTime();
    //  t.update();
    last_calc = millis();
    for (int i = 0; i < LIGHTCHANALS; i++)
    {
      if (SERIAL_OUT==2)
      {
        Serial.print(int(exp(sqrt(chanals[i].pos) * 0.144295) * 0.4)); //Log
        Serial.print(";"); //Log
      }
      if (TimeStamp() >= chanals[i].starttime && TimeStamp() <= (chanals[i].endtime + get_ts(0, chanals[i].sunsetlength, 0)))
      {
        switch (chanals[i].status)
        {
          case NIGHT:
            set_sunrise(i);
            break;
          case SUNRISE:
            sunrise(i);
            break;
          case DAY:
            daylight(i);
            break;
          case CLOUD_ON:
            cloudy(i);
            break;
          case CLOUD_DELAY:
            cloudydelay(i);
            break;           
          case CLOUD_OFF:
            sunny(i);
            break;
          case SUNSET:
            sunset(i);
            break;
        }
        if (TimeStamp() >= chanals[i].endtime && chanals[i].status != SUNSET && chanals[i].status != OFF)
        {
          chanals[i].status = SUNSET;
          chanals[i].startmillis = last_calc;
          chanals[i].freq = float(chanals[i].pos / 60000.0 / chanals[i].sunsetlength);
          chanals[i].endpos = chanals[i].pos;
          if (SERIAL_OUT==1)
          {
            Serial.print(printTime());
            Serial.print("  PIN: ");
            Serial.print(chanals[i].pin);
            Serial.print(" Sonnenuntergang über ");
            Serial.print(chanals[i].sunsetlength);
            Serial.println(" Minute(n)");   
          }
        }
        if (chanals[i].pos != chanals[i].lastpos)
        {
        setpwm(chanals[i].pin, int(chanals[i].pos));         
        }
      }
      chanals[i].lastpos = chanals[i].pos;
    }
    if (SERIAL_OUT==2)
    {
      Serial.println(); //Log
    }
    if (TimeStamp() >= get_ts(0,0,0) && TimeStamp()<=get_ts(0,0,10))
    {
      for (int i = 0; i < LIGHTCHANALS; i++)
      {
        if (chanals[i].status == OFF)
        {
          chanals[i].status = NIGHT;
          if (SERIAL_OUT==1)
          {
            Serial.print(printTime());
            Serial.print("  PIN: ");
            Serial.print(chanals[i].pin);
            Serial.println(" Set NIGHT");
          }
        }
      }
    }
    if (TimeStamp() >= get_ts(0,10,0) && TimeStamp()<=get_ts(0,10,2))
    {
          Serial.print(printTime());
          Serial.print("  Reset Time ");
          setupClock(23,59,40,1,2,20);
          delay(1000);
          Serial.print(printTime());
    }
  }
}
void setpwm(int CH, int pos)
{
  if (CH >= 0 && CH <= 15)
  {
     pwm.setPWM(CH, 0, int(exp(sqrt(pos) * 0.144295) * 0.4));   
     if (SERIAL_OUT==3 && CH ==1)
     {
      Serial.println(int(exp(sqrt(pos) * 0.144295) * 0.4)); //Log CH1
     }
  }
}
void set_sunrise(int y)
{
  switch (chanals[y].wrb)
  {
    case DAYLIGHT:
      chanals[y].freq = float(sq(log((4095.0/100.0*MAXBRIGHTNESS_DAYLIGHT)/0.4)/0.144295)/ 60000.0 / chanals[y].sunriselength);
      break;
    case REDLIGHT:
      chanals[y].freq = float(sq(log((4095.0/100.0*MAXBRIGHTNESS_REDLIGHT)/0.4)/0.144295)/ 60000.0 / chanals[y].sunriselength);
      break;
    case BLUELIGHT:
      chanals[y].freq = float(sq(log((4095.0/100.0*MAXBRIGHTNESS_BLUELIGHT)/0.4)/0.144295)/ 60000.0 / chanals[y].sunriselength);   
      break;
  }
  chanals[y].status = SUNRISE;
  chanals[y].startmillis = last_calc;
  if (SERIAL_OUT==1)
  {
    Serial.print(printTime());
    Serial.print("  PIN: ");
    Serial.print(chanals[y].pin);
    Serial.print(" Sonnenaufgang über ");
    Serial.print(chanals[y].sunriselength);
    Serial.println(" Minute(n)");   
  }
}
void sunrise(int y)
{
  chanals[y].pos = chanals[y].freq * (last_calc - chanals[y].startmillis);
  switch (chanals[y].wrb)
  {
    case DAYLIGHT:
    if (chanals[y].pos >= float(sq(log((4095.0/100.0*MAXBRIGHTNESS_DAYLIGHT)/0.4)/0.144295)))
    {
      chanals[y].pos = float(sq(log((4095.0/100.0*MAXBRIGHTNESS_DAYLIGHT)/0.4)/0.144295));
      chanals[y].status = DAY;
    }
    break;
    case REDLIGHT:
    if (chanals[y].pos >= float(sq(log((4095.0/100.0*MAXBRIGHTNESS_REDLIGHT)/0.4)/0.144295)))
    {
      chanals[y].pos = float(sq(log((4095.0/100.0*MAXBRIGHTNESS_REDLIGHT)/0.4)/0.144295));
      chanals[y].status = DAY;
    }               
    break;
    case BLUELIGHT:
    if (chanals[y].pos >= float(sq(log((4095.0/100.0*MAXBRIGHTNESS_BLUELIGHT)/0.4)/0.144295)))
    {
      chanals[y].pos = float(sq(log((4095.0/100.0*MAXBRIGHTNESS_BLUELIGHT)/0.4)/0.144295));
      chanals[y].status = DAY;
    }               
    break;
  }
}
void daylight(int y)
{
  word cl;
  float tmp;
  cl = random(0, CLOUD_AMOUNT);
  if (cl < LIGHTCHANALS && chanals[y].wrb==DAYLIGHT) //Nur Kanäle mit Tageslicht
  {
    if (TimeStamp() <= (chanals[y].endtime + get_ts(0,0,((2*CLOUD_SPEED)+10))))
    {
      chanals[y].startmillis = last_calc;
      chanals[y].status = CLOUD_ON;                               //Status Wolke an
      chanals[y].endpos = chanals[y].pos;                       //Aktuelle Position zwischenspeichern
      tmp=random(0, CLOUD_MAXBRIGHTNESS-CLOUD_MINBRIGHTNESS);
      chanals[y].cloudset = (sq(log((4095.0/100.0*(CLOUD_MINBRIGHTNESS + tmp))/0.4)/0.144295)/4095)*100;
      chanals[y].freq = float((chanals[y].pos - (4095.0 / 100 * chanals[y].cloudset)) / 1000.0 / CLOUD_SPEED); //Frequenz berechnen für Dimmvorgang
      if (SERIAL_OUT==1)
      {
        Serial.print(printTime());
        Serial.print("  PIN: ");
        Serial.print(chanals[y].pin);
        Serial.print(" Wolke kommt mit ");
        Serial.print(CLOUD_MINBRIGHTNESS+tmp);   
        Serial.print("% Helligkeit   PWM:");
        Serial.println(int(exp(sqrt(4095.0/100.0*chanals[y].cloudset) * 0.144295) * 0.4));
      }
    }
  }
}
void cloudy(int y)
{
  if (chanals[y].wrb==DAYLIGHT) //Nur Kanäle mit Tageslicht
  {
    chanals[y].pos = chanals[y].endpos - (chanals[y].freq * (last_calc - chanals[y].startmillis));
    if (chanals[y].pos <= float(4095.0 / 100.0 * chanals[y].cloudset))   
    {
      chanals[y].pos = float(4095.0 / 100.0 * chanals[y].cloudset);
      chanals[y].status = CLOUD_DELAY;
      chanals[y].startmillis = last_calc;
      chanals[y].freq = float(((sq(log((4095.0/100.0*MAXBRIGHTNESS_DAYLIGHT)/0.4)/0.144295)) - chanals[y].pos) / 1000.0 / CLOUD_SPEED); //Frequenz berechnen für Dimmvorgang
      chanals[y].endpos = chanals[y].pos;
      chanals[y].dly = random(0, CLOUD_DLY);
      if (SERIAL_OUT==1)
      {
        Serial.print(printTime());
        Serial.print("  PIN: ");
        Serial.print(chanals[y].pin);
        Serial.print(" Wolke bleibt für ");
        Serial.print(float((chanals[y].dly+CLOUD_DLYMIN)/1000.000),3);
        Serial.println(" Sekunden");
      }
    }
  }
}
void cloudydelay(int y)
{
  if (chanals[y].wrb==DAYLIGHT) //Nur Kanäle mit Tageslicht
  {
    if (last_calc > chanals[y].startmillis + CLOUD_DLYMIN + chanals[y].dly)
    {
      chanals[y].status = CLOUD_OFF;
      chanals[y].startmillis = last_calc;
      if (SERIAL_OUT==1)
      {
        Serial.print(printTime());
        Serial.print("  PIN: ");
        Serial.print(chanals[y].pin);
        Serial.println(" Wolke geht");
      }
    }
  }
}
void sunny(int y)
{
  if (chanals[y].wrb==DAYLIGHT) //Nur Kanäle mit Tageslicht
  {
    chanals[y].pos = chanals[y].endpos + (chanals[y].freq * (last_calc - chanals[y].startmillis));
    if (chanals[y].pos >= float(sq(log(4095.0/100.0*MAXBRIGHTNESS_DAYLIGHT/0.4)/0.144295)))
    {
      chanals[y].pos = float(sq(log(4095.0/100.0*MAXBRIGHTNESS_DAYLIGHT/0.4)/0.144295));
      chanals[y].status = DAY;
    }
  }
}
void sunset(int y)
{
  chanals[y].pos = chanals[y].endpos - (chanals[y].freq * (last_calc - chanals[y].startmillis));
  if (chanals[y].pos <= 0.0)
  {
    chanals[y].pos = 0;
    chanals[y].status = OFF;
    if (SERIAL_OUT==1)
    {
      Serial.print(printTime());
      Serial.print("  PIN: ");
      Serial.print(chanals[y].pin);
      Serial.println(" Aus");
    }
  }
}