/**
  * Call to get the next task that is ready to be run. If there are no tasks, or the next task
  * still has a remaining delay, this will return {@code null}.
  *
  * <p>If this is being called in parallel with a {@link #tick(ExecutionHandlerInterface)} call,
  * the returned task may already be running. You must check the {@code TaskContainer.running}
  * boolean if this condition is important to you.
  *
  * @param onlyReturnReadyTask {@code false} to return scheduled tasks which may not be ready for
  *     execution
  * @return next ready task, or {@code null} if there are none
  */
 protected TaskContainer getNextTask(boolean onlyReturnReadyTask) {
   TaskContainer nextScheduledTask = scheduledQueue.peekFirst();
   TaskContainer nextExecuteTask = executeQueue.peek();
   if (nextExecuteTask != null) {
     if (nextScheduledTask != null) {
       long scheduleDelay;
       long executeDelay;
       ClockWrapper.stopForcingUpdate();
       try {
         scheduleDelay = nextScheduledTask.getDelayInMillis();
         executeDelay = nextExecuteTask.getDelayInMillis();
       } finally {
         ClockWrapper.resumeForcingUpdate();
       }
       if (scheduleDelay < executeDelay) {
         return nextScheduledTask;
       } else {
         return nextExecuteTask;
       }
     } else {
       return nextExecuteTask;
     }
   } else if (!onlyReturnReadyTask
       || (nextScheduledTask != null && nextScheduledTask.getDelayInMillis() <= 0)) {
     return nextScheduledTask;
   } else {
     return null;
   }
 }
    @Override
    public void runComplete() {
      synchronized (scheduledQueue.getModificationLock()) {
        ClockWrapper.stopForcingUpdate();
        try {
          updateNextRunTime();

          // almost certainly will be the first item in the queue
          int currentIndex = scheduledQueue.indexOf(this);
          if (currentIndex < 0) {
            // task was removed from queue, do not re-insert
            return;
          }
          long nextDelay = getDelayInMillis();
          if (nextDelay < 0) {
            nextDelay = 0;
          }
          int insertionIndex = ListUtils.getInsertionEndIndex(scheduledQueue, nextDelay, true);

          scheduledQueue.reposition(currentIndex, insertionIndex);
        } finally {
          ClockWrapper.resumeForcingUpdate();
        }
      }
    }
  /**
   * Adds a task to scheduled/recurring queue. This call is more expensive than {@link
   * #addImmediateExecute(OneTimeTask)}, but is necessary for any tasks which are either delayed or
   * recurring.
   *
   * @param runnable Task to execute on next {@link #tick(ExceptionHandlerInterface)} call
   */
  protected void addScheduled(TaskContainer runnable) {
    synchronized (scheduledQueue.getModificationLock()) {
      ClockWrapper.stopForcingUpdate();
      try {
        int insertionIndex = ListUtils.getInsertionEndIndex(scheduledQueue, runnable, true);

        scheduledQueue.add(insertionIndex, runnable);
      } finally {
        ClockWrapper.resumeForcingUpdate();
      }
    }

    notifyQueueUpdate();
  }
 /**
  * Abstract call to get the value the scheduler should use to represent the current time. This can
  * be overridden if someone wanted to artificially change the time.
  *
  * @return current time in milliseconds
  */
 protected long nowInMillis() {
   return ClockWrapper.getSemiAccurateMillis();
 }