# Ali Alharbi — Webots Soccer: Robot 1 (procedural, no external libs, no custom functions)
# Runs for Yellow or Blue team by copying into robot1.py
# Sensors allowed: IR ball sensor (as Receiver), compass, sonar (distance sensors), GPS
# Behavior: search → chase ball → push toward opponent goal → avoid obstacles
# Notes: Code is defensive: it tries multiple common device names to work with different worlds/robots.

from controller import Robot

TIME_STEP = 32
MAX_SPEED = 6.28  # typical for e-puck style wheels; adjust if needed

robot = Robot()

# --------- Device getters (defensive: try multiple common names) ---------
# Motors
left_motor = None
right_motor = None
motor_names = [
    ("left wheel motor", "right wheel motor"),
    ("left motor", "right motor"),
    ("left wheel", "right wheel"),
    ("left", "right"),
]
for ln, rn in motor_names:
    try:
        lm = robot.getDevice(ln)
        rm = robot.getDevice(rn)
        if lm and rm:
            left_motor, right_motor = lm, rm
            break
    except:
        pass

if left_motor is None or right_motor is None:
    # Last fallback: scan device list for two motors
    for name in ["motor1", "motor2", "motor_left", "motor_right"]:
        try:
            m = robot.getDevice(name)
            if m and left_motor is None:
                left_motor = m
            elif m and right_motor is None:
                right_motor = m
        except:
            pass

# Configure motors for velocity control
if left_motor and right_motor:
    for m in (left_motor, right_motor):
        m.setPosition(float('inf'))
        m.setVelocity(0.0)

# Distance sensors (sonar / proximity). Try common names.
sonars = []
for name in ["ps0","ps1","ps2","ps3","ps4","ps5","ps6","ps7",
             "so0","so1","so2","so3","so4","so5","so6","so7",
             "front_sensor","left_sensor","right_sensor"]:
    try:
        ds = robot.getDevice(name)
        if ds:
            ds.enable(TIME_STEP)
            sonars.append(ds)
    except:
        pass

# Compass
compass = None
try:
    compass = robot.getDevice("compass")
    if compass:
        compass.enable(TIME_STEP)
except:
    compass = None

# GPS
gps = None
try:
    gps = robot.getDevice("gps")
    if gps:
        gps.enable(TIME_STEP)
except:
    gps = None

# IR / ball receiver (directional)
ball_rx = None
for name in ["ball_receiver","ir","ball_sensor","receiver","ball_rx"]:
    try:
        rcv = robot.getDevice(name)
        if rcv:
            rcv.enable(TIME_STEP)
            ball_rx = rcv
            break
    except:
        pass

# Team orientation guess using GPS (if available)
goal_dir = 1  # +X by default
start_x = 0.0
if gps:
    # read a few steps to get stable GPS
    for _ in range(5):
        robot.step(TIME_STEP)
    pos = gps.getValues() if gps else [0,0,0]
    start_x = pos[0]
    # If we start on right side (x>0), target goal to the -X direction
    goal_dir = -1 if start_x > 0 else 1

# Utility-like inline code (no functions) for small helpers
# target heading vector for goal in world coords
goal_vec = [goal_dir, 0.0]  # (x, z) flattened

# Basic state
state = "SEARCH"  # SEARCH, CHASE, PUSH
no_ball_steps = 0
BALL_LOST_LIMIT = 60  # steps ~ 2s
# thresholds
OBSTACLE_NEAR = 0.05  # for normalized sonar values (adjust per robot)
TURN_SPEED = 0.3 * MAX_SPEED
FWD_SPEED = 0.6 * MAX_SPEED
DRIBBLE_SPEED = 0.4 * MAX_SPEED

# Main control loop
while robot.step(TIME_STEP) != -1:
    left_speed = 0.0
    right_speed = 0.0

    # -------- Read sensors --------
    # Sonar obstacle detection (use max of front-ish sensors if available)
    front_val = 0.0
    if sonars:
        # take the maximum reading among first half as "front-ish"
        for i, ds in enumerate(sonars):
            v = ds.getValue()
            # normalize if possible (Webots DS vary; rough normalization)
            # Avoid division; treat larger raw value as "closer"
            if v > front_val:
                front_val = v
    # Convert front_val to boolean "too close" threshold by heuristic
    obstacle_ahead = front_val > 80.0  # works for many webots DS; adjust if needed

    # Ball direction using receiver (two common patterns):
    # (A) Direction vector (3 floats) accessible via getEmitterDirection()
    # (B) Packet with (x,z) or something; we ignore data and only use direction if available
    ball_dir = None  # unit vector in robot frame: (x,z) approx: x right, z forward
    ball_bearing = None  # angle: negative left / positive right
    if ball_rx:
        # Clear previous packets to keep latest direction
        while ball_rx.getQueueLength() > 0:
            data = ball_rx.getData()  # bytes; often not needed
            ball_rx.nextPacket()
        try:
            # Some receivers (like DirectionalAntenna) provide direction in robot frame
            d = ball_rx.getEmitterDirection()
            if d:
                # Webots gives 3D vector; y is up. We use x,z
                bx = d[0]
                bz = d[2]
                ball_dir = (bx, bz)
                # bearing: atan2(x, z); but avoid importing math; approximate with sign
                # We'll use a simple proportional steering: steer = bx
                ball_bearing = bx  # -1 left ... +1 right
                no_ball_steps = 0
            else:
                no_ball_steps += 1
        except:
            no_ball_steps += 1
    else:
        no_ball_steps += 1

    ball_visible = (no_ball_steps == 0)

    # Decide state
    if ball_visible:
        # If ball roughly in front, PUSH; else CHASE
        if ball_dir and ball_dir[1] > 0.5:
            state = "PUSH"
        else:
            state = "CHASE"
    else:
        if no_ball_steps > BALL_LOST_LIMIT:
            state = "SEARCH"

    # -------- Behavior control --------

    if state == "SEARCH":
        # spin slowly to find ball, avoid obstacles
        left_speed = TURN_SPEED
        right_speed = -TURN_SPEED
        if obstacle_ahead:
            # back off a bit
            left_speed = -TURN_SPEED
            right_speed = -TURN_SPEED * 0.7

    elif state == "CHASE":
        # steer toward ball using bearing (bx). Move forward with differential
        steer = 0.0
        if ball_bearing is not None:
            steer = max(-1.0, min(1.0, ball_bearing))
        base = FWD_SPEED
        left_speed  = base * (1.0 - 0.7 * steer)
        right_speed = base * (1.0 + 0.7 * steer)
        if obstacle_ahead:
            # obstacle avoidance: turn away
            left_speed  = -TURN_SPEED * 0.7
            right_speed =  TURN_SPEED

    elif state == "PUSH":
        # Try to push the ball toward opponent goal:
        # Strategy: go mostly forward; bias direction by compass towards goal direction.
        steer_to_goal = 0.0
        if compass:
            # Compass returns a 3D vector; North-based. We want heading vector (x,z).
            c = compass.getValues()
            # Robot facing vector in world coords (approx): (-c[0], -c[2]) per Webots docs
            hx = -c[0]
            hz = -c[2]
            # Goal vector is goal_vec (x,z). We need the lateral error.
            # Approximate cross product z-component to get left/right sign:
            cross = hx * goal_vec[1] - hz * goal_vec[0]  # since goal_vec[1]=0 => -hz*goal_x
            steer_to_goal = -hz * goal_vec[0]  # simplifies to sign based on facing vs goal x
            # clamp
            if steer_to_goal > 1: steer_to_goal = 1
            if steer_to_goal < -1: steer_to_goal = -1
        base = DRIBBLE_SPEED
        left_speed  = base * (1.0 - 0.4 * steer_to_goal)
        right_speed = base * (1.0 + 0.4 * steer_to_goal)
        if obstacle_ahead:
            # sidestep
            left_speed  =  TURN_SPEED
            right_speed = -TURN_SPEED

    # Safety: if motors not found, continue but avoid crash
    if left_motor and right_motor:
        left_motor.setVelocity(left_speed)
        right_motor.setVelocity(right_speed)
    # else: no motors found; just idle (controller still runs)

