#include <Adafruit_NeoPixel.h>
#include <Time.h>
#include <TimeLib.h>

#ifdef __AVR__
#include <avr/power.h>
#endif
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>

#include "DHT.h"
#define DHTPIN D5    // what digital pin we're connected to
#define DHTTYPE DHT11   // DHT 11
DHT dht(DHTPIN, DHTTYPE, 15);

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

short pMode;
short pSpeed;
short pC;
uint32_t pColor;
unsigned long int last_step;
unsigned long int last_reset;
unsigned int wait = 50;
short oldhour, oldminute, oldsecond;
short count;
bool larson_direction;
short pBrightness;
time_t oldtime;
long int temp_in, temp_out;

String weatherDescription = "";
String weatherLocation = "";
String Country;
float Temperature;
float Humidity;
float Pressure;
WiFiClient client;
char servername[] = "api.openweathermap.org"; // remote server we will connect to
String result;
String APIKEY = "YOUR_OPENWEATHERMAP_KEY";
String CityID = "2964574"; //Dublin, Ireland


#define FIXED 0
#define LARSON 1
#define RAINBOW 2
#define RAINBOW2 3
#define CLOCK 4
#define FIRE 5
#define OFF 6
#define THERMOIN 9
#define THERMOOUT 10
#define PIN D4
#define NUM_PIXELS 60
#define BR 255
#define WAIT 300
#define PCOLOR 0xFFA0A0
#define PR 255
#define PG 128
#define PB 40
#define R 255
#define G 215
#define B 40

ESP8266WebServer server(80);

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_PIXELS, PIN, NEO_GRB + NEO_KHZ800);
boolean fixed = false;

void handle_root() {
  int c = -1;
  int b = -1;
  int m = -1;
  unsigned long int s;
  int v = -1;
    char tarray[16]; 
  if (server.hasArg("b"))
    b = server.arg("b").toInt();
  if (server.hasArg("c"))
    c = server.arg("c").toInt();
  if (server.hasArg("m"))
    m = server.arg("m").toInt();
  if (server.hasArg("s")) {
    server.arg("s").toCharArray(tarray, sizeof(tarray));
    s=atoi(tarray);
  }
  if (server.hasArg("v"))
    v = server.arg("v").toInt();
  temp_in = v;
  if (s != -1)
    oldtime = s % 86400;
  else
    s = now();
  if (c != -1)
    pC = c;
  int h, t;
  switch (c) {
    case 1: pColor = 0xFFDDAA; break;
    case 2: pColor = 0xFFFF00; break;
    case 3: pColor = 0xFFC900; break;
    case 4: pColor = 0xFF8900; break;
    case 5: pColor = 0xFF0000; break;

    case 6: pColor = 0xC57DFF; break;
    case 7: pColor = 0xFF4688; break;
    case 8: pColor = 0xFE00FF; break;
    case 9: pColor = 0xFF00C7; break;
    case 10: pColor = 0x880F8A; break;
    
    case 11: pColor = 0xC6C1FA; break;
    case 12: pColor = 0x867EFF; break;
    case 13: pColor = 0x2000FF; break;
    case 14: pColor = 0x5242FF; break;
    case 15: pColor = 0x0000FF; break;

    case 16: pColor = 0xC5FF83; break;
    case 17: pColor = 0x89FFC3; break;
    case 18: pColor = 0x54FFC3; break;
    case 19: pColor = 0xC5FF00; break;
    case 20: pColor = 0x00FF00; break;
    
    case -1: {
        fixed = false;
        break;
      }

    default: pColor = 0XFFDF30; break;
  }
  if (b <= 255 && b > -1) {
    pBrightness = b;
    strip.setBrightness(b);
  }

  if (m <= 10 && m > -1) {
    pMode = m;
  }
  if (v > -1) {
    pSpeed = v;
  }

  String content = "";
  content += "{\"c\":\"";
  content += pC;
  content += "\", \"b\":\"";
  content += pBrightness;
  content += "\", \"m\":\"";
  content += pMode;
  content += "\", \"v\":\"";
  content += pSpeed;
  content += "\", \"s\":\"";
  content += s;
  content += "\", \"t\":\"";
  content += temp_in;
  content += "\"}\n\n";
  server.sendHeader("Access-Control-Allow-Origin", "*", true);
  server.send(200, "text/plain", content);

  change_lamp();
}

void lightsOff() {
  for ( int i = 0; i < NUM_PIXELS; i++) {
    strip.setPixelColor(i, 0, 0, 0);
  }
  strip.show();
}

// setup phase
void setup() {
  strip.begin();
  lightsOff();
  for (int i = 0; i < NUM_PIXELS; i++)
    strip.setPixelColor(i, 255, 0, 0);
  strip.show();
  WiFi.begin(ssid, password);
  dht.begin();

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    for (int i = 0; i < NUM_PIXELS; i++)
      strip.setPixelColor(i, 255, 255, 0);
    strip.show();
  }
  pinMode(PIN, OUTPUT);
  ArduinoOTA.onStart([]() {
  });
  ArduinoOTA.onEnd([]() {
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
  });
  ArduinoOTA.onError([](ota_error_t error) {
  });
  ArduinoOTA.begin();
  server.on("/", handle_root);
  server.begin();
  pColor = 0xFFDDAA;
  pC = 1;
  pBrightness = 255;
  pMode = OFF;
  pSpeed = 5;
  count = 0;
  larson_direction = true;
  last_step = millis();
  ////dbg.print(F("Ready       "));
  last_reset = millis();
  oldhour = 0;
  oldminute = 0;
  oldsecond = 0;
  for (int i = 0; i < NUM_PIXELS; i++)
    strip.setPixelColor(i, 0, 255, 0);
  strip.show();
  delay(500);
  lightsOff();
}

// loop sequence
void loop() {
  ArduinoOTA.handle();
  server.handleClient();
  handle_lamp();
}

uint32_t dimColor(uint32_t color) {
  double scale = 0.75;
  uint32_t r = color & 0xFF0000;
  uint32_t g = color & 0x00FF00;
  uint32_t b = color & 0x0000FF;
  r = (uint32_t)(floor(r * scale)) & 0xFF0000;
  g = (uint32_t)(floor(g * scale)) & 0x00FF00;
  b = (uint32_t)(floor(b * scale)) & 0x0000FF;
  return r + g + b;
}

void handle_lamp() {
  if (millis() - last_step >= wait) {
    if (pMode == LARSON) {
      if (larson_direction == true) {
        uint32_t tmpcolor;
        strip.setPixelColor(count, pColor);
        tmpcolor = pColor;
        for (int x = count; x > 0; x--) {
          //old_val[x - 1] = dimColor(old_val[x - 1]);
          //strip.setPixelColor(x - 1, old_val[x - 1]);
          tmpcolor = dimColor(tmpcolor);
          strip.setPixelColor(x - 1, tmpcolor);
        }
        strip.show();
        count += 1;
        if (count == NUM_PIXELS) {
          larson_direction = false;
          count -= 1;
        }
      } else {
        uint32_t tmpcolor;
        strip.setPixelColor(count, pColor);
        tmpcolor = pColor;
        for (int x = count; x <= NUM_PIXELS ; x++) {
          tmpcolor = dimColor(tmpcolor);
          strip.setPixelColor(x - 1, tmpcolor);
        }
        strip.show();
        count -= 1;
        if (count == 0) {
          larson_direction = true;
        }
      }
    } else {
      if (pMode == RAINBOW) {
        for (short i = 0; i < NUM_PIXELS; i++) {
          //strip.setPixelColor(i, Wheel((i + count) & 255));
          strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + count) & 255));
        }
        strip.show();
        count += 1;
      } else {
        if (pMode == RAINBOW2) {
          for (short i = 0; i < NUM_PIXELS; i++) {
            strip.setPixelColor(i, Wheel((count) & 255));
          }
          strip.show();
          count += 1;

        } else {
          if (pMode == CLOCK) {
            time_t tmpnow = now();
            if (tmpnow != oldtime) {
              short th = (hour(tmpnow) % 12) * 5;
              short tm = minute(tmpnow);
              short ts = second(tmpnow);
              if (th != oldhour)
                strip.setPixelColor(oldhour, 0, 0, 0);
              if (tm != oldminute)
                strip.setPixelColor(oldminute, 0, 0, 0);
              if (ts != oldsecond)
                strip.setPixelColor(oldsecond, 0, 0, 0);
              strip.setPixelColor(th, 255, 0, 0);
              strip.setPixelColor(tm, 0, 0, 255);
              strip.setPixelColor(ts, 0, 255, 0);
              oldhour = th;
              oldminute = tm;
              oldsecond = ts;
              strip.show();
              oldtime = tmpnow;

            }
          } else {
            if (pMode == FIRE) {
              for (int x = 0; x < NUM_PIXELS; x++) {
                int flicker = random(0, 150);
                int r1 = R - flicker;
                int g1 = G - flicker;
                int b1 = B - flicker;
                if (g1 < 0) g1 = 0;
                if (r1 < 0) r1 = 0;
                if (b1 < 0) b1 = 0;
                strip.setPixelColor(x, r1, g1, b1);
              }
              strip.show();
              //wait = (random(25, 100));
              wait = (random(10 + (5 * (10 - pSpeed)), 50 + (10 * (10 - pSpeed))));
            } else {
              if (pMode == OFF) {
                lightsOff();
              }
            }
          }
        }
      }
    }
    last_step = millis();
  }
}


void change_lamp() {
  switch (pMode) {
    case FIXED:
      for ( int i = 0; i < NUM_PIXELS; i++) {
        strip.setPixelColor(i, pColor);
      }
      strip.setBrightness(pBrightness);
      strip.show();
      count = 0;
      last_step = millis();
      break;
    case LARSON:
      larson_direction = true;
      count = 0;
      switch (pSpeed) {
        case 10:
          wait = 8;
          break;
        case 9:
          wait = 12;
          break;
        case 8:
          wait = 16;
          break;
        case 7:
          wait = 20;
          break;
        case 6:
          wait = 25;
          break;
        case 5:
          wait = 30;
          break;
        case 4:
          wait = 40;
          break;
        case 3:
          wait = 50;
          break;
        case 2:
          wait = 65;
          break;
        case 1:
          wait = 80;
          break;
        case 0:
          wait = 100;
          break;
      }
      lightsOff();
      last_step = millis() - wait - 1;
      break;
    case RAINBOW:
    case RAINBOW2:
      ////dbg.setCursor(0, 1);
      ////dbg.print(pSpeed);
      switch (pSpeed) {
        case 10:
          wait = 15;
          break;
        case 9:
          wait = 20;
          break;
        case 8:
          wait = 25;
          break;
        case 7:
          wait = 30;
          break;
        case 6:
          wait = 40;
          break;
        case 5:
          wait = 50;
          break;
        case 4:
          wait = 75;
          break;
        case 3:
          wait = 100;
          break;
        case 2:
          wait = 125;
          break;
        case 1:
          wait = 150;
          break;
        case 0:
          wait = 200;
          break;
      }
      lightsOff();
      last_step = millis() - wait - 1;
      count = 0;
      break;
    case CLOCK:
      wait = 100;
      lightsOff();
      setTime(oldtime);
      oldhour = (hour(oldtime) % 12) * 5;
      oldminute = minute(oldtime);
      oldsecond = second(oldtime);
      strip.setPixelColor(oldhour, 255, 0, 0);
      strip.setPixelColor(oldminute, 0, 0, 255);
      strip.setPixelColor(oldsecond, 0, 255, 0);
      strip.show();
      break;
    case FIRE:
      wait = 0;
      break;
    case OFF:
      wait = 1000;
      lightsOff();
      break;
    case THERMOIN:
      lightsOff();
      temp_in = int(dht.readTemperature());
      for (int i = 0; i < temp_in * 2; i += 2) {
        strip.setPixelColor(i, Wheel(int(  (30 - (int(i / 2))) * 128 / 30)   ));
        strip.setPixelColor(i + 1, Wheel(int((30 - (int(i / 2))) * 128 / 30)));
      }
      strip.show();
      break;
    case THERMOOUT:
      lightsOff();
      getWeatherData();
      temp_out = int(Temperature);
      for (int i = 0; i < temp_out * 2; i += 2) {
        strip.setPixelColor(i, Wheel(int(  (30 - (int(i / 2))) * 128 / 30)   ));
        strip.setPixelColor(i + 1, Wheel(int((30 - (int(i / 2))) * 128 / 30)));
      }
      strip.show();
      break;
  }
}


void getWeatherData() //client function to send/receive GET request data.
{
  if (client.connect(servername, 80)) {  //starts client connection, checks for connection
    client.println("GET /data/2.5/weather?id=" + CityID + "&units=metric&APPID=" + APIKEY);
    client.println("Host: api.openweathermap.org");
    client.println("User-Agent: ArduinoWiFi/1.1");
    client.println("Connection: close");
    client.println();
  }
  else {
    Serial.println("connection failed"); //error message if no client connect
    Serial.println();
  }

  while (client.connected() && !client.available()) delay(1); //waits for data
  while (client.connected() || client.available()) { //connected or data available
    char c = client.read(); //gets byte from ethernet buffer
    result = result + c;
  }

  client.stop(); //stop client
  result.replace('[', ' ');
  result.replace(']', ' ');
  Serial.println(result);

  char jsonArray [1024];
  result.toCharArray(jsonArray, sizeof(jsonArray));
  jsonArray[result.length() + 1] = '\0';

  StaticJsonBuffer<1024> json_buf;
  JsonObject &root = json_buf.parseObject(jsonArray);
  if (!root.success())
  {
    Serial.println("parseObject() failed");
  }

  String location = root["name"];
  String country = root["sys"]["country"];
  float temperature = root["main"]["temp"];
  float humidity = root["main"]["humidity"];
  String weather = root["weather"]["main"];
  String description = root["weather"]["description"];
  float pressure = root["main"]["pressure"];

  weatherDescription = description;
  weatherLocation = location;
  Country = country;
  Temperature = temperature;
  Humidity = humidity;
  Pressure = pressure;

}


// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

