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

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

import org.infinispan.commons.util.ByRef;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.entries.ImmortalCacheEntry;
import org.infinispan.stream.impl.KeyTrackingTerminalOperation;
import org.infinispan.stream.impl.intops.IntermediateOperation;
import org.infinispan.stream.impl.termop.BaseTerminalOperation;

/**
 * Terminal rehash aware operation that handles an iterator when a map intermediate operation was performed on
 * the stream.  This is important due to the fact that we need to figure out the keys that map to each entry still.
 * This class assumes the stream is composed of {@link java.util.Map.Entry} instances where the key is typed the same
 * as defined K type.
 * @param <K> key type
 * @param <V> unused type
 * @param <V2> resulting type from the operation
 */
public class MapIteratorOperation<K, V, V2> extends BaseTerminalOperation implements KeyTrackingTerminalOperation<K, V,
        V2> {
   protected final int batchSize;

   public MapIteratorOperation(Iterable<IntermediateOperation> intermediateOperations,
           Supplier<Stream<CacheEntry>> supplier, int batchSize) {
      super(intermediateOperations, supplier);
      this.batchSize = batchSize;
   }

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

   @Override
   public List<V> performOperation(IntermediateCollector<Collection<V>> response) {
      /**
       * This is for rehash only! {@link NoMapIteratorOperation} should always be used for non rehash
       */
      throw new UnsupportedOperationException();
   }

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

      List<CacheEntry<K, V2>> collectedValues = new ArrayList(batchSize);

      ByRef<Object> ref = new ByRef<>(null);
      stream = ((Stream<Map.Entry<K, ?>>) stream).peek(
              e -> ref.set(e.getKey()));
      for (IntermediateOperation intermediateOperation : intermediateOperations) {
         stream = intermediateOperation.perform(stream);
      }

      Stream<V> convertedStream = ((Stream<V>)stream);
      // We rely on the fact that iterator processes 1 entry at a time
      convertedStream.forEach(v -> {
         // TODO: do we care about metadata in this case?
         collectedValues.add(new ImmortalCacheEntry(ref.get(), v));
         if (collectedValues.size() >= batchSize) {
            response.sendDataResonse(collectedValues);
            collectedValues.clear();
         }
      });
      return collectedValues;
   }

   public int getBatchSize() {
      return batchSize;
   }
}
