/*
 * Copyright 2019, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.server.delivery;

import com.google.errorprone.annotations.Immutable;

import java.io.Serializable;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.Math.abs;

/**
 * The strategy of splitting the entities into a number of shards uniformly.
 *
 * <p>Uses a hash code of the entity identifier and the remainder of its division by the total
 * number of shards to determine the index of a shard, at which the modification is allowed.
 */
@Immutable
public final class UniformAcrossAllShards implements DeliveryStrategy, Serializable {

    private static final long serialVersionUID = 0L;

    private final int numberOfShards;

    /**
     * Creates an instance of this strategy.
     *
     * @param numberOfShards
     *         a number of shards; must be greater than zero
     */
    private UniformAcrossAllShards(int numberOfShards) {
        checkArgument(numberOfShards > 0, "Number of shards must be positive");
        this.numberOfShards = numberOfShards;
    }

    /**
     * Creates a strategy of uniform target distribution across shards, for a given shard number.
     *
     * @param totalShards
     *         a number of shards
     * @return a uniform distribution strategy instance for a given shard number
     */
    public static DeliveryStrategy forNumber(int totalShards) {
        UniformAcrossAllShards result = new UniformAcrossAllShards(totalShards);
        return result;
    }

    @Override
    public ShardIndex indexFor(Object entityId) {
        if (1 == numberOfShards) {
            return newIndex(0);
        }
        int hashValue = entityId.hashCode();
        int totalShards = shardCount();
        int indexValue = abs(hashValue % totalShards);
        ShardIndex result = newIndex(indexValue);
        return result;
    }

    @Override
    public int shardCount() {
        return numberOfShards;
    }

    private ShardIndex newIndex(int indexValue) {
        return ShardIndex.newBuilder()
                         .setIndex(indexValue)
                         .setOfTotal(shardCount())
                         .build();
    }

    private enum SingleShard {
        INSTANCE;
        private final UniformAcrossAllShards strategy = new UniformAcrossAllShards(1);
    }

    /**
     * Returns a pre-defined strategy instance, which defines a single shard and puts all
     * the targets into it.
     *
     * @return a strategy that puts all entities in a single shard
     */
    @SuppressWarnings("WeakerAccess")   // a part of the public API.
    public static DeliveryStrategy singleShard() {
        return SingleShard.INSTANCE.strategy;
    }
}

@@ -1063,70 +1063,223 @@
 gle.
-errorprone.annotations.Immutable;%0A%0Aimport java.io.Serializable
+common.hash.HashFunction;%0Aimport com.google.common.hash.Hashing;%0Aimport com.google.errorprone.annotations.Immutable;%0Aimport io.spine.string.Stringifiers;%0A%0Aimport java.io.Serializable;%0Aimport java.nio.charset.Charset
 ;%0A%0Ai
@@ -1656,159 +1656,807 @@
 .%0A *
-/%0A@Immutable%0Apublic final class UniformAcrossAllShards implements DeliveryStrategy, Serializable %7B%0A%0A    private static final long serialVersionUID = 0L
+%0A * %3Cp%3ETo reach the consistency of the calculated values across different nodes,%0A * the identifier is transformed into a %7B@code String%7D, and this value is then hashed%0A * using %7B@linkplain Hashing#murmur3_32() MurmurHash3, x86 variant%7D.%0A *%0A * %3Cp%3EWhile Guava's %7B@code Hashing%7D is marked %7B@code @Beta%7D, it is still the best option%0A * for hashing functions %E2%80%94 not to involve any heavy-weight third-party hashing%0A * solutions.%0A */%0A@Immutable%0Apublic final class UniformAcrossAllShards implements DeliveryStrategy, Serializable %7B%0A%0A    private static final long serialVersionUID = 0L;%0A%0A    /**%0A     * The hash function to use for the shard index calculation.%0A     */%0A    @SuppressWarnings(%22UnstableApiUsage%22)   // See the class-level docs.%0A    private static final HashFunction HASHER = Hashing.murmur3_32(0)
 ;%0A%0A 
@@ -3443,24 +3443,29 @@
 Value = 
+hash(
 entityId
 .hashCod
@@ -3460,18 +3460,8 @@
 tyId
-.hashCode(
 );%0A 
@@ -3626,32 +3626,277 @@
  result;%0A    %7D%0A%0A
+    private static int hash(Object entityId) %7B%0A        String asString = Stringifiers.toString(entityId);%0A        int value = HASHER.hashString(asString, Charset.defaultCharset())%0A                          .asInt();%0A        return value;%0A    %7D%0A%0A
     @Override%0A  

Use Guava's `Hashing` to calculate the shard index consistently.