#include <WiFi.h>
#include <ESPAsyncWebServer.h> // Requires AsyncWebServer and AsyncTCP libraries
#include <WebSocketsServer.h>  // Requires WebSockets library by Markus Sattler
#include <ESP32Servo.h>

// WiFi Credentials
const char* ssid = "309";         // [cite: 4]
const char* password = "12345678"; // [cite: 5]

// Stepper 1 Pins (Table)
#define DIR_PIN 18    // [cite: 6]
#define STEP_PIN 19   // [cite: 6]

// Stepper 2 Pins (Lift)
#define DIR_PIN2 16   // [cite: 6]
#define STEP_PIN2 17  // [cite: 6]
#define EN_PIN 5      // [cite: 6]

// Servos
Servo myservo1;  // [cite: 2]
Servo myservo2;  // [cite: 2]
static const int servoPin1 = 13; // [cite: 3]
static const int servoPin2 = 14; // [cite: 4]

// Motor movement states
int tableSpeedDelay = 0; // 0 means stopped
bool tableDirection = HIGH;
int liftSpeedDelay = 0;  // 0 means stopped
bool liftDirection = HIGH;

// Create WebServer (Port 80) and WebSocket Server (Port 81)
AsyncWebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);

// HTML & JavaScript for the Virtual Control Lever Webpage
const char index_html[] PROGMEM = R"rawhtml(
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ESP32 Control Lever</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; background: #222; color: #fff; margin: 0; padding: 20px; }
        h1 { margin-bottom: 30px; }
        #joystick-container { position: relative; width: 250px; height: 250px; background: #444; border-radius: 50%; margin: 50px auto; touch-action: none; box-shadow: 0 0 20px rgba(0,0,0,0.5); }
        #joystick-knob { position: absolute; width: 70px; height: 70px; background: #00adb5; border-radius: 50%; top: 90px; left: 90px; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.3); }
        .status { color: #aaa; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>IoT Machine Control Lever</h1>
    <div id="joystick-container">
        <div id="joystick-knob"></div>
    </div>
    <div class="status">Table (X): <span id="valX">0</span> | Lift (Y): <span id="valY">0</span></div>

    <script>
        var gateway = `ws://${window.location.hostname}:81/`;
        var websocket = new WebSocket(gateway);
        
        const container = document.getElementById('joystick-container');
        const knob = document.getElementById('joystick-knob');
        const valX = document.getElementById('valX');
        const valY = document.getElementById('valY');
        
        let centerX = container.clientWidth / 2;
        let centerY = container.clientHeight / 2;
        let active = false;

        function handleMove(e) {
            if (!active) return;
            let clientX = e.touches ? e.touches[0].clientX : e.clientX;
            let clientY = e.touches ? e.touches[0].clientY : e.clientY;
            
            let rect = container.getBoundingClientRect();
            let x = clientX - rect.left - centerX;
            let y = clientY - rect.top - centerY;
            
            // Limit knob movement within container radius
            let distance = Math.min(100, Math.sqrt(x*x + y*y));
            let angle = Math.atan2(y, x);
            
            let finalX = distance * Math.cos(angle);
            let finalY = distance * Math.sin(angle);
            
            knob.style.transform = `translate(${finalX}px, ${finalY}px)`;
            
            // Send normalized coordinates (-100 to 100) over WebSocket
            // Invert Y so up is positive
            let sendX = Math.round(finalX);
            let sendY = Math.round(-finalY);
            
            valX.innerText = sendX;
            valY.innerText = sendY;
            
            if (websocket.readyState === WebSocket.OPEN) {
                websocket.send(`${sendX},${sendY}`);
            }
        }

        function handleEnd() {
            if (!active) return;
            active = false;
            knob.style.transform = `translate(0px, 0px)`;
            valX.innerText = 0;
            valY.innerText = 0;
            if (websocket.readyState === WebSocket.OPEN) {
                websocket.send("0,0");
            }
        }

        knob.addEventListener('mousedown', () => active = true);
        window.addEventListener('mousemove', handleMove);
        window.addEventListener('mouseup', handleEnd);
        
        knob.addEventListener('touchstart', () => active = true);
        window.addEventListener('touchmove', handleMove);
        window.addEventListener('touchend', handleEnd);
    </script>
</body>
</html>
)rawhtml";

// Dynamic Speed Conversion Map
int mapSpeed(int intensity) {
  if (intensity == 0) return 0;
  // Further the displacement, smaller the microsecond delay (meaning faster movement)
  return map(intensity, 10, 100, 2000, 300); 
}

// WebSocket Event Handler
void onWebSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
  if (type == WStype_TEXT) {
    String message = String((char*)payload);
    int commaIndex = message.indexOf(',');
    if (commaIndex > 0) {
      int xVal = message.substring(0, commaIndex).toInt();
      int yVal = message.substring(commaIndex + 1).toInt();
      
      // 1. Process X Axis -> Table
      if (abs(xVal) > 10) { // Deadzone protection
        tableDirection = (xVal > 0) ? HIGH : LOW; 
        tableSpeedDelay = mapSpeed(abs(xVal));
      } else {
        tableSpeedDelay = 0; // Stop
      }
      
      // 2. Process Y Axis -> Lift
      if (abs(yVal) > 10) { // Deadzone protection
        liftDirection = (yVal > 0) ? HIGH : LOW;
        liftSpeedDelay = mapSpeed(abs(yVal));
      } else {
        liftSpeedDelay = 0; // Stop
      }
    }
  }
}

void setup() {
  Serial.begin(115200);

  // Initialize Stepper/Driver Hardware Pins
  pinMode(DIR_PIN, OUTPUT);   // [cite: 46]
  pinMode(STEP_PIN, OUTPUT);  // [cite: 47]
  pinMode(DIR_PIN2, OUTPUT);
  pinMode(STEP_PIN2, OUTPUT);
  pinMode(EN_PIN, OUTPUT);    // [cite: 47]
  digitalWrite(EN_PIN, LOW);  // Enable drivers [cite: 47]

  // Attach Servos
  myservo1.attach(servoPin1); // 
  myservo2.attach(servoPin2); // 

  // Setup Wi-Fi
  WiFi.begin(ssid, password); // 
  while (WiFi.status() != WL_CONNECTED) { // 
    delay(500); // 
    Serial.print("."); // 
  }
  Serial.println("\nWiFi Connected!");
  Serial.print("Go to this IP address in your browser: ");
  Serial.println(WiFi.localIP()); // 

  // Serve the Webpage Frontend
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html);
  });

  // Start Server Ecosystem
  server.begin();
  webSocket.begin();
  webSocket.onEvent(onWebSocketEvent);
}

void loop() {
  webSocket.loop();

  // Execute Non-Blocking Step Control for the Table
  if (tableSpeedDelay > 0) {
    digitalWrite(DIR_PIN, tableDirection);
    digitalWrite(STEP_PIN, HIGH);
    delayMicroseconds(10); // Minimum step pulse pulsewidth
    digitalWrite(STEP_PIN, LOW);
    delayMicroseconds(tableSpeedDelay);
  }

  // Execute Non-Blocking Step Control for the Lift
  if (liftSpeedDelay > 0) {
    digitalWrite(DIR_PIN2, liftDirection);
    digitalWrite(STEP_PIN2, HIGH);
    delayMicroseconds(10); 
    digitalWrite(STEP_PIN2, LOW);
    delayMicroseconds(liftSpeedDelay);
  }
}