package org.infinispan.scattered;

import java.util.concurrent.CompletableFuture;

import org.infinispan.commons.util.IntSet;
import org.infinispan.container.versioning.EntryVersion;

/**
 * Manages versions of entries and states of segments.
 *
 * @author Radim Vansa &lt;rvansa@redhat.com&gt;
 */
public interface ScatteredVersionManager<K> {

   /**
    * Generates a new version for an entry in given segment.
    *
    * @param segment
    */
   EntryVersion incrementVersion(int segment);

   /**
    * @param segment
    * @param version
    * @return True if the version was generated by this node after most recently becoming owner of the given segment.
    */
   boolean isVersionActual(int segment, EntryVersion version);

   /**
    * Notifies the manager that an operation on given key with version previously retrieved from
    * {@link #incrementVersion(int)} has finished. This operation has to be executed on originator of the operation
    * once the entry was stored on both nodes.
    *
    * Eventually order versions of entries will be removed on other nodes. When the entry was completely removed
    * by the operation, the nodes have stored a tombstone of that entry. The older versions will be dropped regularly
    * and after this is confirmed the tombstones will be invalidated, too.
    *
    * @param key
    * @param version
    * @param removal
    */
   void scheduleKeyInvalidation(K key, EntryVersion version, boolean removal);

   /**
    * Forget all non-processed invalidations scheduled through {@link #scheduleKeyInvalidation(Object, EntryVersion, boolean)}
    */
   void clearInvalidations();

   /**
    * Move the segment to {@link SegmentState#BLOCKED} state.
    * @param segment
    */
   void registerSegment(int segment);

   /**
    * Move the segment to {@link SegmentState#NOT_OWNED} state.
    * @param segment
    */
   void unregisterSegment(int segment);

   /**
    * Move the segment from {@link SegmentState#NOT_OWNED} to {@link SegmentState#OWNED} without transferring data.
    * @param segments
    */
   void setOwnedSegments(IntSet segments);

   /**
    * Move the segments from {@link SegmentState#BLOCKED} to {@link SegmentState#KEY_TRANSFER} state.
    */
   void startKeyTransfer(IntSet segments);

   /**
    * All key + version data from given segment have been received, or the key transfer failed.
    * @param segment
    * @param expectValues True when the transfer failed and the segment will be moved
    *                     to the {@link SegmentState#OWNED} state without waiting for values.
    * @param cancelled True is the transfer was cancelled due to a new topology - in that case
    *                  the segment will end up in {@link SegmentState#NOT_OWNED}. This takes
    *                  precedence over <code>expectValues</code>.
    */
   void notifyKeyTransferFinished(int segment, boolean expectValues, boolean cancelled);

   /**
    * All entries have been received and we can put segments owned according to consistent hash
    * to {@link SegmentState#OWNED} state.
    */
   void notifyValueTransferFinished();

   /**
    * @param segment
    * @return Current {@link SegmentState status} of the segment.
    */
   SegmentState getSegmentState(int segment);

   /**
    * @param segment
    * @return A completable future that can be used to schedule an operation once that the segment has moved from
    * {@link SegmentState#BLOCKED} state.
    */
   CompletableFuture<Void> getBlockingFuture(int segment);

   void setValuesTransferTopology(int topologyId);

   /**
    * @return A {@code CompletableFuture} that completes when value transfer has finished for the given topology id.
    */
   CompletableFuture<Void> valuesFuture(int topologyId);

   /**
    * Set current topology id.
    *
    * @param topologyId
    */
   void setTopologyId(int topologyId);

   /**
    * This is called only during preload. Makes sure that the cache will start with topology higher than
    * the one stored in a cache store.
    * @param version
    */
   void updatePreloadedEntryVersion(EntryVersion version);

   enum SegmentState {
      NOT_OWNED('N'),      // Not owned and request to new version ends with failure
      BLOCKED('B'),        // Owned but we're waiting for the previous owner to revoke the segment
      KEY_TRANSFER('K'),   // Owned but does not have previous value
      VALUE_TRANSFER('V'), // Knows who has the correct value but does not know the value itself
      OWNED('O')           // Owned and operating
      ;

      private final char singleChar;

      SegmentState(char singleChar) {
         this.singleChar = singleChar;
      }

      public char singleChar() {
         return singleChar;
      }
   }
}
