@Override
 public InsertionData getInsertionData(
     final VehicleRoute currentRoute,
     final Job jobToInsert,
     final Vehicle newVehicle,
     double newVehicleDepartureTime,
     final Driver newDriver,
     final double bestKnownPrice) {
   double fixcost_contribution = getFixCostContribution(currentRoute, jobToInsert, newVehicle);
   if (fixcost_contribution > bestKnownPrice) {
     return InsertionData.createEmptyInsertionData();
   }
   InsertionData iData =
       standardServiceInsertion.getInsertionData(
           currentRoute,
           jobToInsert,
           newVehicle,
           newVehicleDepartureTime,
           newDriver,
           bestKnownPrice);
   if (iData instanceof NoInsertionFound) {
     return iData;
   }
   double totalInsertionCost = iData.getInsertionCost() + fixcost_contribution;
   InsertionData insertionData =
       new InsertionData(
           totalInsertionCost,
           iData.getPickupInsertionIndex(),
           iData.getDeliveryInsertionIndex(),
           newVehicle,
           newDriver);
   insertionData.setVehicleDepartureTime(newVehicleDepartureTime);
   insertionData.getEvents().addAll(iData.getEvents());
   return insertionData;
 }
  static ScoredJob getScoredJob(
      Collection<VehicleRoute> routes,
      Job unassignedJob,
      JobInsertionCostsCalculator insertionCostsCalculator,
      ScoringFunction scoringFunction) {
    InsertionData best = null;
    InsertionData secondBest = null;
    VehicleRoute bestRoute = null;

    double benchmark = Double.MAX_VALUE;
    for (VehicleRoute route : routes) {
      if (secondBest != null) {
        benchmark = secondBest.getInsertionCost();
      }
      InsertionData iData =
          insertionCostsCalculator.getInsertionData(
              route,
              unassignedJob,
              NO_NEW_VEHICLE_YET,
              NO_NEW_DEPARTURE_TIME_YET,
              NO_NEW_DRIVER_YET,
              benchmark);
      if (iData instanceof InsertionData.NoInsertionFound) continue;
      if (best == null) {
        best = iData;
        bestRoute = route;
      } else if (iData.getInsertionCost() < best.getInsertionCost()) {
        secondBest = best;
        best = iData;
        bestRoute = route;
      } else if (secondBest == null || (iData.getInsertionCost() < secondBest.getInsertionCost())) {
        secondBest = iData;
      }
    }

    VehicleRoute emptyRoute = VehicleRoute.emptyRoute();
    InsertionData iData =
        insertionCostsCalculator.getInsertionData(
            emptyRoute,
            unassignedJob,
            NO_NEW_VEHICLE_YET,
            NO_NEW_DEPARTURE_TIME_YET,
            NO_NEW_DRIVER_YET,
            benchmark);
    if (!(iData instanceof InsertionData.NoInsertionFound)) {
      if (best == null) {
        best = iData;
        bestRoute = emptyRoute;
      } else if (iData.getInsertionCost() < best.getInsertionCost()) {
        secondBest = best;
        best = iData;
        bestRoute = emptyRoute;
      } else if (secondBest == null || (iData.getInsertionCost() < secondBest.getInsertionCost())) {
        secondBest = iData;
      }
    }
    if (best == null) {
      return new RegretInsertion.BadJob(unassignedJob);
    }
    double score = score(unassignedJob, best, secondBest, scoringFunction);
    ScoredJob scoredJob;
    if (bestRoute == emptyRoute) {
      scoredJob = new ScoredJob(unassignedJob, score, best, bestRoute, true);
    } else scoredJob = new ScoredJob(unassignedJob, score, best, bestRoute, false);
    return scoredJob;
  }
  /**
   * Runs insertion.
   *
   * <p>
   *
   * <p>Before inserting a job, all unassigned jobs are scored according to its best- and
   * secondBest-insertion plus additional scoring variables.
   *
   * @throws java.lang.RuntimeException if smth went wrong with thread execution
   */
  @Override
  public Collection<Job> insertUnassignedJobs(
      Collection<VehicleRoute> routes, Collection<Job> unassignedJobs) {
    List<Job> badJobs = new ArrayList<Job>(unassignedJobs.size());

    Iterator<Job> jobIterator = unassignedJobs.iterator();
    while (jobIterator.hasNext()) {
      Job job = jobIterator.next();
      if (job instanceof Break) {
        VehicleRoute route = InsertionDataUpdater.findRoute(routes, job);
        if (route == null) {
          badJobs.add(job);
        } else {
          InsertionData iData =
              insertionCostsCalculator.getInsertionData(
                  route,
                  job,
                  NO_NEW_VEHICLE_YET,
                  NO_NEW_DEPARTURE_TIME_YET,
                  NO_NEW_DRIVER_YET,
                  Double.MAX_VALUE);
          if (iData instanceof InsertionData.NoInsertionFound) {
            badJobs.add(job);
          } else {
            insertJob(job, iData, route);
          }
        }
        jobIterator.remove();
      }
    }

    List<Job> jobs = new ArrayList<Job>(unassignedJobs);
    TreeSet<VersionedInsertionData>[] priorityQueues =
        new TreeSet[vrp.getJobs().values().size() + 2];
    VehicleRoute lastModified = null;
    boolean firstRun = true;
    int updateRound = 0;
    Map<VehicleRoute, Integer> updates = new HashMap<VehicleRoute, Integer>();
    while (!jobs.isEmpty()) {
      List<Job> unassignedJobList = new ArrayList<Job>(jobs);
      List<Job> badJobList = new ArrayList<Job>();
      if (!firstRun && lastModified == null)
        throw new IllegalStateException("ho. this must not be.");
      if (firstRun) {
        firstRun = false;
        updateInsertionData(priorityQueues, routes, unassignedJobList, updateRound);
        for (VehicleRoute r : routes) updates.put(r, updateRound);
      } else {
        updateInsertionData(
            priorityQueues, Arrays.asList(lastModified), unassignedJobList, updateRound);
        updates.put(lastModified, updateRound);
      }
      updateRound++;
      ScoredJob bestScoredJob =
          InsertionDataUpdater.getBest(
              switchAllowed,
              initialVehicleIds,
              fleetManager,
              insertionCostsCalculator,
              scoringFunction,
              priorityQueues,
              updates,
              unassignedJobList,
              badJobList);
      if (bestScoredJob != null) {
        if (bestScoredJob.isNewRoute()) {
          routes.add(bestScoredJob.getRoute());
        }
        insertJob(
            bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute());
        jobs.remove(bestScoredJob.getJob());
        lastModified = bestScoredJob.getRoute();
      } else lastModified = null;
      for (Job bad : badJobList) {
        jobs.remove(bad);
        badJobs.add(bad);
      }
    }
    return badJobs;
  }