#include <Wire.h>
#include <EEPROM.h>

#define EEPROM_SIZE        64
#define EEPROM_ADDR_LOC    0
#define EEPROM_NAME_LOC    1
#define DEVICE_NAME_LEN    24
#define DEFAULT_I2C_ADDR   0x08

// Motor pins
#define EN1 0
#define IN1 1
#define IN2 2
#define IN3 4
#define IN4 5
#define EN2 6

char deviceName[DEVICE_NAME_LEN] = "Motor";
uint8_t i2cAddress = DEFAULT_I2C_ADDR;

// I2C command codes
enum Command : uint8_t {
  CMD_SET_ADDR    = 0x01,
  CMD_SET_NAME    = 0x02,
  CMD_MOTOR_CTRL  = 0x10,
  CMD_BOTH_CTRL   = 0x11,
};

// Store address to EEPROM
void storeAddressToEEPROM(uint8_t addr) {
  EEPROM.write(EEPROM_ADDR_LOC, addr);
  EEPROM.commit();
}

// Read address from EEPROM (with fallback to default)
uint8_t readAddressFromEEPROM() {
  uint8_t addr = EEPROM.read(EEPROM_ADDR_LOC);
  if (addr == 0xFF || addr == 0x00) { // Uninitialized or invalid
    addr = DEFAULT_I2C_ADDR;
    storeAddressToEEPROM(addr);
  }
  return addr;
}

void storeNameToEEPROM(const char* name) {
  for (int i = 0; i < DEVICE_NAME_LEN; i++) {
    EEPROM.write(EEPROM_NAME_LOC + i, name[i]);
    if (name[i] == '\0') break;
  }
  EEPROM.commit();
}

void readNameFromEEPROM(char* name) {
  for (int i = 0; i < DEVICE_NAME_LEN; i++) {
    name[i] = EEPROM.read(EEPROM_NAME_LOC + i);
    if (name[i] == '\0') break;
  }
  name[DEVICE_NAME_LEN - 1] = '\0';
}

// Helper: Set motor speed and direction
void setMotor(uint8_t enPin, uint8_t in1, uint8_t in2, uint8_t speed, uint8_t dir) {
  analogWrite(enPin, speed);
  if (dir == 0) {
    digitalWrite(in1, HIGH);
    digitalWrite(in2, LOW);
  } else {
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
  }
}

// Stop motor
void stopMotor(uint8_t enPin, uint8_t in1, uint8_t in2) {
  analogWrite(enPin, 0);
  digitalWrite(in1, LOW);
  digitalWrite(in2, LOW);
}

// I2C receive handler
void onReceive(int len) {
  if (len < 1) return;
  uint8_t cmd = Wire.read();
  switch (cmd) {
    case CMD_SET_ADDR: 
      if (Wire.available()) {
        uint8_t newAddr = Wire.read();
        if (newAddr > 0x00 && newAddr < 0x78) { // Valid 7-bit address
          storeAddressToEEPROM(newAddr);
          ESP.restart(); // Or NVIC_SystemReset() on STM32, or just reset the MCU as needed
        }
      }
      break;
    case CMD_SET_NAME: {
      int i = 0;
      while (Wire.available() && i < DEVICE_NAME_LEN - 1) {
        deviceName[i++] = Wire.read();
      }
      deviceName[i] = '\0';
      storeNameToEEPROM(deviceName);
      break;
    }
    case CMD_MOTOR_CTRL: {
      if (Wire.available() >= 3) {
        uint8_t motor_id = Wire.read(); // 1: MotorA, 2: MotorB
        uint8_t speed_pct = Wire.read(); // 0-100
        uint8_t dir = Wire.read(); // 0: FWD, 1: REV
        uint8_t pwm = map(speed_pct, 0, 100, 0, 255);

        if (motor_id == 1) { // MotorA
          setMotor(EN1, IN1, IN2, pwm, dir);
        } else if (motor_id == 2) { // MotorB
          setMotor(EN2, IN3, IN4, pwm, dir);
        }
      }
      break;
    }
    case CMD_BOTH_CTRL: {
      if (Wire.available() >= 4) {
        uint8_t speedA = Wire.read();
        uint8_t dirA = Wire.read();
        uint8_t speedB = Wire.read();
        uint8_t dirB = Wire.read();
        uint8_t pwmA = map(speedA, 0, 100, 0, 255);
        uint8_t pwmB = map(speedB, 0, 100, 0, 255);

        setMotor(EN1, IN1, IN2, pwmA, dirA);
        setMotor(EN2, IN3, IN4, pwmB, dirB);
      }
      break;
    }
    default: break;
  }
}

// I2C request handler
void onRequest() {
  String response = String(i2cAddress, HEX) + ",GENERIC," + deviceName;
  Wire.write((const uint8_t*)response.c_str(), response.length());
}

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

  i2cAddress = readAddressFromEEPROM();
  readNameFromEEPROM(deviceName);

  // Motor pins setup
  pinMode(EN1, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(EN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);

  Wire.begin(i2cAddress);
  Wire.onReceive(onReceive);
  Wire.onRequest(onRequest);

  Serial.print("I2C Slave started at address 0x");
  Serial.println(i2cAddress, HEX);
  Serial.print("Device name: ");
  Serial.println(deviceName);
}

void loop() {
  // Nothing here
}
