#include <Servo.h>

// Servo Definitions
Servo baseServo, shoulderServo, elbowServo, gripperServo;

// Pin Assignments
const int BASE_SERVO_PIN = 13, SHOULDER_SERVO_PIN = 7, ELBOW_SERVO_PIN = 8, GRIPPER_SERVO_PIN = 11;
const int MOTOR1_IN1_PIN = 3, MOTOR1_IN2_PIN = 4, MOTOR2_IN3_PIN = 5, MOTOR2_IN4_PIN = 6;
const int MOTOR1_EN_PIN = 9, MOTOR2_EN_PIN = 10;
const int TRIG_PIN = 12, ECHO_PIN = 2;

// Servo Movement Parameters
const int BASE_MIN_ANGLE = 0, BASE_MAX_ANGLE = 180;
const int SHOULDER_MIN_ANGLE = 0, SHOULDER_MAX_ANGLE = 180;
const int ELBOW_MIN_ANGLE = 0, ELBOW_MAX_ANGLE = 180;
const int GRIPPER_OPEN_ANGLE = 0, GRIPPER_CLOSE_ANGLE = 90;

// Home Positions
const int BASE_HOME_ANGLE = (BASE_MIN_ANGLE + BASE_MAX_ANGLE) / 2;
const int SHOULDER_HOME_ANGLE = (SHOULDER_MIN_ANGLE + SHOULDER_MAX_ANGLE) / 2;
const int ELBOW_HOME_ANGLE = (ELBOW_MIN_ANGLE + ELBOW_MAX_ANGLE) / 2;
const int GRIPPER_HOME_ANGLE = GRIPPER_OPEN_ANGLE;

// Timing Parameters
const int DELAY_MS = 50, GRIPPER_DELAY_MS = 200;
const int MOVE_DURATION = 2000, TURN_DELAY = 1000;

// Movement Parameters
const int MOTOR_SPEED = 200;
const int OBSTACLE_DISTANCE = 15;

// Obstacle detection parameters
const unsigned long OBSTACLE_CHECK_INTERVAL = 100; // Check every 100ms
unsigned long lastObstacleCheckTime = 0;
bool obstacleDetected = false;

void setup() {
  initializeServos();
  initializeMotors();
  initializeUltrasonicSensor();
  initializeSerialAndRandom();
}

void loop() {
  unsigned long currentTime = millis();
  
  // Check for obstacles periodically
  if (currentTime - lastObstacleCheckTime >= OBSTACLE_CHECK_INTERVAL) {
    checkForObstacles();
    lastObstacleCheckTime = currentTime;
  }

  if (obstacleDetected) {
    handleObstacle();
  } else {
    testArmMovements();
    testBaseMovements();
  }
}

// Initialization Functions
void initializeServos() {
  baseServo.attach(BASE_SERVO_PIN);
  shoulderServo.attach(SHOULDER_SERVO_PIN);
  elbowServo.attach(ELBOW_SERVO_PIN);
  gripperServo.attach(GRIPPER_SERVO_PIN);
}

void initializeMotors() {
  pinMode(MOTOR1_IN1_PIN, OUTPUT);
  pinMode(MOTOR1_IN2_PIN, OUTPUT);
  pinMode(MOTOR2_IN3_PIN, OUTPUT);
  pinMode(MOTOR2_IN4_PIN, OUTPUT);
  pinMode(MOTOR1_EN_PIN, OUTPUT);
  pinMode(MOTOR2_EN_PIN, OUTPUT);
}

void initializeUltrasonicSensor() {
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
}

void initializeSerialAndRandom() {
  Serial.begin(9600);
  Serial.println("Starting Combined Robot Arm and Base Test with Periodic Obstacle Avoidance...");
  randomSeed(analogRead(0));
}

// Obstacle Detection and Handling
void checkForObstacles() {
  float distance = readUltrasonic();
  obstacleDetected = (distance < OBSTACLE_DISTANCE);
}

void handleObstacle() {
  Serial.println("Obstacle detected! Stopping and turning...");
  stopMotors();
  delay(TURN_DELAY);
  turnRight();
  delay(TURN_DELAY);
  stopMotors();
}

// Arm Movement Functions
void testArmMovements() {
  Serial.println("Testing Arm Movements...");
  testIndividualServos();
  delay(3000);
  backToHome();
  performRandomArmMovements();
  delay(1000);
}

void testIndividualServos() {
  Serial.println("Initial Testing of Each Servo One by One...");
  testServo("Base", baseServo, BASE_MIN_ANGLE, BASE_MAX_ANGLE);
  testServo("Shoulder", shoulderServo, SHOULDER_MIN_ANGLE, SHOULDER_MAX_ANGLE);
  testServo("Elbow", elbowServo, ELBOW_MIN_ANGLE, ELBOW_MAX_ANGLE);
  testGripperServo();
}

void testServo(const char* servoName, Servo& servo, int minAngle, int maxAngle) {
  Serial.print("Testing ");
  Serial.print(servoName);
  Serial.println(" Servo...");
  moveServo(servo, minAngle, maxAngle);
}

void moveServo(Servo& servo, int minAngle, int maxAngle) {
  for (int pos = minAngle; pos <= maxAngle; pos += 10) {
    servo.write(pos);
    delay(DELAY_MS);
  }
  for (int pos = maxAngle; pos >= minAngle; pos -= 10) {
    servo.write(pos);
    delay(DELAY_MS);
  }
}

void testGripperServo() {
  Serial.println("Testing Gripper Servo...");
  gripperServo.write(GRIPPER_OPEN_ANGLE);
  delay(GRIPPER_DELAY_MS);
  gripperServo.write(GRIPPER_CLOSE_ANGLE);
  delay(GRIPPER_DELAY_MS);
}

void performRandomArmMovements() {
  Serial.println("Starting Random and Combined Arm Movements...");
  for (int i = 0; i < 5; i++) {
    performRandomMove();
    delay(500);
  }
  performComplexMovements();
  backToHome();
}

void performRandomMove() {
  int randomTest = random(1, 5);
  switch (randomTest) {
    case 1: randomMoveServo("Base", baseServo, BASE_MIN_ANGLE, BASE_MAX_ANGLE); break;
    case 2: randomMoveServo("Shoulder", shoulderServo, SHOULDER_MIN_ANGLE, SHOULDER_MAX_ANGLE); break;
    case 3: randomMoveServo("Elbow", elbowServo, ELBOW_MIN_ANGLE, ELBOW_MAX_ANGLE); break;
    case 4: randomGripper(); break;
  }
}

void randomMoveServo(const char* servoName, Servo& servo, int minAngle, int maxAngle) {
  Serial.print("Random ");
  Serial.print(servoName);
  Serial.println(" Servo Movement...");
  int randomAngle = random(minAngle, maxAngle);
  servo.write(randomAngle);
  delay(random(100, 500));
}

void randomGripper() {
  Serial.println("Random Gripper Movement...");
  gripperServo.write(random(0, 2) == 0 ? GRIPPER_OPEN_ANGLE : GRIPPER_CLOSE_ANGLE);
  delay(GRIPPER_DELAY_MS);
}

void performComplexMovements() {
  Serial.println("Performing Complex Movement Sequences...");
  complexMovementSequence1();
  complexMovementSequence2();
  complexMovementSequence3();
  complexMovementSequence4();
}

void complexMovementSequence1() {
  Serial.println("Complex Movement 1: Rotate and Reach");
  baseServo.write(90);
  shoulderServo.write(45);
  delay(500);
  elbowServo.write(90);
  delay(500);
  gripperServo.write(GRIPPER_CLOSE_ANGLE);
  delay(GRIPPER_DELAY_MS);
  backToHome();
}

void complexMovementSequence2() {
  Serial.println("Complex Movement 2: Pick and Drop");
  baseServo.write(45);
  shoulderServo.write(60);
  delay(400);
  elbowServo.write(120);
  delay(400);
  gripperServo.write(GRIPPER_CLOSE_ANGLE);
  delay(GRIPPER_DELAY_MS);
  elbowServo.write(60);
  baseServo.write(BASE_HOME_ANGLE);
  delay(400);
  gripperServo.write(GRIPPER_OPEN_ANGLE);
  delay(GRIPPER_DELAY_MS);
  backToHome();
}

void complexMovementSequence3() {
  Serial.println("Complex Movement 3: Scan Left to Right");
  for (int angle = BASE_MIN_ANGLE; angle <= BASE_MAX_ANGLE; angle += 20) {
    baseServo.write(angle);
    delay(300);
  }
  for (int angle = BASE_MAX_ANGLE; angle >= BASE_MIN_ANGLE; angle -= 20) {
    baseServo.write(angle);
    delay(300);
  }
  backToHome();
}

void complexMovementSequence4() {
  Serial.println("Complex Movement 4: Wave Motion");
  shoulderServo.write(90);
  elbowServo.write(90);
  for (int i = 0; i < 3; i++) {
    shoulderServo.write(70);
    delay(300);
    shoulderServo.write(110);
    delay(300);
  }
  backToHome();
}

void backToHome() {
  Serial.println("Returning to Home Position...");
  baseServo.write(BASE_HOME_ANGLE);
  shoulderServo.write(SHOULDER_HOME_ANGLE);
  elbowServo.write(ELBOW_HOME_ANGLE);
  gripperServo.write(GRIPPER_HOME_ANGLE);
  delay(500);
}

// Base Movement Functions
void testBaseMovements() {
  Serial.println("Testing Base Movements with Obstacle Avoidance...");
  float distance = readUltrasonic();
  Serial.print("Distance: ");
  Serial.print(distance);
  Serial.println(" cm");

  if (distance > OBSTACLE_DISTANCE) {
    performBaseMovements();
  } else {
    handleObstacle();
  }
}

void performBaseMovements() {
  moveAndPause("Moving Forward...", moveForward);
  moveAndPause("Moving Backward...", moveBackward);
  moveAndPause("Turning Left...", turnLeft);
  moveAndPause("Turning Right...", turnRight);
  Serial.println("Stopping Motors...");
  stopMotors();
  delay(2000);
}

void moveAndPause(const char* message, void (*moveFunction)()) {
  Serial.println(message);
  moveFunction();
  delay(MOVE_DURATION);
}

// Ultrasonic Sensor Function
float readUltrasonic() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  long duration = pulseIn(ECHO_PIN, HIGH);
  return (duration * 0.034) / 2;
}

// Motor Control Functions
void moveForward() {
  setMotorDirection(HIGH, LOW, HIGH, LOW);
}

void moveBackward() {
  setMotorDirection(LOW, HIGH, LOW, HIGH);
}

void turnLeft() {
  setMotorDirection(LOW, HIGH, HIGH, LOW);
}

void turnRight() {
  setMotorDirection(HIGH, LOW, LOW, HIGH);
}

void stopMotors() {
  analogWrite(MOTOR1_EN_PIN, 0);
  analogWrite(MOTOR2_EN_PIN, 0);
  setMotorDirection(LOW, LOW, LOW, LOW);
}

void setMotorDirection(int in1, int in2, int in3, int in4) {
  digitalWrite(MOTOR1_IN1_PIN, in1);
  digitalWrite(MOTOR1_IN2_PIN, in2);
  digitalWrite(MOTOR2_IN3_PIN, in3);
  digitalWrite(MOTOR2_IN4_PIN, in4);
  analogWrite(MOTOR1_EN_PIN, MOTOR_SPEED);
  analogWrite(MOTOR2_EN_PIN, MOTOR_SPEED);
}
L298N Breakout