package org.infinispan.distribution.util;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

import org.infinispan.commons.util.AbstractDelegatingCollection;
import org.infinispan.commons.util.IntSet;
import org.infinispan.distribution.LocalizedCacheTopology;

/**
 * Set implementation that shows a read only view of the provided set by only allowing
 * entries that map to a given segment using the provided consistent hash.
 * <p>
 * This set is useful when you don't want to copy an entire set but only need to see
 * values from the given segments.
 * <p>
 * Note many operations are not constant time when using this set.  Please check the method
 * you are using to see if it will perform differently than normally expected.
 * @author wburns
 * @since 7.2
 */
public class ReadOnlySegmentAwareCollection<E> extends AbstractDelegatingCollection<E> {

   protected final Collection<E> set;
   protected final LocalizedCacheTopology topology;
   protected final IntSet allowedSegments;

   public ReadOnlySegmentAwareCollection(Collection<E> set, LocalizedCacheTopology topology, IntSet allowedSegments) {
      super();
      this.set = Collections.unmodifiableCollection(set);
      this.topology = topology;
      this.allowedSegments = allowedSegments;
   }

   @Override
   protected Collection<E> delegate() {
      return set;
   }

   protected boolean valueAllowed(Object obj) {
      int segment = topology.getSegment(obj);
      return allowedSegments.contains(segment);
   }

   @Override
   public boolean contains(Object o) {
      if (valueAllowed(o)) {
         return super.contains(o);
      }
      return false;
   }

   @Override
   public boolean containsAll(Collection<?> c) {
      for (Object obj : c) {
         if (valueAllowed(obj) && !super.contains(obj)) {
            return false;
         }
      }
      return true;
   }

   /**
    * Checks if the provided set is empty.  This is done by iterating over all of the values
    * until it can find a key that maps to a given segment.
    * <p>
    * This method should always be preferred over checking the size to see if it is empty.
    * <p>
    * This time complexity for this method between O(1) to O(N).
    */
   @Override
   public boolean isEmpty() {
      Iterator<E> iter = iterator();
      return !iter.hasNext();
   }

   /**
    * Returns the size of the read only set.  This is done by iterating over all of the
    * values counting all that are in the segments.
    * <p>
    * If you are using this method to verify if the set is empty, you should instead use
    * the {@link ReadOnlySegmentAwareEntryCollection#isEmpty()} as it will perform better if the
    * size is only used for this purpose.
    * <p>
    * This time complexity for this method is always O(N).
    */
   @Override
   public int size() {
      Iterator<E> iter = iterator();
      int count = 0;
      while (iter.hasNext()) {
         iter.next();
         count++;
      }
      return count;
   }

   @Override
   public Iterator<E> iterator() {
      return new ReadOnlySegmentAwareIterator<>(super.iterator(), topology, allowedSegments);
   }
}
