content: package io.vividcode.happyride.driversimulator;
  
  import io.vividcode.happyride.common.DriverState;
  import io.vividcode.happyride.dispatchservice.api.events.DriverLocation;
  import io.vividcode.happyride.dispatchservice.api.events.DriverLocationUpdatedEvent;
  import java.math.BigDecimal;
  import java.time.Duration;
  import java.util.UUID;
  import java.util.concurrent.ThreadLocalRandom;
  import org.axonframework.eventhandling.gateway.EventGateway;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import reactor.core.publisher.EmitterProcessor;
  import reactor.core.publisher.Flux;
  
  public class DriverSimulator {
  
    private final String id;
    private final EventGateway eventGateway;
  
    private final int[][] deltas = new int[][]{
        new int[]{1, 0},
        new int[]{0, -1},
        new int[]{-1, 0},
        new int[]{0, 1}
    };
  
    private int direction = 0;
    private DriverLocation currentLocation;
    private DriverState state = DriverState.OFFLINE;
    private EmitterProcessor<Boolean> stop;
    private final String driverId;
    private final String vehicleId;
  
    private static final Logger LOGGER = LoggerFactory
        .getLogger(DriverSimulator.class);
  
    public DriverSimulator(final String driverId, final String vehicleId,
        final EventGateway eventGateway,
        final DriverLocation initialLocation) {
      this.id = UUID.randomUUID().toString();
      this.driverId = driverId;
      this.vehicleId = vehicleId;
      this.currentLocation = initialLocation;
      this.eventGateway = eventGateway;
    }
  
    public String getId() {
      return this.id;
    }
  
    public String getDriverId() {
      return this.driverId;
    }
  
    public void startSimulation() {
      LOGGER.info("Start simulation for driver [{}] with vehicle [{}]",
          this.driverId,
          this.vehicleId);
      this.state = DriverState.AVAILABLE;
      this.stop = EmitterProcessor.create();
      Flux.interval(Duration.ofSeconds(5))
          .takeUntilOther(this.stop)
          .subscribe((v) -> {
            this.updateLocation();
            this.sendLocation();
          });
    }
  
    public void stopSimulation() {
      LOGGER.info("Stop simulation for driver [{}] with vehicle [{}]",
          this.driverId,
          this.vehicleId);
      this.state = DriverState.OFFLINE;
      this.stop.onNext(true);
      this.stop.onComplete();
      this.stop = null;
    }
  
    public void resetPosition(final BigDecimal lng, final BigDecimal lat) {
      this.currentLocation = this.currentLocation.resetTo(lng, lat);
    }
  
    public void markAsAvailable() {
      this.state = DriverState.AVAILABLE;
    }
  
    public void markAsNotAvailable() {
      this.state = DriverState.NOT_AVAILABLE;
    }
  
    private void sendLocation() {
      final DriverLocationUpdatedEvent event = new DriverLocationUpdatedEvent();
      event.setTimestamp(System.currentTimeMillis());
      event.setLocation(this.currentLocation);
      event.setState(this.state);
      this.eventGateway.publish(event);
    }
  
    private void updateLocation() {
      final ThreadLocalRandom random = ThreadLocalRandom.current();
      final boolean shouldTurn = random.nextInt(3) > 0;
      if (shouldTurn) {
        final boolean turnLeft = random.nextBoolean();
        if (turnLeft) {
          this.direction = (this.direction + 3) % 4;
        } else {
          this.direction = this.direction++ % 4;
        }
      }
      final int speed = random.nextInt(1, 4);
      final double latDelta = this.deltas[this.direction][0] * 0.000001 * speed;
      final double lngDelta = this.deltas[this.direction][1] * 0.000001 * speed;
      this.currentLocation = this.currentLocation
          .moveTo(BigDecimal.valueOf(lngDelta), BigDecimal.valueOf(latDelta));
    }
  
    public DriverSimulatorSnapshot dump() {
      final DriverSimulatorSnapshot snapshot = new DriverSimulatorSnapshot();
      snapshot.setId(this.id);
      snapshot.setDriverId(this.driverId);
      snapshot.setVehicleId(this.vehicleId);
      snapshot.setState(this.state);
      snapshot.setPosLng(this.currentLocation.getLng());
      snapshot.setPosLat(this.currentLocation.getLat());
      return snapshot;
    }
  }
  