package org.infinispan.distribution.ch.impl;

import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.infinispan.commons.hash.Hash;
import org.infinispan.commons.marshall.AbstractExternalizer;
import org.infinispan.distribution.ch.ConsistentHashFactory;
import org.infinispan.globalstate.ScopedPersistentState;
import org.infinispan.marshall.core.Ids;
import org.infinispan.remoting.transport.Address;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;

/**
 * {@link SyncConsistentHashFactory} adapted for replicated caches, so that the primary owner of a key
 * is the same in replicated and distributed caches.
 *
 * @author Dan Berindei
 * @since 8.2
 */
public class SyncReplicatedConsistentHashFactory implements ConsistentHashFactory<ReplicatedConsistentHash> {
   private static final Log log = LogFactory.getLog(SyncReplicatedConsistentHashFactory.class);
   public static final float OWNED_SEGMENTS_ALLOWED_VARIATION = 1.10f;
   public static final float PRIMARY_SEGMENTS_ALLOWED_VARIATION = 1.20f;

   private static final SyncConsistentHashFactory syncCHF = new SyncConsistentHashFactory();

   @Override
   public ReplicatedConsistentHash create(Hash hashFunction, int numOwners, int numSegments,
         List<Address> members, Map<Address, Float> capacityFactors) {
      DefaultConsistentHash dch = syncCHF.create(hashFunction, 1, numSegments, members, null);
      return replicatedFromDefault(dch);
   }

   @Override
   public ReplicatedConsistentHash fromPersistentState(ScopedPersistentState state) {
      String consistentHashClass = state.getProperty("consistentHash");
      if (!ReplicatedConsistentHash.class.getName().equals(consistentHashClass))
         throw log.persistentConsistentHashMismatch(this.getClass().getName(), consistentHashClass);
      return new ReplicatedConsistentHash(state);
   }

   private ReplicatedConsistentHash replicatedFromDefault(DefaultConsistentHash dch) {
      int numSegments = dch.getNumSegments();
      List<Address> members = dch.getMembers();
      int[] primaryOwners = new int[numSegments];
      for (int segment = 0; segment < numSegments; segment++) {
         primaryOwners[segment] = members.indexOf(dch.locatePrimaryOwnerForSegment(segment));
      }
      return new ReplicatedConsistentHash(dch.getHashFunction(), members, primaryOwners);
   }

   @Override
   public ReplicatedConsistentHash updateMembers(ReplicatedConsistentHash baseCH, List<Address> newMembers,
         Map<Address, Float> actualCapacityFactors) {
      DefaultConsistentHash baseDCH = defaultFromReplicated(baseCH);
      DefaultConsistentHash dch = syncCHF.updateMembers(baseDCH, newMembers, null);
      return replicatedFromDefault(dch);
   }

   private DefaultConsistentHash defaultFromReplicated(ReplicatedConsistentHash baseCH) {
      int numSegments = baseCH.getNumSegments();
      List<Address>[] baseSegmentOwners = new List[numSegments];
      for (int segment = 0; segment < numSegments; segment++) {
         baseSegmentOwners[segment] = Collections.singletonList(baseCH.locatePrimaryOwnerForSegment(segment));
      }
      return new DefaultConsistentHash(baseCH.getHashFunction(), 1,
            numSegments, baseCH.getMembers(), null, baseSegmentOwners);
   }

   @Override
   public ReplicatedConsistentHash rebalance(ReplicatedConsistentHash baseCH) {
      DefaultConsistentHash baseDCH = defaultFromReplicated(baseCH);
      DefaultConsistentHash dch = syncCHF.rebalance(baseDCH);
      return replicatedFromDefault(dch);
   }

   @Override
   public ReplicatedConsistentHash union(ReplicatedConsistentHash ch1, ReplicatedConsistentHash ch2) {
      return ch1.union(ch2);
   }

   public static class Externalizer extends AbstractExternalizer<SyncReplicatedConsistentHashFactory> {

      @Override
      public void writeObject(ObjectOutput output, SyncReplicatedConsistentHashFactory chf) {
      }

      @Override
      @SuppressWarnings("unchecked")
      public SyncReplicatedConsistentHashFactory readObject(ObjectInput unmarshaller) {
         return new SyncReplicatedConsistentHashFactory();
      }

      @Override
      public Integer getId() {
         return Ids.SYNC_REPLICATED_CONSISTENT_HASH_FACTORY;
      }

      @Override
      public Set<Class<? extends SyncReplicatedConsistentHashFactory>> getTypeClasses() {
         return Collections.<Class<? extends SyncReplicatedConsistentHashFactory>>singleton(
               SyncReplicatedConsistentHashFactory.class);
      }
   }
}
