private Optional<Long> getNextRunAt(
      SingularityRequest request,
      RequestState state,
      SingularityDeployStatistics deployStatistics,
      PendingType pendingType) {
    final long now = System.currentTimeMillis();

    long nextRunAt = now;

    if (request.isScheduled()) {
      if (pendingType == PendingType.IMMEDIATE || pendingType == PendingType.RETRY) {
        LOG.info("Scheduling requested immediate run of {}", request.getId());
      } else {
        try {
          Date scheduleFrom = new Date(now);

          CronExpression cronExpression = new CronExpression(request.getQuartzScheduleSafe());

          final Date nextRunAtDate = cronExpression.getNextValidTimeAfter(scheduleFrom);

          if (nextRunAtDate == null) {
            return Optional.absent();
          }

          LOG.trace(
              "Calculating nextRunAtDate for {} (schedule: {}): {} (from: {})",
              request.getId(),
              request.getSchedule(),
              nextRunAtDate,
              scheduleFrom);

          nextRunAt =
              Math.max(
                  nextRunAtDate.getTime(),
                  now); // don't create a schedule that is overdue as this is used to indicate that
                        // singularity is not fulfilling requests.

          LOG.trace(
              "Scheduling next run of {} (schedule: {}) at {} (from: {})",
              request.getId(),
              request.getSchedule(),
              nextRunAtDate,
              scheduleFrom);
        } catch (ParseException pe) {
          throw Throwables.propagate(pe);
        }
      }
    }

    if (pendingType == PendingType.TASK_DONE
        && request.getWaitAtLeastMillisAfterTaskFinishesForReschedule().or(0L) > 0) {
      nextRunAt =
          Math.max(
              nextRunAt, now + request.getWaitAtLeastMillisAfterTaskFinishesForReschedule().get());

      LOG.trace(
          "Adjusted next run of {} to {} (by {}) due to waitAtLeastMillisAfterTaskFinishesForReschedule",
          request.getId(),
          nextRunAt,
          JavaUtils.durationFromMillis(
              request.getWaitAtLeastMillisAfterTaskFinishesForReschedule().get()));
    }

    if (state == RequestState.SYSTEM_COOLDOWN && pendingType != PendingType.NEW_DEPLOY) {
      final long prevNextRunAt = nextRunAt;
      nextRunAt =
          Math.max(
              nextRunAt,
              now + TimeUnit.SECONDS.toMillis(configuration.getCooldownMinScheduleSeconds()));
      LOG.trace(
          "Adjusted next run of {} to {} (from: {}) due to cooldown",
          request.getId(),
          nextRunAt,
          prevNextRunAt);
    }

    return Optional.of(nextRunAt);
  }