#include <ESP8266WiFi.h>
#include <espnow.h>
#include <EEPROM.h>
#include <ESP8266WebServer.h>
#include <WebSocketsServer.h>

// ================= MOTOR PINS (MX1508) =================
#define IN1  D5
#define IN2  D6
#define IN3  D7
#define IN4  D8

// ================= STATUS LED =================
#define STATUS_LED LED_BUILTIN

// ================= DEVICE CONFIG =================
#define DEVICE_ID "C2"        // CHANGE FOR EACH CAR
#define EEPROM_SIZE 100

ESP8266WebServer server(80);
WebSocketsServer webSocket(81);

// ================= STATUS VARIABLES =================
bool pageAccessed = false;
unsigned long lastBlink = 0;
bool ledState = false;

// ================= STRUCTURES =================
typedef struct {
  char targetID[4];
  char ssid[32];
  char password[32];
} config_message;

typedef struct {
  char deviceID[4];
  bool success;
} ack_message;

config_message receivedData;
config_message storedConfig;
ack_message ackData;

// 🔥 PUT MASTER MAC HERE
uint8_t masterMAC[] = {0xC4,0xD8,0xD5,0x12,0x9A,0x51};

// =======================================================
// ================= MOTOR CONTROL =======================
// =======================================================

void stopCar() {
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
}

void forward() {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
}

void backward() {
  digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
}

void left() {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);  digitalWrite(IN4, LOW);
}

void right() {
  digitalWrite(IN1, LOW);  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
}

// =======================================================
// ================= EEPROM FUNCTIONS ====================
// =======================================================

void saveConfig(config_message data) {
  EEPROM.begin(EEPROM_SIZE);
  EEPROM.put(0, data);
  EEPROM.commit();
  EEPROM.end();
}

void loadConfig(config_message &data) {
  EEPROM.begin(EEPROM_SIZE);
  EEPROM.get(0, data);
  EEPROM.end();

  // Validate EEPROM (prevent garbage)
  if (data.ssid[0] == '\0' || data.ssid[0] == 0xFF) {
    memset(&data, 0, sizeof(data));
  }
}

// =======================================================
// ================= ACCESS POINT ========================
// =======================================================

void startAP(const char* ssid, const char* password) {

  WiFi.softAP(ssid, password);

  Serial.println("===== ACCESS POINT STARTED =====");
  Serial.print("SSID: ");
  Serial.println(ssid);
  Serial.print("IP: ");
  Serial.println(WiFi.softAPIP());
  Serial.println("================================");
}

// =======================================================
// ================= WEB MANIFEST ========================
// =======================================================

void handleManifest() {
  server.send(200, "application/json", R"rawliteral(
{
  "name": "RC Car",
  "short_name": "RC Car",
  "display": "standalone",
  "start_url": "/",
  "background_color": "#000000",
  "theme_color": "#000000",
  "orientation": "landscape"
}
)rawliteral");
}

// =======================================================
// ================= WEB PAGE ============================
// =======================================================

void handleRoot() {

  pageAccessed = true;
  digitalWrite(STATUS_LED, LOW);

  server.send(200, "text/html", R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>RC CAR</title>

<style>
html, body {
  margin: 0;
  height: 100%;
  background: #111;
  display: flex;
  justify-content: center;
  align-items: center;
  touch-action: none;
  user-select: none;
}

#joystick-base {
  width: 220px;
  height: 220px;
  background: #333;
  border-radius: 50%;
  position: relative;
}

#joystick-knob {
  width: 90px;
  height: 90px;
  background: #2196F3;
  border-radius: 50%;
  position: absolute;
  top: 65px;
  left: 65px;
}
</style>
</head>

<body>

<div id="joystick-base">
  <div id="joystick-knob"></div>
</div>

<script>
let ws = new WebSocket("ws://" + location.hostname + ":81/");
let base = document.getElementById("joystick-base");
let knob = document.getElementById("joystick-knob");

let centerX = base.offsetWidth / 2;
let centerY = base.offsetHeight / 2;
let maxDist = 70;
let activeCmd = "S";

function send(cmd) {
  if (ws.readyState === WebSocket.OPEN && cmd !== activeCmd) {
    ws.send(cmd);
    activeCmd = cmd;
  }
}

function moveJoystick(x, y) {
  let dx = x - centerX;
  let dy = y - centerY;

  let dist = Math.sqrt(dx*dx + dy*dy);
  if (dist > maxDist) {
    dx = dx * maxDist / dist;
    dy = dy * maxDist / dist;
  }

  knob.style.left = (centerX + dx - 45) + "px";
  knob.style.top  = (centerY + dy - 45) + "px";

  if (Math.abs(dx) < 20 && Math.abs(dy) < 20) {
    send("S");
  }
  else if (Math.abs(dx) > Math.abs(dy)) {
    send(dx > 0 ? "R" : "L");
  }
  else {
    send(dy > 0 ? "B" : "F");
  }
}

function resetJoystick() {
  knob.style.left = "65px";
  knob.style.top  = "65px";
  send("S");
}

base.addEventListener("pointerdown", e => {
  base.setPointerCapture(e.pointerId);
  moveJoystick(e.offsetX, e.offsetY);
});

base.addEventListener("pointermove", e => {
  if (e.pressure > 0) {
    moveJoystick(e.offsetX, e.offsetY);
  }
});

base.addEventListener("pointerup", resetJoystick);
base.addEventListener("pointercancel", resetJoystick);
</script>

</body>
</html>
)rawliteral");
}

// =======================================================
// ================= WEBSOCKET ===========================
// =======================================================

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {

  if (type == WStype_TEXT && length > 0) {

    char cmd = payload[0];

    if      (cmd == 'F') forward();
    else if (cmd == 'B') backward();
    else if (cmd == 'L') left();
    else if (cmd == 'R') right();
    else stopCar();
  }
}

// =======================================================
// ================= ESP-NOW RECEIVE =====================
// =======================================================

void onDataRecv(uint8_t *mac, uint8_t *incomingData, uint8_t len) {

  memcpy(&receivedData, incomingData, sizeof(receivedData));

  if (strcmp(receivedData.targetID, DEVICE_ID) == 0 ||
      strcmp(receivedData.targetID, "ALL") == 0) {

    strcpy(ackData.deviceID, DEVICE_ID);
    ackData.success = true;

    esp_now_send(masterMAC, (uint8_t*)&ackData, sizeof(ackData));

    saveConfig(receivedData);

    delay(1000);
    ESP.restart();
  }
}

// =======================================================
// ================= SETUP ===============================
// =======================================================

void setup() {

  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  stopCar();

  pinMode(STATUS_LED, OUTPUT);
  digitalWrite(STATUS_LED, HIGH);

  Serial.begin(115200);
  delay(1000);

  WiFi.mode(WIFI_AP_STA);

  server.on("/", handleRoot);
  server.on("/manifest.json", handleManifest);
  server.begin();

  webSocket.begin();
  webSocket.onEvent(webSocketEvent);

  Serial.println("========== DEVICE INFO ==========");
  Serial.print("Device ID: ");
  Serial.println(DEVICE_ID);
  Serial.print("Station MAC: ");
  Serial.println(WiFi.macAddress());
  Serial.println("=================================");

  loadConfig(storedConfig);

  if (strlen(storedConfig.ssid) > 0) {
    startAP(storedConfig.ssid, storedConfig.password);
  } else {
    startAP("CAR_" DEVICE_ID, "12345678");
  }

  if (esp_now_init() != 0) {
    Serial.println("ESP-NOW Init Failed");
    return;
  }

  esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
  esp_now_register_recv_cb(onDataRecv);
  esp_now_add_peer(masterMAC, ESP_NOW_ROLE_CONTROLLER, 1, NULL, 0);

  Serial.println("Receiver Ready.");
}

// =======================================================
// ================= LOOP ================================
// =======================================================

void loop() {

  // Blink LED until page opened
  if (!pageAccessed && millis() - lastBlink >= 500) {
    lastBlink = millis();
    ledState = !ledState;
    digitalWrite(STATUS_LED, ledState ? LOW : HIGH);
  }

  // Safety stop if disconnected
  if (webSocket.connectedClients() == 0) {
    stopCar();
  }

  server.handleClient();
  webSocket.loop();
}