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 flat map intermediate operation was performed on
 * the stream.  This is important due to the fact that we need to track what keys have been processed for the iterator.
 * Since flat map can produce multiple values for the same key we need to handle that special so we can guarantee
 * we are returning the keys properly and a rehash wouldn't lose some.
 * 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> resulting value type
 */
public class FlatMapIteratorOperation<K, V> extends BaseTerminalOperation implements KeyTrackingTerminalOperation<K, V,
        Collection<V>> {
   private final int batchSize;

   public FlatMapIteratorOperation(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, Collection<V>>> performOperationRehashAware(
           IntermediateCollector<Collection<CacheEntry<K, Collection<V>>>> response) {
      // We only support sequential streams for iterator rehash aware
      BaseStream<?, ?> stream = supplier.get().sequential();

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

      ByRef<List<V>> currentList = new ByRef<>(new ArrayList<>());
      ByRef<K> currentKey = new ByRef<>(null);
      stream = ((Stream<Map.Entry<K, ?>>) stream).peek(e -> {
         List<V> list = currentList.get();
         if (!list.isEmpty()) {
            collectedValues.add(new ImmortalCacheEntry(currentKey.get(), list));
            if (collectedValues.size() >= batchSize) {
               response.sendDataResonse(collectedValues);
               collectedValues.clear();
               list.clear();
            } else {
               currentList.set(new ArrayList<V>(list.size()));
            }
         }
         currentKey.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 -> {
         currentList.get().add(v);
      });
      List<V> lastList = currentList.get();
      if (lastList != null && !lastList.isEmpty()) {
         collectedValues.add(new ImmortalCacheEntry(currentKey.get(), lastList));
      }
      return collectedValues;
   }

   public int getBatchSize() {
      return batchSize;
   }
}
