// Pocket Library 1.0 by Akaath - Telegram: @Akaath
// A library of classic books that fits in your pocket
// No copyright on this code, but you should follow the licenses of the included libraries

#include <Arduino.h>
#include <Preferences.h>
#include <WiFi.h>
#include "FS.h"
#include <LittleFS.h>
#include <DNSServer.h>
#include <ESPAsyncWebServer.h>
#include <FastLED.h>

// Set up the DNS server on 192.168.0.1
// Set up the async HTTP server on port 80
// Init RGB LED stuff
DNSServer dns;
AsyncWebServer http_server(80);
IPAddress ap_ip(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
CRGB leds[4];
Preferences prefs;
int led_current_mode;
int frame_time;
bool reset_led;
fl::CRGB next_color;

// A very messy last-minute RGB led function
void ledUpdate(){
  if(esp_timer_get_time() > (frame_time + 33333)){
    frame_time = esp_timer_get_time();
    switch(led_current_mode) {
      case 0: {
        // Pulse green and fade out
        int frame_count = (frame_time / 33333) % 60;
        for (int i = 0; i < 4; i++){
          if ((frame_count == 0) || reset_led){
              leds[i] = CRGB::DarkGreen;
              leds[i].nscale8(64);
          }else{
              leds[i].nscale8(230);
          }
        }
      }
      break;
      case 1: {
        // Cycle through the colors of the pride flag
        fl::CRGB colors[6] = {CRGB::Purple, CRGB::Blue, CRGB::DarkGreen, CRGB::Yellow, CRGB::DarkOrange, CRGB::Red};
        int frame_count = (frame_time / 33333) % 180;
        for (int i = 0; i < 4; i++){
          if ((frame_count == 0) || reset_led){
              leds[i] = colors[0];
              leds[i].nscale8(64);
          }else{
            if (frame_count % 30 == 0){
              leds[i] = colors[frame_count / 30];
              leds[i].nscale8(64);
            }
          }
        }
      }
      break;
      case 2: {
        // Add a single random color LED at a time to the chain of 4
        int frame_count = (frame_time / 33333) % 30;
        if (reset_led){
          for (int i = 0; i < 4; i++){
            leds[i] = CRGB::Black;
          }
        }
        if (frame_count == 0){
          for (int x = 3; x > 0; x--){
            leds[x] = leds[x - 1];
          }
          leds[0].setHSV(esp_random() % 256, 128 + esp_random() % 128, 64);
        }
      }
      break;
      case 3: {
        // Randomly pick a color, and fade to another random color
        int frame_count = (frame_time / 33333) % 64;
        if (reset_led){
          fl::u8 hue = esp_random() % 256;
          fl::u8 sat = 128 + (esp_random() % 128);
          for (int i = 0; i < 4; i++){
            leds[i].setHSV(hue, sat, 64);
          }
          next_color.setHSV(esp_random() % 256, 128 + (esp_random() % 128), 64);
        }
        if (frame_count == 0){
          next_color.setHSV(esp_random() % 256, 128 + (esp_random() % 128), 64);
        } else {
          for (int i = 0; i < 4; i++){
            leds[i] = leds[i].lerp8(next_color, frame_count * 4);
          }
        }
      }
      break;
      case 4: {
        // A really, really jank candle type light
        int frame_count = (frame_time / 33333) % 60;
        if (reset_led){
          for (int i = 0; i < 4; i++){
            leds[i].setHSV(8 + esp_random() % 24, 255, 128);
          }
        }
        if (frame_count == 0){
          for (int i = 0; i < 4; i++){
            if ((esp_random() % 6) == 0){
              leds[i].setHSV(8 + esp_random() % 24, 255, 128);
            }
          }
        } else {
          for (int i = 0; i < 4; i++){
            if ((esp_random() % 8) == 0){
              leds[i].nscale8(254);
            }
          }
        }
      }
      break;
    }
    reset_led = false;
    FastLED.show();
  }
}

// increment the total visitors counter whenever a client connects to wifi
void countClient(WiFiEvent_t event, WiFiEventInfo_t info){
 
  prefs.putInt("client_count", prefs.getInt("client_count") + 1);
}

// Since we're doing DNS redirection, we should catch wrong host requests
// and redirect them back to 192.168.0.1 rather than serve content on the
// incorrect host name
class WrongHostRedirect : public AsyncWebHandler {
public:
  bool canHandle(AsyncWebServerRequest *request) const override {
    if(request->host() == "192.168.0.1"){
      return false;
    } else {
      return true;
    }
  }
  void handleRequest(AsyncWebServerRequest *request) {
    request->redirect("http://192.168.0.1");
  }
};

// If we get a 404 error then redirect to the home page
void error404(AsyncWebServerRequest *request){
  request->redirect("http://192.168.0.1");
}

// One-time setup
void setup(){
  // Init RGB led stuff
  frame_time = 0;
  reset_led = true;
  // Init saved preferences if not already set
  prefs.begin("prefs", false);
  if(!prefs.isKey("led_mode")){
    prefs.putInt("led_mode", 0);
  }
  led_current_mode = prefs.getInt("led_mode");
  if(!prefs.isKey("client_count")){
    prefs.putInt("client_count", 0);
  }
  // Begin the RGB LED controller, the LittleFS filesystem, and the WiFi AP
  FastLED.addLeds<NEOPIXEL, 4>(leds, 4);
  LittleFS.begin();
  WiFi.softAPConfig(ap_ip, ap_ip, subnet);
  WiFi.softAP("PocketLibrary");
  WiFi.onEvent(countClient, ARDUINO_EVENT_WIFI_AP_STACONNECTED);
  dns.start(53, "*", WiFi.softAPIP());
  // Add handlers for serving redirects, 404s, and static files
  http_server.addHandler(new WrongHostRedirect()).setFilter(ON_AP_FILTER);
  http_server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html").setTryGzipFirst(true).setCacheControl("max-age=3600");
  http_server.onNotFound(error404);
  // Add a couple handlers for the stats and LED control endpoint specifically
  http_server.on("/stats", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "application/json", "{\"free_memory\": " + String(ESP.getFreeHeap()) + ", \"visitor_count\": " + String(prefs.getInt("client_count")) + ", \"uptime\": " + String(esp_timer_get_time()) + ", \"led_mode\": " + String(prefs.getInt("led_mode")) + "}");
  });
  http_server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request){
    if(request->hasParam("mode")){
      const AsyncWebParameter* led_mode = request->getParam("mode");
      prefs.putInt("led_mode", led_mode->value().toInt());
      led_current_mode = prefs.getInt("led_mode");
      reset_led = true;
    };
    request->send(200, "text/plain", "200 OK");
  });
  // Start the server
  http_server.begin();
}

void loop(){
  // Process DNS requests
  dns.processNextRequest();
  // Process LED updates
  ledUpdate();
  // Async HTTP happens in the background
}