/*The Cryogenius LED Fairy Light 10 x 10 x 10 Cube Program with OTA 
   uploading to an ESP32. The OTA program also sends
   serial data to the Platformio CLI terminal by using logToClient("logtoClient Loop alive\n")
   command instead of Serial.println.  From the terminal prompt use "pio device monitor --port socket://192.168.1.xx:23" where xx is your IP address.
*/
#include "Effects.h"
#include <ArduinoOTA.h>
#include <Arduino.h>
#define LED_TYPE WS2812B
#define COLOR_ORDER RGB


const char* ssid = "YourSSID";
const char* password = "YourPassword";

WiFiServer server(23);     // Telnet-style port for serial bridge
WiFiClient client;

// Timing for non-blocking delays
unsigned long lastPrint = 0;
const unsigned long printInterval = 1000;

void logToClient(const String& s) {
  if (client && client.connected() && client.availableForWrite()) {
    client.print(s);
  }
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\n\nStarting ESP32...");

  // --- WiFi ---
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println("\nWiFi connected!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // --- OTA Setup ---
  ArduinoOTA.setHostname("esp32-ota");
  ArduinoOTA.onStart([]() {
    Serial.println("OTA Update Start");
    logToClient("OTA Update Starting...\n");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nOTA Update Complete");
    logToClient("OTA Update Complete\n");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("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();
  Serial.println("OTA Ready");

  // --- TCP Serial Bridge ---
  server.begin();
  server.setNoDelay(true);

  Serial.println("\n=== System Ready ===");
  Serial.println("Connect with:");
  Serial.print("pio device monitor --port socket://");
  Serial.print(WiFi.localIP());
  Serial.println(":23");
  Serial.println("====================\n");

  // --- FastLED Setup ---
  FastLED.addLeds<LED_TYPE, 16, COLOR_ORDER>(leds[0], LEDS_PER_PIN).setCorrection(TypicalPixelString);
  FastLED.addLeds<LED_TYPE, 17, COLOR_ORDER>(leds[1], LEDS_PER_PIN).setCorrection(TypicalPixelString);
  FastLED.addLeds<LED_TYPE, 18, COLOR_ORDER>(leds[2], LEDS_PER_PIN).setCorrection(TypicalPixelString);
  FastLED.addLeds<LED_TYPE, 21, COLOR_ORDER>(leds[3], LEDS_PER_PIN).setCorrection(TypicalPixelString);
  FastLED.addLeds<LED_TYPE, 22, COLOR_ORDER>(leds[4], LEDS_PER_PIN).setCorrection(TypicalPixelString);
  FastLED.setBrightness(BRIGHTNESS);

}

void loop() {
// Keep OTA responsive for 10 seconds
for(int otaLoop=0; otaLoop<2000; otaLoop++) {
  ArduinoOTA.handle();
  FastLED.delay(10);
}
  // Handle client connections
  if (!client || !client.connected()) {
    // Clean up disconnected client
    if (client) {
      client.stop();
    }

  // Accept new client
    WiFiClient newClient = server.accept();
    if (newClient && newClient.connected()) {
      client = newClient;
      client.setNoDelay(true);
      Serial.println("TCP Client connected");
      logToClient("=== Connected to ESP32 ===\n");
      logToClient("IP: ");
      logToClient(WiFi.localIP().toString() + "\n");
    }
  }

  // Non-blocking periodic updates
  if (millis() - lastPrint >= printInterval) {
    lastPrint = millis();

  Serial.println("Starting Effects Test Sequence\n");
  logToClient("Starting Effects Test Sequence\n"); 
  }
  FastLED.clear();

rainbowAnimation(300);
effect_loadbar(&client,100, CRGB::DarkViolet);
wrap_scroll (WRAP_STRIPES_DIAG, CRGB::Cyan,   CRGB::DarkBlue, 40, 60);
wrap_scroll (WRAP_DIAMOND, CRGB::Orange, CRGB::Black,40, 60, -1);
wrap_scroll_rainbow(WRAP_CHEVRON, 40, 60);
wrap_scroll_repeat (WRAP_STRIPES_DIAG, CRGB::Red, CRGB::White, 40, 60, 3);
effect_standing_wave_thick(50, 8, CRGB::Red);
effect_standing_wave_thin(50, 160, CRGB::Blue);
//  Serial.println("Effect LineDrop\n");
//  logToClient("LineDrop\n"); 
effect_plane_linedrop();
//  effect_plane_linedrop_fast();
//planeTestVariable(6);
//planeTest(6);
//logToClient("starfawkes\n"); 
//Serial.println("Effect Starfawkes\n");
effect_starfawkes(100, 150);
effect_random_sparkle_flash(2, 400, 2);
effect_random_filler(1,1);
effect_random_filler(1,0);
//Serial.println("Effect 3D Color Orb\n");
//effect_fireball(5); //Note: Just use effect_pulse with gradient 10
effect_pulse(4, 33); 
effect_pulse(4, 34); //magenta
//logToClient("rainbowBox\n");
rainbowBox(0,255,200,200);
//  logToClient("effect_rain\n");
effect_rain(128);
blinkAll(300);
crossBlink(500);
// fireMap(80, 250);
// logToClient("Randsuspend\n");
effect_suspension(30,300);
effect_box_woopwoop (8, 50, 1);
effect_box_Woop_2 (50);
//  logToClient("gradient playlist\n");
effect_color_grad_playlist(20, 200, 5);
// squareSpiral2(200,900,20);
//  logToClient("telcStairs\n");
//  Serial.println("telcStairs\n");
effect_telcstairs (0, 30, 160, 255, 255);
effect_telcstairs (0, 30, 0, 0, 0);
effect_telcstairs (1, 30, 60, 255, 255);
effect_telcstairs (1, 30, 0, 0, 0);
effect_diagonal_fill(10);
effect_stairsteps(30,8);
effect_pulsing_sphere(100, 6, CRGB::Purple);
effect_rotating_rainbow_sphere(10, 10);
effect_sine_wave_filled(1000, CRGB::BlueViolet);
effect_sine_wave_animated(100, 12, CRGB::BlueViolet);
effect_sine_wave_rainbow(100, 12);
effect_sine_wave_gradient(100, 12);
effect_sine_wave_layers(100, 12);
effect_traveling_wave_rainbow(100, 6);
effect_traveling_wave_gradient(100, 6);
effect_gradient_march(500);
effect_pyramid(120, 160);
//    effect_vorbox(100, 10);
sendplane_rand_y(0,5,10);
sendplane_rand_y(9,5,10);
effect_pulse_from_center(100,10);
effect_tornado(5, 4, CRGB::Green);
effect_tornado_rainbow(5, 4);
effect_tornado_double_helix(5, 4);
effect_tornado_thick(5, 4);

effect_FlyingText();
bitmap_spin_repeat (BITMAP_DIAMOND, 32, 40, CRGB::Cyan, 3, AXIS_Y);
bitmap_spin_repeat (BITMAP_SMILEY,  32, 40, CRGB::Yellow, 3, AXIS_Y);
bitmap_spin_rainbow_repeat(BITMAP_HEART, 32, 40, AXIS_Y, 6, 0);
bitmap_spin_repeat (BITMAP_ARROW,   32, 40, CRGB::Orange, 3, AXIS_Y);
bitmap_spin_repeat (BITMAP_STAR,    32, 40, CRGB::Orange, 3, AXIS_Z);

//effect_BitmapSpin_Loop();
//effect_BitmapSpin_Rainbow();

/*  
  logToClient("squareSpiral2\n");
//  logToClient("blinkAll\n");
//  effect_3D_color_orbR (255, 34);
//  Serial.println("Effect Random Filler\n");
  Serial.println("3D Color Orb x 33\n");
//  for(int k=0; k<34;k++)
//    effect_3D_color_orb(255,k);
  // effect_diagonal_fill(10);
  effect_stairsteps(10,6);
  //effect_sine_wave_pulse(10, 12, CRGB::BlueViolet);
  effect_standing_wave_thick(100, 8, CRGB::Red);
  //effect_tornado_reverse(10, 6, CRGB::Blue);
  */
//effect_vorbox(100, 16);
}