content: package org.araymond.joal.core.ttorrent.client;
  
  import lombok.Getter;
  import lombok.RequiredArgsConstructor;
  import org.araymond.joal.core.torrent.torrent.InfoHash;
  
  import java.time.LocalDateTime;
  import java.time.temporal.TemporalUnit;
  import java.util.*;
  import java.util.concurrent.locks.Lock;
  import java.util.concurrent.locks.ReentrantLock;
  import java.util.function.Predicate;
  
  import static java.util.Collections.emptyList;
  
  public class DelayQueue<T extends DelayQueue.InfoHashAble> {
      private final Lock lock = new ReentrantLock();
      private final Queue<IntervalAware<T>> queue = new PriorityQueue<>();
  
      /**
       * Add to item to the queue, and ensure item uniqueness into the queue.
       *
       * @param item
       * @param interval
       * @param unit
       */
      public void addOrReplace(final T item, final int interval, final TemporalUnit unit) {
          final IntervalAware<T> intervalAware = new IntervalAware<>(
                  item,
                  LocalDateTime.now().plus(interval, unit)
          );
          this.lock.lock();
          try {
              this.queue.removeIf(infoHashEquals(item));  // Ensure no double will be present in the queue (don't want to have two announce type for a torrent)
              this.queue.add(intervalAware);
          } finally {
              this.lock.unlock();
          }
      }
  
      /**
       * Get list of requests that are ready to be executed.
       */
      public List<T> getAvailables() {
          this.lock.lock();
          try {
              final IntervalAware<T> first = queue.peek();
              final LocalDateTime now = LocalDateTime.now();
              if (first == null || first.releaseAt.isAfter(now)) {
                  return emptyList();
              }
  
              final List<T> timedOutItems = new ArrayList<>();
              do {
                  timedOutItems.add(this.queue.poll().getItem());
              } while (!this.queue.isEmpty() && !this.queue.peek().releaseAt.isAfter(now));
  
              return timedOutItems;
          } finally {
              this.lock.unlock();
          }
      }
  
      private Predicate<IntervalAware<T>> infoHashEquals(T item) {
          return i -> i.getItem().getInfoHash().equals(item.getInfoHash());
      }
  
      public void remove(final T itemToRemove) {
          this.lock.lock();
          try {
              this.queue.removeIf(infoHashEquals(itemToRemove));
          } finally {
              this.lock.unlock();
          }
      }
  
      public List<T> drainAll() {
          this.lock.lock();
          try {
              final List<T> items = new ArrayList<>(queue.size());
              while (!queue.isEmpty()) {
                  items.add(queue.poll().getItem());
              }
              return items;
          } finally {
              this.lock.unlock();
          }
      }
  
      @RequiredArgsConstructor
      private static final class IntervalAware<T> implements Comparable<IntervalAware> {
          @Getter private final T item;
          private final LocalDateTime releaseAt;
  
          @Override
          public int compareTo(final IntervalAware o) {
              return this.releaseAt.compareTo(o.releaseAt);
          }
      }
  
      public interface InfoHashAble {
          InfoHash getInfoHash();
      }
  }
  