#include <ESP32Servo.h>
#include <cmath> // Include the standard C++ math library

// Define the number of rows and columns in the grid
const int ROWS = 3;
const int COLS = 3;

// Define the servo pins
const int servoPins[ROWS][COLS] = {
  {2, 4, 16},
  {17, 5, 18},
  {33, 32, 25} // Corrected pin 35 to 25, as 35 is input only
};

// Create servo objects
Servo servos[ROWS][COLS];

// Define the minimum and maximum angles for the servos
const int MIN_ANGLE = 30;
const int MAX_ANGLE = 150;

// Define the base delay between each servo movement
const int MIN_SERVO_DELAY = 5;
const int MAX_SERVO_DELAY = 50;

// Define the wave delay between rows
const int MIN_WAVE_DELAY = 50;
const int MAX_WAVE_DELAY = 500;

// Define the delay between columns
const int COLUMN_DELAY = 20;

// Define the potentiometer pins for speed and intensity control
const int SPEED_POT_PIN = 26;
const int INTENSITY_POT_PIN = 27;

// Define an array to store the current angle of each servo
int servoAngles[ROWS][COLS];

// Variables to store potentiometer values
int speedPotValue;
int intensityPotValue;

// Structure to hold servo movement data
typedef struct {
  int row;
  int col;
  float phase;
  unsigned long startTime;
} ServoMovement;

// Array to track active servo movements
ServoMovement activeMovements[ROWS * COLS];
int activeMovementCount = 0;

void setup() {
  Serial.begin(115200);
  // Allow allocation of all timers
  ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);
  // Attach each servo to its corresponding pin
  for (int i = 0; i < ROWS; i++) {
    for (int j = 0; j < COLS; j++) {
      servos[i][j].setPeriodHertz(50);
      servos[i][j].attach(servoPins[i][j], 500, 2400);
      servoAngles[i][j] = MIN_ANGLE; // Start at the minimum angle
      servos[i][j].write(servoAngles[i][j]);
    }
  }
}

void loop() {
  // Read potentiometer values
  speedPotValue = analogRead(SPEED_POT_PIN);
  intensityPotValue = analogRead(INTENSITY_POT_PIN);

  // Map potentiometer values to servo delay and wave delay
  int servoDelay = map(speedPotValue, 0, 4095, MAX_SERVO_DELAY, MIN_SERVO_DELAY); // Reverse mapping for speed
  int waveDelay = map(intensityPotValue, 0, 4095, MIN_WAVE_DELAY, MAX_WAVE_DELAY);

  // Initiate new movements
  initiateMovements(waveDelay);

  // Update active movements
  updateMovements(servoDelay);

}

void initiateMovements(int waveDelay) {
  static unsigned long lastRowMovementTime[ROWS] = {0};
  
  unsigned long currentTime = millis();

  for (int i = 0; i < ROWS; i++) {
    if (currentTime - lastRowMovementTime[i] >= waveDelay) {
      for (int j = 0; j < COLS; j++) {
        
          // Check if a movement for this servo is already active
          bool movementAlreadyActive = false;
          for (int k = 0; k < activeMovementCount; k++) {
            if (activeMovements[k].row == i && activeMovements[k].col == j) {
              movementAlreadyActive = true;
              break;
            }
          }

          // If no movement is active for this servo, add a new one
          if (!movementAlreadyActive) {
            // Add the new movement to the array
            if (activeMovementCount < ROWS * COLS) {
              activeMovements[activeMovementCount].row = i;
              activeMovements[activeMovementCount].col = j;
              activeMovements[activeMovementCount].phase = 0.0f;
              activeMovements[activeMovementCount].startTime = currentTime;
              activeMovementCount++;
            
          }
        }
      }
      lastRowMovementTime[i] = currentTime; // Update last row movement time
    }
  }
}

void updateMovements(int servoDelay) {
  unsigned long currentTime = millis();

  for (int i = 0; i < activeMovementCount; i++) {
    ServoMovement* movement = &activeMovements[i];

    if (currentTime - movement->startTime >= servoDelay) {
      int angleOffset = map(intensityPotValue, 0, 4095, 0, (MAX_ANGLE - MIN_ANGLE) / 2);

      // Use the standard sin() function from cmath
      int newAngle = MIN_ANGLE + (MAX_ANGLE - MIN_ANGLE) / 2 + angleOffset * sin(movement->phase + movement->row * 0.5 + movement->col * 0.2);

      int angleVariation = random(-3, 4);
      newAngle = constrain(newAngle + angleVariation, MIN_ANGLE, MAX_ANGLE);

      servos[movement->row][movement->col].write(newAngle);
      servoAngles[movement->row][movement->col] = newAngle;

      movement->phase += 0.05;
      movement->startTime = currentTime;

      if (movement->phase >= 2 * PI) {
        // Remove completed movement
        for (int j = i; j < activeMovementCount - 1; j++) {
          activeMovements[j] = activeMovements[j + 1];
        }
        activeMovementCount--;
        i--; // Decrement i to recheck the current index after removal
      }
    }
  }
}
$abcdeabcde151015202530fghijfghij
esp:0
esp:2
esp:4
esp:5
esp:12
esp:13
esp:14
esp:15
esp:16
esp:17
esp:18
esp:19
esp:21
esp:22
esp:23
esp:25
esp:26
esp:27
esp:32
esp:33
esp:34
esp:35
esp:3V3
esp:EN
esp:VP
esp:VN
esp:GND.1
esp:D2
esp:D3
esp:CMD
esp:5V
esp:GND.2
esp:TX
esp:RX
esp:GND.3
esp:D1
esp:D0
esp:CLK
servo1:GND
servo1:V+
servo1:PWM
servo2:GND
servo2:V+
servo2:PWM
servo3:GND
servo3:V+
servo3:PWM
servo4:GND
servo4:V+
servo4:PWM
servo5:GND
servo5:V+
servo5:PWM
servo6:GND
servo6:V+
servo6:PWM
servo7:GND
servo7:V+
servo7:PWM
servo8:GND
servo8:V+
servo8:PWM
servo9:GND
servo9:V+
servo9:PWM
vcc1:VCC
gnd1:GND
gnd2:GND
External Power Supply
pot1:GND
pot1:SIG
pot1:VCC
pot2:GND
pot2:SIG
pot2:VCC
Speed Control
Intensity Control