package org.infinispan.stream.impl.termop.object;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.BaseStream;
import java.util.stream.Stream;

import org.infinispan.container.entries.CacheEntry;
import org.infinispan.stream.impl.intops.IntermediateOperation;

/**
 * Terminal rehash aware operation that handles an iterator when no flat map or map intermediate operation was performed
 * on the stream.  This is important due to the fact that we can just return the entries as is and the client can sort
 * out what is the key and what isn't.
 * @param <K> key type
 * @param <V> resulting value type
 */
public class NoMapIteratorOperation<K, V> extends MapIteratorOperation<K, V, V> {
   public NoMapIteratorOperation(Iterable<IntermediateOperation> intermediateOperations,
           Supplier<Stream<CacheEntry>> supplier, int batchSize) {
      super(intermediateOperations, supplier, batchSize);
   }

   @Override
   public boolean lostSegment(boolean stopIfLost) {
      // TODO: stop this early
      return true;
   }

   @Override
   public List<V> performOperation(IntermediateCollector<Collection<V>> response) {
      BaseStream<?, ?> stream = supplier.get();
      for (IntermediateOperation intOp : intermediateOperations) {
         stream = intOp.perform(stream);
      }

      Stream<V> convertedStream = ((Stream<V>)stream);
      return actualPerformOperation(response, convertedStream);
   }

   private <R> List<R> actualPerformOperation(IntermediateCollector<Collection<R>> response, Stream<R> stream) {
      BiConsumer<List<R>, R> accumulator = (l, e) -> {
         l.add(e);
         if (l.size() >= batchSize) {
            response.sendDataResonse(l);
            l.clear();
         }
      };

      // We use collect instead of forEach due to the fact that forEach would require a concurrent
      // list to handle, where as collect will merge them together in a thread safe way
      List<R> list = stream.collect(ArrayList::new, accumulator, (l1, l2) -> {
         l2.forEach(e -> accumulator.accept(l1, e));
      });
      return list;
   }

   @Override
   public Collection<CacheEntry<K, V>> performOperationRehashAware(
           IntermediateCollector<Collection<CacheEntry<K, V>>> response) {
      // We only support sequential streams for iterator rehash aware
      BaseStream<?, ?> stream = supplier.get().sequential();

      for (IntermediateOperation intermediateOperation : intermediateOperations) {
         stream = intermediateOperation.perform(stream);
      }

      return actualPerformOperation(response, (Stream<CacheEntry<K, V>>) stream);
   }
}
