/*
 * Copyright (C) 2011 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.hash;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.Beta;
import com.google.errorprone.annotations.Immutable;
import java.security.Key;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javax.crypto.spec.SecretKeySpec;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Static methods to obtain {@link HashFunction} instances, and other static hashing-related
 * utilities.
 *
 *
A comparison of the various hash functions can be found here.
 *
 * @author Kevin Bourrillion
 * @author Dimitris Andreou
 * @author Kurt Alfred Kluever
 * @since 11.0
 */
@Beta
public final class Hashing {
  /**
   * Returns a general-purpose, temporary-use, non-cryptographic hash function. The algorithm
   * the returned function implements is unspecified and subject to change without notice.
   *
   *
Warning: a new random seed for these functions is chosen each time the {@code
   * Hashing} class is loaded. Do not use this method if hash codes may escape the current
   * process in any way, for example being sent over RPC, or saved to disk. For a general-purpose,
   * non-cryptographic hash function that will never change behavior, we suggest {@link
   * #murmur3_128}.
   *
   *
Repeated calls to this method on the same loaded {@code Hashing} class, using the same value
   * for {@code minimumBits}, will return identically-behaving {@link HashFunction} instances.
   *
   * @param minimumBits a positive integer (can be arbitrarily large)
   * @return a hash function, described above, that produces hash codes of length {@code
   *     minimumBits} or greater
   */
  public static HashFunction goodFastHash(int minimumBits) {
    int bits = checkPositiveAndMakeMultipleOf32(minimumBits);

    if (bits == 32) {
      return Murmur3_32HashFunction.GOOD_FAST_HASH_32;
    }
    if (bits <= 128) {
      return Murmur3_128HashFunction.GOOD_FAST_HASH_128;
    }

    // Otherwise, join together some 128-bit murmur3s
    int hashFunctionsNeeded = (bits + 127) / 128;
    HashFunction[] hashFunctions = new HashFunction[hashFunctionsNeeded];
    hashFunctions[0] = Murmur3_128HashFunction.GOOD_FAST_HASH_128;
    int seed = GOOD_FAST_HASH_SEED;
    for (int i = 1; i < hashFunctionsNeeded; i++) {
      seed += 1500450271; // a prime; shouldn't matter
      hashFunctions[i] = murmur3_128(seed);
    }
    return new ConcatenatedHashFunction(hashFunctions);
  }

  /**
   * Used to randomize {@link #goodFastHash} instances, so that programs which persist anything
   * dependent on the hash codes they produce will fail sooner.
   */
  @SuppressWarnings("GoodTime") // reading system time without TimeSource
  static final int GOOD_FAST_HASH_SEED = (int) System.currentTimeMillis();

  /**
   * Returns a hash function implementing the 32-bit murmur3
   * algorithm, x86 variant (little-endian variant), using the given seed value.
   *
   *
The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A).
   */
  public static HashFunction murmur3_32(int seed) {
    return new Murmur3_32HashFunction(seed);
  }

  /**
   * Returns a hash function implementing the 32-bit murmur3
   * algorithm, x86 variant (little-endian variant), using a seed value of zero.
   *
   *
The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A).
   */
  public static HashFunction murmur3_32() {
    return Murmur3_32HashFunction.MURMUR3_32;
  }

  /**
   * Returns a hash function implementing the 128-bit murmur3
   * algorithm, x64 variant (little-endian variant), using the given seed value.
   *
   *
The exact C++ equivalent is the MurmurHash3_x64_128 function (Murmur3F).
   */
  public static HashFunction murmur3_128(int seed) {
    return new Murmur3_128HashFunction(seed);
  }

  /**
   * Returns a hash function implementing the 128-bit murmur3
   * algorithm, x64 variant (little-endian variant), using a seed value of zero.
   *
   *
The exact C++ equivalent is the MurmurHash3_x64_128 function (Murmur3F).
   */
  public static HashFunction murmur3_128() {
    return Murmur3_128HashFunction.MURMUR3_128;
  }

  /**
   * Returns a hash function implementing the 64-bit
   * SipHash-2-4 algorithm using a seed value of {@code k = 00 01 02 ...}.
   *
   * @since 15.0
   */
  public static HashFunction sipHash24() {
    return SipHashFunction.SIP_HASH_24;
  }

  /**
   * Returns a hash function implementing the 64-bit
   * SipHash-2-4 algorithm using the given seed.
   *
   * @since 15.0
   */
  public static HashFunction sipHash24(long k0, long k1) {
    return new SipHashFunction(2, 4, k0, k1);
  }

  /**
   * Returns a hash function implementing the MD5 hash algorithm (128 hash bits).
   *
   * @deprecated If you must interoperate with a system that requires MD5, then use this method,
   *     despite its deprecation. But if you can choose your hash function, avoid MD5, which is
   *     neither fast nor secure. As of January 2017, we suggest:
   *

   *
For security:
   *           {@link Hashing#sha256} or a higher-level API.
   *
For speed: {@link Hashing#goodFastHash}, though see its docs for caveats.
   *

   */
  @Deprecated
  public static HashFunction md5() {
    return Md5Holder.MD5;
  }

  private static class Md5Holder {
    static final HashFunction MD5 = new MessageDigestHashFunction("MD5", "Hashing.md5()");
  }

  /**
   * Returns a hash function implementing the SHA-1 algorithm (160 hash bits).
   *
   * @deprecated If you must interoperate with a system that requires SHA-1, then use this method,
   *     despite its deprecation. But if you can choose your hash function, avoid SHA-1, which is
   *     neither fast nor secure. As of January 2017, we suggest:
   *

   *
For security:
   *           {@link Hashing#sha256} or a higher-level API.
   *
For speed: {@link Hashing#goodFastHash}, though see its docs for caveats.
   *

   */
  @Deprecated
  public static HashFunction sha1() {
    return Sha1Holder.SHA_1;
  }

  private static class Sha1Holder {
    static final HashFunction SHA_1 = new MessageDigestHashFunction("SHA-1", "Hashing.sha1()");
  }

  /** Returns a hash function implementing the SHA-256 algorithm (256 hash bits). */
  public static HashFunction sha256() {
    return Sha256Holder.SHA_256;
  }

  private static class Sha256Holder {
    static final HashFunction SHA_256 =
        new MessageDigestHashFunction("SHA-256", "Hashing.sha256()");
  }

  /**
   * Returns a hash function implementing the SHA-384 algorithm (384 hash bits).
   *
   * @since 19.0
   */
  public static HashFunction sha384() {
    return Sha384Holder.SHA_384;
  }

  private static class Sha384Holder {
    static final HashFunction SHA_384 =
        new MessageDigestHashFunction("SHA-384", "Hashing.sha384()");
  }

  /** Returns a hash function implementing the SHA-512 algorithm (512 hash bits). */
  public static HashFunction sha512() {
    return Sha512Holder.SHA_512;
  }

  private static class Sha512Holder {
    static final HashFunction SHA_512 =
        new MessageDigestHashFunction("SHA-512", "Hashing.sha512()");
  }

  /**
   * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the
   * MD5 (128 hash bits) hash function and the given secret key.
   *
   *
   * @param key the secret key
   * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC
   * @since 20.0
   */
  public static HashFunction hmacMd5(Key key) {
    return new MacHashFunction("HmacMD5", key, hmacToString("hmacMd5", key));
  }

  /**
   * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the
   * MD5 (128 hash bits) hash function and a {@link SecretKeySpec} created from the given byte array
   * and the MD5 algorithm.
   *
   *
   * @param key the key material of the secret key
   * @since 20.0
   */
  public static HashFunction hmacMd5(byte[] key) {
    return hmacMd5(new SecretKeySpec(checkNotNull(key), "HmacMD5"));
  }

  /**
   * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the
   * SHA-1 (160 hash bits) hash function and the given secret key.
   *
   *
   * @param key the secret key
   * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC
   * @since 20.0
   */
  public static HashFunction hmacSha1(Key key) {
    return new MacHashFunction("HmacSHA1", key, hmacToString("hmacSha1", key));
  }

  /**
   * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the
   * SHA-1 (160 hash bits) hash function and a {@link SecretKeySpec} created from the given byte
   * array and the SHA-1 algorithm.
   *
   *
   * @param key the key material of the secret key
   * @since 20.0
   */
  public static HashFunction hmacSha1(byte[] key) {
    return hmacSha1(new SecretKeySpec(checkNotNull(key), "HmacSHA1"));
  }

  /**
   * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the
   * SHA-256 (256 hash bits) hash function and the given secret key.
   *
   *
   * @param key the secret key
   * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC
   * @since 20.0
   */
  public static HashFunction hmacSha256(Key key) {
    return new MacHashFunction("HmacSHA256", key, hmacToString("hmacSha256", key));
  }

  /**
   * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the
   * SHA-256 (256 hash bits) hash function and a {@link SecretKeySpec} created from the given byte
   * array and the SHA-256 algorithm.
   *
   *
   * @param key the key material of the secret key
   * @since 20.0
   */
  public static HashFunction hmacSha256(byte[] key) {
    return hmacSha256(new SecretKeySpec(checkNotNull(key), "HmacSHA256"));
  }

  /**
   * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the
   * SHA-512 (512 hash bits) hash function and the given secret key.
   *
   *
   * @param key the secret key
   * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC
   * @since 20.0
   */
  public static HashFunction hmacSha512(Key key) {
    return new MacHashFunction("HmacSHA512", key, hmacToString("hmacSha512", key));
  }

  /**
   * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the
   * SHA-512 (512 hash bits) hash function and a {@link SecretKeySpec} created from the given byte
   * array and the SHA-512 algorithm.
   *
   *
   * @param key the key material of the secret key
   * @since 20.0
   */
  public static HashFunction hmacSha512(byte[] key) {
    return hmacSha512(new SecretKeySpec(checkNotNull(key), "HmacSHA512"));
  }

  private static String hmacToString(String methodName, Key key) {
    return String.format(
        "Hashing.%s(Key[algorithm=%s, format=%s])",
        methodName, key.getAlgorithm(), key.getFormat());
  }

  /**
   * Returns a hash function implementing the CRC32C checksum algorithm (32 hash bits) as described
   * by RFC 3720, Section 12.1.
   *
   *
This function is best understood as a checksum rather than a true hash function.
   *
   * @since 18.0
   */
  public static HashFunction crc32c() {
    return Crc32cHashFunction.CRC_32_C;
  }

  /**
   * Returns a hash function implementing the CRC-32 checksum algorithm (32 hash bits).
   *
   *
To get the {@code long} value equivalent to {@link Checksum#getValue()} for a {@code
   * HashCode} produced by this function, use {@link HashCode#padToLong()}.
   *
   *
This function is best understood as a checksum rather than a true hash function.
   *
   * @since 14.0
   */
  public static HashFunction crc32() {
    return ChecksumType.CRC_32.hashFunction;
  }

  /**
   * Returns a hash function implementing the Adler-32 checksum algorithm (32 hash bits).
   *
   *
To get the {@code long} value equivalent to {@link Checksum#getValue()} for a {@code
   * HashCode} produced by this function, use {@link HashCode#padToLong()}.
   *
   *
This function is best understood as a checksum rather than a true hash function.
   *
   * @since 14.0
   */
  public static HashFunction adler32() {
    return ChecksumType.ADLER_32.hashFunction;
  }

  @Immutable
  enum ChecksumType implements ImmutableSupplier<Checksum> {
    CRC_32("Hashing.crc32()") {
      @Override
      public Checksum get() {
        return new CRC32();
      }
    },
    ADLER_32("Hashing.adler32()") {
      @Override
      public Checksum get() {
        return new Adler32();
      }
    };

    public final HashFunction hashFunction;

    ChecksumType(String toString) {
      this.hashFunction = new ChecksumHashFunction(this, 32, toString);
    }
  }

  /**
   * Returns a hash function implementing FarmHash's Fingerprint64, an open-source algorithm.
   *
   *
This is designed for generating persistent fingerprints of strings. It isn't
   * cryptographically secure, but it produces a high-quality hash with fewer collisions than some
   * alternatives we've used in the past.
   *
   *
FarmHash fingerprints are encoded by {@link HashCode#asBytes} in little-endian order. This
   * means {@link HashCode#asLong} is guaranteed to return the same value that
   * farmhash::Fingerprint64() would for the same input (when compared using {@link
   * com.google.common.primitives.UnsignedLongs}'s encoding of 64-bit unsigned numbers).
   *
   *
This function is best understood as a fingerprint rather than a true
   * hash function.
   *
   * @since 20.0
   */
  public static HashFunction farmHashFingerprint64() {
    return FarmHashFingerprint64.FARMHASH_FINGERPRINT_64;
  }

  /**
   * Assigns to {@code hashCode} a "bucket" in the range {@code [0, buckets)}, in a uniform manner
   * that minimizes the need for remapping as {@code buckets} grows. That is, {@code
   * consistentHash(h, n)} equals:
   *
   *

   *
{@code n - 1}, with approximate probability {@code 1/n}
   *
{@code consistentHash(h, n - 1)}, otherwise (probability {@code 1 - 1/n})
   *

   *
   *
This method is suitable for the common use case of dividing work among buckets that meet the
   * following conditions:
   *
   *

   *
You want to assign the same fraction of inputs to each bucket.
   *
When you reduce the number of buckets, you can accept that the most recently added
   *       buckets will be removed first. More concretely, if you are dividing traffic among tasks,
   *       you can decrease the number of tasks from 15 and 10, killing off the final 5 tasks, and
   *       {@code consistentHash} will handle it. If, however, you are dividing traffic among
   *       servers {@code alpha}, {@code bravo}, and {@code charlie} and you occasionally need to
   *       take each of the servers offline, {@code consistentHash} will be a poor fit: It provides
   *       no way for you to specify which of the three buckets is disappearing. Thus, if your
   *       buckets change from {@code [alpha, bravo, charlie]} to {@code [bravo, charlie]}, it will
   *       assign all the old {@code alpha} traffic to {@code bravo} and all the old {@code bravo}
   *       traffic to {@code charlie}, rather than letting {@code bravo} keep its traffic.
   *

   *
   *
   *
See the Wikipedia article on
   * consistent hashing for more information.
   */
  public static int consistentHash(HashCode hashCode, int buckets) {
    return consistentHash(hashCode.padToLong(), buckets);
  }

  /**
   * Assigns to {@code input} a "bucket" in the range {@code [0, buckets)}, in a uniform manner that
   * minimizes the need for remapping as {@code buckets} grows. That is, {@code consistentHash(h,
   * n)} equals:
   *
   *

   *
{@code n - 1}, with approximate probability {@code 1/n}
   *
{@code consistentHash(h, n - 1)}, otherwise (probability {@code 1 - 1/n})
   *

   *
   *
This method is suitable for the common use case of dividing work among buckets that meet the
   * following conditions:
   *
   *

   *
You want to assign the same fraction of inputs to each bucket.
   *
When you reduce the number of buckets, you can accept that the most recently added
   *       buckets will be removed first. More concretely, if you are dividing traffic among tasks,
   *       you can decrease the number of tasks from 15 and 10, killing off the final 5 tasks, and
   *       {@code consistentHash} will handle it. If, however, you are dividing traffic among
   *       servers {@code alpha}, {@code bravo}, and {@code charlie} and you occasionally need to
   *       take each of the servers offline, {@code consistentHash} will be a poor fit: It provides
   *       no way for you to specify which of the three buckets is disappearing. Thus, if your
   *       buckets change from {@code [alpha, bravo, charlie]} to {@code [bravo, charlie]}, it will
   *       assign all the old {@code alpha} traffic to {@code bravo} and all the old {@code bravo}
   *       traffic to {@code charlie}, rather than letting {@code bravo} keep its traffic.
   *

   *
   *
   *
See the Wikipedia article on
   * consistent hashing for more information.
   */
  public static int consistentHash(long input, int buckets) {
    checkArgument(buckets > 0, "buckets must be positive: %s", buckets);
    LinearCongruentialGenerator generator = new LinearCongruentialGenerator(input);
    int candidate = 0;
    int next;

    // Jump from bucket to bucket until we go out of range
    while (true) {
      next = (int) ((candidate + 1) / generator.nextDouble());
      if (next >= 0 && next < buckets) {
        candidate = next;
      } else {
        return candidate;
      }
    }
  }

  /**
   * Returns a hash code, having the same bit length as each of the input hash codes, that combines
   * the information of these hash codes in an ordered fashion. That is, whenever two equal hash
   * codes are produced by two calls to this method, it is as likely as possible that each
   * was computed from the same input hash codes in the same order.
   *
   * @throws IllegalArgumentException if {@code hashCodes} is empty, or the hash codes do not all
   *     have the same bit length
   */
  public static HashCode combineOrdered(Iterable<HashCode> hashCodes) {
    Iterator<HashCode> iterator = hashCodes.iterator();
    checkArgument(iterator.hasNext(), "Must be at least 1 hash code to combine.");
    int bits = iterator.next().bits();
    byte[] resultBytes = new byte[bits / 8];
    for (HashCode hashCode : hashCodes) {
      byte[] nextBytes = hashCode.asBytes();
      checkArgument(
          nextBytes.length == resultBytes.length, "All hashcodes must have the same bit length.");
      for (int i = 0; i < nextBytes.length; i++) {
        resultBytes[i] = (byte) (resultBytes[i] * 37 ^ nextBytes[i]);
      }
    }
    return HashCode.fromBytesNoCopy(resultBytes);
  }

  /**
   * Returns a hash code, having the same bit length as each of the input hash codes, that combines
   * the information of these hash codes in an unordered fashion. That is, whenever two equal hash
   * codes are produced by two calls to this method, it is as likely as possible that each
   * was computed from the same input hash codes in some order.
   *
   * @throws IllegalArgumentException if {@code hashCodes} is empty, or the hash codes do not all
   *     have the same bit length
   */
  public static HashCode combineUnordered(Iterable<HashCode> hashCodes) {
    Iterator<HashCode> iterator = hashCodes.iterator();
    checkArgument(iterator.hasNext(), "Must be at least 1 hash code to combine.");
    byte[] resultBytes = new byte[iterator.next().bits() / 8];
    for (HashCode hashCode : hashCodes) {
      byte[] nextBytes = hashCode.asBytes();
      checkArgument(
          nextBytes.length == resultBytes.length, "All hashcodes must have the same bit length.");
      for (int i = 0; i < nextBytes.length; i++) {
        resultBytes[i] += nextBytes[i];
      }
    }
    return HashCode.fromBytesNoCopy(resultBytes);
  }

  /** Checks that the passed argument is positive, and ceils it to a multiple of 32. */
  static int checkPositiveAndMakeMultipleOf32(int bits) {
    checkArgument(bits > 0, "Number of bits must be positive");
    return (bits + 31) & ~31;
  }

  /**
   * Returns a hash function which computes its hash code by concatenating the hash codes of the
   * underlying hash functions together. This can be useful if you need to generate hash codes of a
   * specific length.
   *
   *
For example, if you need 1024-bit hash codes, you could join two {@link Hashing#sha512} hash
   * functions together: {@code Hashing.concatenating(Hashing.sha512(), Hashing.sha512())}.
   *
   * @since 19.0
   */
  public static HashFunction concatenating(
      HashFunction first, HashFunction second, HashFunction... rest) {
    // We can't use Lists.asList() here because there's no hash->collect dependency
    List<HashFunction> list = new ArrayList<>();
    list.add(first);
    list.add(second);
    list.addAll(Arrays.asList(rest));
    return new ConcatenatedHashFunction(list.toArray(new HashFunction[0]));
  }

  /**
   * Returns a hash function which computes its hash code by concatenating the hash codes of the
   * underlying hash functions together. This can be useful if you need to generate hash codes of a
   * specific length.
   *
   *
For example, if you need 1024-bit hash codes, you could join two {@link Hashing#sha512} hash
   * functions together: {@code Hashing.concatenating(Hashing.sha512(), Hashing.sha512())}.
   *
   * @since 19.0
   */
  public static HashFunction concatenating(Iterable<HashFunction> hashFunctions) {
    checkNotNull(hashFunctions);
    // We can't use Iterables.toArray() here because there's no hash->collect dependency
    List<HashFunction> list = new ArrayList<>();
    for (HashFunction hashFunction : hashFunctions) {
      list.add(hashFunction);
    }
    checkArgument(list.size() > 0, "number of hash functions (%s) must be > 0", list.size());
    return new ConcatenatedHashFunction(list.toArray(new HashFunction[0]));
  }

  private static final class ConcatenatedHashFunction extends AbstractCompositeHashFunction {

    private ConcatenatedHashFunction(HashFunction... functions) {
      super(functions);
      for (HashFunction function : functions) {
        checkArgument(
            function.bits() % 8 == 0,
            "the number of bits (%s) in hashFunction (%s) must be divisible by 8",
            function.bits(),
            function);
      }
    }

    @Override
    HashCode makeHash(Hasher[] hashers) {
      byte[] bytes = new byte[bits() / 8];
      int i = 0;
      for (Hasher hasher : hashers) {
        HashCode newHash = hasher.hash();
        i += newHash.writeBytesTo(bytes, i, newHash.bits() / 8);
      }
      return HashCode.fromBytesNoCopy(bytes);
    }

    @Override
    public int bits() {
      int bitSum = 0;
      for (HashFunction function : functions) {
        bitSum += function.bits();
      }
      return bitSum;
    }

    @Override
    public boolean equals(@Nullable Object object) {
      if (object instanceof ConcatenatedHashFunction) {
        ConcatenatedHashFunction other = (ConcatenatedHashFunction) object;
        return Arrays.equals(functions, other.functions);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Arrays.hashCode(functions);
    }
  }

  /**
   * Linear CongruentialGenerator to use for consistent hashing. See
   * http://en.wikipedia.org/wiki/Linear_congruential_generator
   */
  private static final class LinearCongruentialGenerator {
    private long state;

    public LinearCongruentialGenerator(long seed) {
      this.state = seed;
    }

    public double nextDouble() {
      state = 2862933555777941757L * state + 1;
      return ((double) ((int) (state >>> 33) + 1)) / 0x1.0p31;
    }
  }

  private Hashing() {}
}


/*
 * Copyright (C) 2012 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.CollectPreconditions.checkRemove;
import static com.google.common.collect.Hashing.smearedHash;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.j2objc.annotations.WeakOuter;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * CompactHashMap is an implementation of a Map. All optional operations (put and remove) are
 * supported. Null keys and values are supported.
 *
 *
{@code containsKey(k)}, {@code put(k, v)} and {@code remove(k)} are all (expected and
 * amortized) constant time operations. Expected in the hashtable sense (depends on the hash
 * function doing a good job of distributing the elements to the buckets to a distribution not far
 * from uniform), and amortized since some operations can trigger a hash table resize.
 *
 *
Unlike {@code java.util.HashMap}, iteration is only proportional to the actual {@code size()},
 * which is optimal, and not the size of the internal hashtable, which could be much larger
 * than {@code size()}. Furthermore, this structure places significantly reduced load on the garbage
 * collector by only using a constant number of internal objects.
 *
 *
If there are no removals, then iteration order for the {@link #entrySet}, {@link #keySet}, and
 * {@link #values} views is the same as insertion order. Any removal invalidates any ordering
 * guarantees.
 *
 *
This class should not be assumed to be universally superior to {@code java.util.HashMap}.
 * Generally speaking, this class reduces object allocation and memory consumption at the price of
 * moderately increased constant factors of CPU. Only use this class when there is a specific reason
 * to prioritize memory over CPU.
 *
 * @author Louis Wasserman
 */
@GwtIncompatible // not worth using in GWT for now
class CompactHashMap<K, V> extends AbstractMap<K, V> implements Serializable {
  /*
   * TODO: Make this a drop-in replacement for j.u. versions, actually drop them in, and test the
   * world. Figure out what sort of space-time tradeoff we're actually going to get here with the
   * *Map variants. Followon optimizations, such as using 16-bit indices for small collections, will
   * take more work to implement. This class is particularly hard to benchmark, because the benefit
   * is not only in less allocation, but also having the GC do less work to scan the heap because of
   * fewer references, which is particularly hard to quantify.
   */

  /** Creates an empty {@code CompactHashMap} instance. */
  public static <K, V> CompactHashMap<K, V> create() {
    return new CompactHashMap<>();
  }

  /**
   * Creates a {@code CompactHashMap} instance, with a high enough "initial capacity" that it
   * should hold {@code expectedSize} elements without growth.
   *
   * @param expectedSize the number of elements you expect to add to the returned set
   * @return a new, empty {@code CompactHashMap} with enough capacity to hold {@code expectedSize}
   *     elements without resizing
   * @throws IllegalArgumentException if {@code expectedSize} is negative
   */
  public static <K, V> CompactHashMap<K, V> createWithExpectedSize(int expectedSize) {
    return new CompactHashMap<>(expectedSize);
  }

  private static final float LOAD_FACTOR = 1.0f;

  /** Bitmask that selects the low 32 bits. */
  private static final long NEXT_MASK = (1L << 32) - 1;

  /** Bitmask that selects the high 32 bits. */
  private static final long HASH_MASK = ~NEXT_MASK;

  // TODO(user): decide default size
  static final int DEFAULT_SIZE = 3;

  // used to indicate blank table entries
  static final int UNSET = -1;

  /**
   * The hashtable. Its values are indexes to the keys, values, and entries arrays.
   *
   *
Currently, the UNSET value means "null pointer", and any non negative value x is the actual
   * index.
   *
   *
Its size must be a power of two.
   */
  private transient int @MonotonicNonNull [] table;

  /**
   * Contains the logical entries, in the range of [0, size()). The high 32 bits of each long is the
   * smeared hash of the element, whereas the low 32 bits is the "next" pointer (pointing to the
   * next entry in the bucket chain). The pointers in [size(), entries.length) are all "null"
   * (UNSET).
   */
  @VisibleForTesting transient long @MonotonicNonNull [] entries;

  /**
   * The keys of the entries in the map, in the range of [0, size()). The keys in [size(),
   * keys.length) are all {@code null}.
   */
  @VisibleForTesting transient Object @MonotonicNonNull [] keys;

  /**
   * The values of the entries in the map, in the range of [0, size()). The values in [size(),
   * values.length) are all {@code null}.
   */
  @VisibleForTesting transient Object @MonotonicNonNull [] values;

  /**
   * Keeps track of modifications of this set, to make it possible to throw
   * ConcurrentModificationException in the iterator. Note that we choose not to make this volatile,
   * so we do less of a "best effort" to track such errors, for better performance.
   */
  transient int modCount;

  /** The number of elements contained in the set. */
  private transient int size;

  /** Constructs a new empty instance of {@code CompactHashMap}. */
  CompactHashMap() {
    init(DEFAULT_SIZE);
  }

  /**
   * Constructs a new instance of {@code CompactHashMap} with the specified capacity.
   *
   * @param expectedSize the initial capacity of this {@code CompactHashMap}.
   */
  CompactHashMap(int expectedSize) {
    init(expectedSize);
  }

  /** Pseudoconstructor for serialization support. */
  void init(int expectedSize) {
    Preconditions.checkArgument(expectedSize >= 0, "Expected size must be non-negative");
    this.modCount = Math.max(1, expectedSize); // Save expectedSize for use in allocArrays()
  }

  /** Returns whether arrays need to be allocated. */
  boolean needsAllocArrays() {
    return table == null;
  }

  /** Handle lazy allocation of arrays. */
  void allocArrays() {
    Preconditions.checkState(needsAllocArrays(), "Arrays already allocated");

    int expectedSize = modCount;
    int buckets = Hashing.closedTableSize(expectedSize, LOAD_FACTOR);
    this.table = newTable(buckets);

    this.entries = newEntries(expectedSize);
    this.keys = new Object[expectedSize];
    this.values = new Object[expectedSize];
  }

  private static int[] newTable(int size) {
    int[] array = new int[size];
    Arrays.fill(array, UNSET);
    return array;
  }

  private static long[] newEntries(int size) {
    long[] array = new long[size];
    Arrays.fill(array, UNSET);
    return array;
  }

  private int hashTableMask() {
    return table.length - 1;
  }

  private static int getHash(long entry) {
    return (int) (entry >>> 32);
  }

  /** Returns the index, or UNSET if the pointer is "null" */
  private static int getNext(long entry) {
    return (int) entry;
  }

  /** Returns a new entry value by changing the "next" index of an existing entry */
  private static long swapNext(long entry, int newNext) {
    return (HASH_MASK & entry) | (NEXT_MASK & newNext);
  }

  /**
   * Mark an access of the specified entry. Used only in {@code CompactLinkedHashMap} for LRU
   * ordering.
   */
  void accessEntry(int index) {
    // no-op by default
  }

  @CanIgnoreReturnValue
  @Override
  public @Nullable V put(@Nullable K key, @Nullable V value) {
    if (needsAllocArrays()) {
      allocArrays();
    }
    long[] entries = this.entries;
    Object[] keys = this.keys;
    Object[] values = this.values;

    int hash = smearedHash(key);
    int tableIndex = hash & hashTableMask();
    int newEntryIndex = this.size; // current size, and pointer to the entry to be appended
    int next = table[tableIndex];
    if (next == UNSET) { // uninitialized bucket
      table[tableIndex] = newEntryIndex;
    } else {
      int last;
      long entry;
      do {
        last = next;
        entry = entries[next];
        if (getHash(entry) == hash && Objects.equal(key, keys[next])) {
          @SuppressWarnings("unchecked") // known to be a V
          @Nullable
          V oldValue = (V) values[next];

          values[next] = value;
          accessEntry(next);
          return oldValue;
        }
        next = getNext(entry);
      } while (next != UNSET);
      entries[last] = swapNext(entry, newEntryIndex);
    }
    if (newEntryIndex == Integer.MAX_VALUE) {
      throw new IllegalStateException("Cannot contain more than Integer.MAX_VALUE elements!");
    }
    int newSize = newEntryIndex + 1;
    resizeMeMaybe(newSize);
    insertEntry(newEntryIndex, key, value, hash);
    this.size = newSize;
    int oldCapacity = table.length;
    if (Hashing.needsResizing(newEntryIndex, oldCapacity, LOAD_FACTOR)) {
      resizeTable(2 * oldCapacity);
    }
    modCount++;
    return null;
  }

  /**
   * Creates a fresh entry with the specified object at the specified position in the entry arrays.
   */
  void insertEntry(int entryIndex, @Nullable K key, @Nullable V value, int hash) {
    this.entries[entryIndex] = ((long) hash << 32) | (NEXT_MASK & UNSET);
    this.keys[entryIndex] = key;
    this.values[entryIndex] = value;
  }

  /** Resizes the entries storage if necessary. */
  private void resizeMeMaybe(int newSize) {
    int entriesSize = entries.length;
    if (newSize > entriesSize) {
      int newCapacity = entriesSize + Math.max(1, entriesSize >>> 1);
      if (newCapacity < 0) {
        newCapacity = Integer.MAX_VALUE;
      }
      if (newCapacity != entriesSize) {
        resizeEntries(newCapacity);
      }
    }
  }

  /**
   * Resizes the internal entries array to the specified capacity, which may be greater or less than
   * the current capacity.
   */
  void resizeEntries(int newCapacity) {
    this.keys = Arrays.copyOf(keys, newCapacity);
    this.values = Arrays.copyOf(values, newCapacity);
    long[] entries = this.entries;
    int oldCapacity = entries.length;
    entries = Arrays.copyOf(entries, newCapacity);
    if (newCapacity > oldCapacity) {
      Arrays.fill(entries, oldCapacity, newCapacity, UNSET);
    }
    this.entries = entries;
  }

  private void resizeTable(int newCapacity) { // newCapacity always a power of two
    int[] newTable = newTable(newCapacity);
    long[] entries = this.entries;

    int mask = newTable.length - 1;
    for (int i = 0; i < size; i++) {
      long oldEntry = entries[i];
      int hash = getHash(oldEntry);
      int tableIndex = hash & mask;
      int next = newTable[tableIndex];
      newTable[tableIndex] = i;
      entries[i] = ((long) hash << 32) | (NEXT_MASK & next);
    }

    this.table = newTable;
  }

  private int indexOf(@Nullable Object key) {
    if (needsAllocArrays()) {
      return -1;
    }
    int hash = smearedHash(key);
    int next = table[hash & hashTableMask()];
    while (next != UNSET) {
      long entry = entries[next];
      if (getHash(entry) == hash && Objects.equal(key, keys[next])) {
        return next;
      }
      next = getNext(entry);
    }
    return -1;
  }

  @Override
  public boolean containsKey(@Nullable Object key) {
    return indexOf(key) != -1;
  }

  @SuppressWarnings("unchecked") // values only contains Vs
  @Override
  public V get(@Nullable Object key) {
    int index = indexOf(key);
    accessEntry(index);
    return (index == -1) ? null : (V) values[index];
  }

  @CanIgnoreReturnValue
  @Override
  public @Nullable V remove(@Nullable Object key) {
    if (needsAllocArrays()) {
      return null;
    }
    return remove(key, smearedHash(key));
  }

  private @Nullable V remove(@Nullable Object key, int hash) {
    int tableIndex = hash & hashTableMask();
    int next = table[tableIndex];
    if (next == UNSET) { // empty bucket
      return null;
    }
    int last = UNSET;
    do {
      if (getHash(entries[next]) == hash && Objects.equal(key, keys[next])) {
        @SuppressWarnings("unchecked") // values only contains Vs
        @Nullable
        V oldValue = (V) values[next];

        if (last == UNSET) {
          // we need to update the root link from table[]
          table[tableIndex] = getNext(entries[next]);
        } else {
          // we need to update the link from the chain
          entries[last] = swapNext(entries[last], getNext(entries[next]));
        }

        moveLastEntry(next);
        size--;
        modCount++;
        return oldValue;
      }
      last = next;
      next = getNext(entries[next]);
    } while (next != UNSET);
    return null;
  }

  @CanIgnoreReturnValue
  private V removeEntry(int entryIndex) {
    return remove(keys[entryIndex], getHash(entries[entryIndex]));
  }

  /**
   * Moves the last entry in the entry array into {@code dstIndex}, and nulls out its old position.
   */
  void moveLastEntry(int dstIndex) {
    int srcIndex = size() - 1;
    if (dstIndex < srcIndex) {
      // move last entry to deleted spot
      keys[dstIndex] = keys[srcIndex];
      values[dstIndex] = values[srcIndex];
      keys[srcIndex] = null;
      values[srcIndex] = null;

      // move the last entry to the removed spot, just like we moved the element
      long lastEntry = entries[srcIndex];
      entries[dstIndex] = lastEntry;
      entries[srcIndex] = UNSET;

      // also need to update whoever's "next" pointer was pointing to the last entry place
      // reusing "tableIndex" and "next"; these variables were no longer needed
      int tableIndex = getHash(lastEntry) & hashTableMask();
      int lastNext = table[tableIndex];
      if (lastNext == srcIndex) {
        // we need to update the root pointer
        table[tableIndex] = dstIndex;
      } else {
        // we need to update a pointer in an entry
        int previous;
        long entry;
        do {
          previous = lastNext;
          lastNext = getNext(entry = entries[lastNext]);
        } while (lastNext != srcIndex);
        // here, entries[previous] points to the old entry location; update it
        entries[previous] = swapNext(entry, dstIndex);
      }
    } else {
      keys[dstIndex] = null;
      values[dstIndex] = null;
      entries[dstIndex] = UNSET;
    }
  }

  int firstEntryIndex() {
    return isEmpty() ? -1 : 0;
  }

  int getSuccessor(int entryIndex) {
    return (entryIndex + 1 < size) ? entryIndex + 1 : -1;
  }

  /**
   * Updates the index an iterator is pointing to after a call to remove: returns the index of the
   * entry that should be looked at after a removal on indexRemoved, with indexBeforeRemove as the
   * index that *was* the next entry that would be looked at.
   */
  int adjustAfterRemove(int indexBeforeRemove, @SuppressWarnings("unused") int indexRemoved) {
    return indexBeforeRemove - 1;
  }

  private abstract class Itr<T> implements Iterator<T> {
    int expectedModCount = modCount;
    int currentIndex = firstEntryIndex();
    int indexToRemove = -1;

    @Override
    public boolean hasNext() {
      return currentIndex >= 0;
    }

    abstract T getOutput(int entry);

    @Override
    public T next() {
      checkForConcurrentModification();
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      indexToRemove = currentIndex;
      T result = getOutput(currentIndex);
      currentIndex = getSuccessor(currentIndex);
      return result;
    }

    @Override
    public void remove() {
      checkForConcurrentModification();
      checkRemove(indexToRemove >= 0);
      expectedModCount++;
      removeEntry(indexToRemove);
      currentIndex = adjustAfterRemove(currentIndex, indexToRemove);
      indexToRemove = -1;
    }

    private void checkForConcurrentModification() {
      if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
      }
    }
  }

  @Override
  public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
    checkNotNull(function);
    for (int i = 0; i < size; i++) {
      values[i] = function.apply((K) keys[i], (V) values[i]);
    }
  }

  private transient @MonotonicNonNull Set<K> keySetView;

  @Override
  public Set<K> keySet() {
    return (keySetView == null) ? keySetView = createKeySet() : keySetView;
  }

  Set<K> createKeySet() {
    return new KeySetView();
  }

  @WeakOuter
  class KeySetView extends Maps.KeySet<K, V> {
    KeySetView() {
      super(CompactHashMap.this);
    }

    @Override
    public Object[] toArray() {
      if (needsAllocArrays()) {
        return new Object[0];
      }
      return ObjectArrays.copyAsObjectArray(keys, 0, size);
    }

    @Override
    public <T> T[] toArray(T[] a) {
      if (needsAllocArrays()) {
        if (a.length > 0) {
          a[0] = null;
        }
        return a;
      }
      return ObjectArrays.toArrayImpl(keys, 0, size, a);
    }

    @Override
    public boolean remove(@Nullable Object o) {
      int index = indexOf(o);
      if (index == -1) {
        return false;
      } else {
        removeEntry(index);
        return true;
      }
    }

    @Override
    public Iterator<K> iterator() {
      return keySetIterator();
    }

    @Override
    public Spliterator<K> spliterator() {
      if (needsAllocArrays()) {
        return Spliterators.spliterator(new Object[0], Spliterator.DISTINCT | Spliterator.ORDERED);
      }
      return Spliterators.spliterator(keys, 0, size, Spliterator.DISTINCT | Spliterator.ORDERED);
    }

    @Override
    public void forEach(Consumer<? super K> action) {
      checkNotNull(action);
      for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) {
        action.accept((K) keys[i]);
      }
    }
  }

  Iterator<K> keySetIterator() {
    return new Itr<K>() {
      @SuppressWarnings("unchecked") // keys only contains Ks
      @Override
      K getOutput(int entry) {
        return (K) keys[entry];
      }
    };
  }

  @Override
  public void forEach(BiConsumer<? super K, ? super V> action) {
    checkNotNull(action);
    for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) {
      action.accept((K) keys[i], (V) values[i]);
    }
  }

  private transient @MonotonicNonNull Set<Entry<K, V>> entrySetView;

  @Override
  public Set<Entry<K, V>> entrySet() {
    return (entrySetView == null) ? entrySetView = createEntrySet() : entrySetView;
  }

  Set<Entry<K, V>> createEntrySet() {
    return new EntrySetView();
  }

  @WeakOuter
  class EntrySetView extends Maps.EntrySet<K, V> {
    @Override
    Map<K, V> map() {
      return CompactHashMap.this;
    }

    @Override
    public Iterator<Entry<K, V>> iterator() {
      return entrySetIterator();
    }

    @Override
    public Spliterator<Entry<K, V>> spliterator() {
      return CollectSpliterators.indexed(
          size, Spliterator.DISTINCT | Spliterator.ORDERED, MapEntry::new);
    }

    @Override
    public boolean contains(@Nullable Object o) {
      if (o instanceof Entry) {
        Entry<?, ?> entry = (Entry<?, ?>) o;
        int index = indexOf(entry.getKey());
        return index != -1 && Objects.equal(values[index], entry.getValue());
      }
      return false;
    }

    @Override
    public boolean remove(@Nullable Object o) {
      if (o instanceof Entry) {
        Entry<?, ?> entry = (Entry<?, ?>) o;
        int index = indexOf(entry.getKey());
        if (index != -1 && Objects.equal(values[index], entry.getValue())) {
          removeEntry(index);
          return true;
        }
      }
      return false;
    }
  }

  Iterator<Entry<K, V>> entrySetIterator() {
    return new Itr<Entry<K, V>>() {
      @Override
      Entry<K, V> getOutput(int entry) {
        return new MapEntry(entry);
      }
    };
  }

  final class MapEntry extends AbstractMapEntry<K, V> {
    private final @Nullable K key;

    private int lastKnownIndex;

    @SuppressWarnings("unchecked") // keys only contains Ks
    MapEntry(int index) {
      this.key = (K) keys[index];
      this.lastKnownIndex = index;
    }

    @Override
    public K getKey() {
      return key;
    }

    private void updateLastKnownIndex() {
      if (lastKnownIndex == -1
          || lastKnownIndex >= size()
          || !Objects.equal(key, keys[lastKnownIndex])) {
        lastKnownIndex = indexOf(key);
      }
    }

    @SuppressWarnings("unchecked") // values only contains Vs
    @Override
    public V getValue() {
      updateLastKnownIndex();
      return (lastKnownIndex == -1) ? null : (V) values[lastKnownIndex];
    }

    @SuppressWarnings("unchecked") // values only contains Vs
    @Override
    public V setValue(V value) {
      updateLastKnownIndex();
      if (lastKnownIndex == -1) {
        put(key, value);
        return null;
      } else {
        V old = (V) values[lastKnownIndex];
        values[lastKnownIndex] = value;
        return old;
      }
    }
  }

  @Override
  public int size() {
    return size;
  }

  @Override
  public boolean isEmpty() {
    return size == 0;
  }

  @Override
  public boolean containsValue(@Nullable Object value) {
    for (int i = 0; i < size; i++) {
      if (Objects.equal(value, values[i])) {
        return true;
      }
    }
    return false;
  }

  private transient @MonotonicNonNull Collection<V> valuesView;

  @Override
  public Collection<V> values() {
    return (valuesView == null) ? valuesView = createValues() : valuesView;
  }

  Collection<V> createValues() {
    return new ValuesView();
  }

  @WeakOuter
  class ValuesView extends Maps.Values<K, V> {
    ValuesView() {
      super(CompactHashMap.this);
    }

    @Override
    public Iterator<V> iterator() {
      return valuesIterator();
    }

    @Override
    public void forEach(Consumer<? super V> action) {
      checkNotNull(action);
      for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) {
        action.accept((V) values[i]);
      }
    }

    @Override
    public Spliterator<V> spliterator() {
      if (needsAllocArrays()) {
        return Spliterators.spliterator(new Object[0], Spliterator.ORDERED);
      }
      return Spliterators.spliterator(values, 0, size, Spliterator.ORDERED);
    }

    @Override
    public Object[] toArray() {
      if (needsAllocArrays()) {
        return new Object[0];
      }
      return ObjectArrays.copyAsObjectArray(values, 0, size);
    }

    @Override
    public <T> T[] toArray(T[] a) {
      if (needsAllocArrays()) {
        if (a.length > 0) {
          a[0] = null;
        }
        return a;
      }
      return ObjectArrays.toArrayImpl(values, 0, size, a);
    }
  }

  Iterator<V> valuesIterator() {
    return new Itr<V>() {
      @SuppressWarnings("unchecked") // values only contains Vs
      @Override
      V getOutput(int entry) {
        return (V) values[entry];
      }
    };
  }

  /**
   * Ensures that this {@code CompactHashMap} has the smallest representation in memory, given its
   * current size.
   */
  public void trimToSize() {
    if (needsAllocArrays()) {
      return;
    }
    int size = this.size;
    if (size < entries.length) {
      resizeEntries(size);
    }
    int minimumTableSize = Hashing.closedTableSize(size, LOAD_FACTOR);
    if (minimumTableSize < table.length) {
      resizeTable(minimumTableSize);
    }
  }

  @Override
  public void clear() {
    if (needsAllocArrays()) {
      return;
    }
    modCount++;
    Arrays.fill(keys, 0, size, null);
    Arrays.fill(values, 0, size, null);
    Arrays.fill(table, UNSET);
    Arrays.fill(entries, 0, size, UNSET);
    this.size = 0;
  }

  /**
   * The serial form currently mimics Android's java.util.HashMap version, e.g. see
   * http://omapzoom.org/?p=platform/libcore.git;a=blob;f=luni/src/main/java/java/util/HashMap.java
   */
  private void writeObject(ObjectOutputStream stream) throws IOException {
    stream.defaultWriteObject();
    stream.writeInt(size);
    for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) {
      stream.writeObject(keys[i]);
      stream.writeObject(values[i]);
    }
  }

  @SuppressWarnings("unchecked")
  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    stream.defaultReadObject();
    int elementCount = stream.readInt();
    if (elementCount < 0) {
      throw new InvalidObjectException("Invalid size: " + elementCount);
    }
    init(elementCount);
    for (int i = 0; i < elementCount; i++) {
      K key = (K) stream.readObject();
      V value = (V) stream.readObject();
      put(key, value);
    }
  }
}


/*
 * Copyright (C) 2009 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.concurrent.LazyInit;
import com.google.j2objc.annotations.RetainedWith;
import com.google.j2objc.annotations.Weak;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * A {@link SetMultimap} whose contents will never change, with many other important properties
 * detailed at {@link ImmutableCollection}.
 *
 *
See the Guava User Guide article on  immutable collections.
 *
 * @author Mike Ward
 * @since 2.0
 */
@GwtCompatible(serializable = true, emulated = true)
public class ImmutableSetMultimap<K, V> extends ImmutableMultimap<K, V>
    implements SetMultimap<K, V> {
  /**
   * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSetMultimap}
   * whose keys and values are the result of applying the provided mapping functions to the input
   * elements.
   *
   *
For streams with {@linkplain java.util.stream#Ordering defined encounter order}, that order
   * is preserved, but entries are grouped by key.
   *
   *
Example:
   *
   *
{@code
   * static final Multimap<Character, String> FIRST_LETTER_MULTIMAP =
   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
   *         .collect(toImmutableSetMultimap(str -> str.charAt(0), str -> str.substring(1)));
   *
   * // is equivalent to
   *
   * static final Multimap<Character, String> FIRST_LETTER_MULTIMAP =
   *     new ImmutableSetMultimap.Builder<Character, String>()
   *         .put('b', "anana")
   *         .putAll('a', "pple", "sparagus")
   *         .putAll('c', "arrot", "herry")
   *         .build();
   * }

   *
   * @since 21.0
   */
  public static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap(
      Function<? super T, ? extends K> keyFunction,
      Function<? super T, ? extends V> valueFunction) {
    checkNotNull(keyFunction, "keyFunction");
    checkNotNull(valueFunction, "valueFunction");
    return Collector.of(
        ImmutableSetMultimap::<K, V>builder,
        (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)),
        ImmutableSetMultimap.Builder::combine,
        ImmutableSetMultimap.Builder::build);
  }

  /**
   * Returns a {@code Collector} accumulating entries into an {@code ImmutableSetMultimap}. Each
   * input element is mapped to a key and a stream of values, each of which are put into the
   * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the
   * streams of values.
   *
   *
Example:
   *
   *
{@code
   * static final ImmutableSetMultimap<Character, Character> FIRST_LETTER_MULTIMAP =
   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
   *         .collect(
   *             flatteningToImmutableSetMultimap(
   *                  str -> str.charAt(0),
   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c));
   *
   * // is equivalent to
   *
   * static final ImmutableSetMultimap<Character, Character> FIRST_LETTER_MULTIMAP =
   *     ImmutableSetMultimap.<Character, Character>builder()
   *         .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'))
   *         .putAll('a', Arrays.asList('p', 'p', 'l', 'e'))
   *         .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'))
   *         .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'))
   *         .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'))
   *         .build();
   *
   * // after deduplication, the resulting multimap is equivalent to
   *
   * static final ImmutableSetMultimap<Character, Character> FIRST_LETTER_MULTIMAP =
   *     ImmutableSetMultimap.<Character, Character>builder()
   *         .putAll('b', Arrays.asList('a', 'n'))
   *         .putAll('a', Arrays.asList('p', 'l', 'e', 's', 'a', 'r', 'g', 'u'))
   *         .putAll('c', Arrays.asList('a', 'r', 'o', 't', 'h', 'e', 'y'))
   *         .build();
   * }
   * }

   *
   * @since 21.0
   */
  public static <T, K, V>
      Collector<T, ?, ImmutableSetMultimap<K, V>> flatteningToImmutableSetMultimap(
          Function<? super T, ? extends K> keyFunction,
          Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
    checkNotNull(keyFunction);
    checkNotNull(valuesFunction);
    return Collectors.collectingAndThen(
        Multimaps.flatteningToMultimap(
            input -> checkNotNull(keyFunction.apply(input)),
            input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull),
            MultimapBuilder.linkedHashKeys().linkedHashSetValues()::<K, V>build),
        ImmutableSetMultimap::copyOf);
  }

  /** Returns the empty multimap. */
  // Casting is safe because the multimap will never hold any elements.
  @SuppressWarnings("unchecked")
  public static <K, V> ImmutableSetMultimap<K, V> of() {
    return (ImmutableSetMultimap<K, V>) EmptyImmutableSetMultimap.INSTANCE;
  }

  /** Returns an immutable multimap containing a single entry. */
  public static <K, V> ImmutableSetMultimap<K, V> of(K k1, V v1) {
    ImmutableSetMultimap.Builder<K, V> builder = ImmutableSetMultimap.builder();
    builder.put(k1, v1);
    return builder.build();
  }

  /**
   * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of
   * an entry (according to {@link Object#equals}) after the first are ignored.
   */
  public static <K, V> ImmutableSetMultimap<K, V> of(K k1, V v1, K k2, V v2) {
    ImmutableSetMultimap.Builder<K, V> builder = ImmutableSetMultimap.builder();
    builder.put(k1, v1);
    builder.put(k2, v2);
    return builder.build();
  }

  /**
   * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of
   * an entry (according to {@link Object#equals}) after the first are ignored.
   */
  public static <K, V> ImmutableSetMultimap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
    ImmutableSetMultimap.Builder<K, V> builder = ImmutableSetMultimap.builder();
    builder.put(k1, v1);
    builder.put(k2, v2);
    builder.put(k3, v3);
    return builder.build();
  }

  /**
   * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of
   * an entry (according to {@link Object#equals}) after the first are ignored.
   */
  public static <K, V> ImmutableSetMultimap<K, V> of(
      K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
    ImmutableSetMultimap.Builder<K, V> builder = ImmutableSetMultimap.builder();
    builder.put(k1, v1);
    builder.put(k2, v2);
    builder.put(k3, v3);
    builder.put(k4, v4);
    return builder.build();
  }

  /**
   * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of
   * an entry (according to {@link Object#equals}) after the first are ignored.
   */
  public static <K, V> ImmutableSetMultimap<K, V> of(
      K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
    ImmutableSetMultimap.Builder<K, V> builder = ImmutableSetMultimap.builder();
    builder.put(k1, v1);
    builder.put(k2, v2);
    builder.put(k3, v3);
    builder.put(k4, v4);
    builder.put(k5, v5);
    return builder.build();
  }

  // looking for of() with > 5 entries? Use the builder instead.

  /** Returns a new {@link Builder}. */
  public static <K, V> Builder<K, V> builder() {
    return new Builder<>();
  }

  /**
   * A builder for creating immutable {@code SetMultimap} instances, especially {@code public static
   * final} multimaps ("constant multimaps"). Example:
   *
   *
{@code
   * static final Multimap<String, Integer> STRING_TO_INTEGER_MULTIMAP =
   *     new ImmutableSetMultimap.Builder<String, Integer>()
   *         .put("one", 1)
   *         .putAll("several", 1, 2, 3)
   *         .putAll("many", 1, 2, 3, 4, 5)
   *         .build();
   * }

   *
   *
Builder instances can be reused; it is safe to call {@link #build} multiple times to build
   * multiple multimaps in series. Each multimap contains the key-value mappings in the previously
   * created multimaps.
   *
   * @since 2.0
   */
  public static final class Builder<K, V> extends ImmutableMultimap.Builder<K, V> {
    /**
     * Creates a new builder. The returned builder is equivalent to the builder generated by {@link
     * ImmutableSetMultimap#builder}.
     */
    public Builder() {
      super();
    }

    @Override
    Collection<V> newMutableValueCollection() {
      return Platform.preservesInsertionOrderOnAddsSet();
    }

    /** Adds a key-value mapping to the built multimap if it is not already present. */
    @CanIgnoreReturnValue
    @Override
    public Builder<K, V> put(K key, V value) {
      super.put(key, value);
      return this;
    }

    /**
     * Adds an entry to the built multimap if it is not already present.
     *
     * @since 11.0
     */
    @CanIgnoreReturnValue
    @Override
    public Builder<K, V> put(Entry<? extends K, ? extends V> entry) {
      super.put(entry);
      return this;
    }

    /**
     * {@inheritDoc}
     *
     * @since 19.0
     */
    @CanIgnoreReturnValue
    @Beta
    @Override
    public Builder<K, V> putAll(Iterable<? extends Entry<? extends K, ? extends V>> entries) {
      super.putAll(entries);
      return this;
    }

    @CanIgnoreReturnValue
    @Override
    public Builder<K, V> putAll(K key, Iterable<? extends V> values) {
      super.putAll(key, values);
      return this;
    }

    @CanIgnoreReturnValue
    @Override
    public Builder<K, V> putAll(K key, V... values) {
      return putAll(key, Arrays.asList(values));
    }

    @CanIgnoreReturnValue
    @Override
    public Builder<K, V> putAll(Multimap<? extends K, ? extends V> multimap) {
      for (Entry<? extends K, ? extends Collection<? extends V>> entry :
          multimap.asMap().entrySet()) {
        putAll(entry.getKey(), entry.getValue());
      }
      return this;
    }

    @CanIgnoreReturnValue
    @Override
    Builder<K, V> combine(ImmutableMultimap.Builder<K, V> other) {
      super.combine(other);
      return this;
    }

    /**
     * {@inheritDoc}
     *
     * @since 8.0
     */
    @CanIgnoreReturnValue
    @Override
    public Builder<K, V> orderKeysBy(Comparator<? super K> keyComparator) {
      super.orderKeysBy(keyComparator);
      return this;
    }

    /**
     * Specifies the ordering of the generated multimap's values for each key.
     *
     *
If this method is called, the sets returned by the {@code get()} method of the generated
     * multimap and its {@link Multimap#asMap()} view are {@link ImmutableSortedSet} instances.
     * However, serialization does not preserve that property, though it does maintain the key and
     * value ordering.
     *
     * @since 8.0
     */
    // TODO: Make serialization behavior consistent.
    @CanIgnoreReturnValue
    @Override
    public Builder<K, V> orderValuesBy(Comparator<? super V> valueComparator) {
      super.orderValuesBy(valueComparator);
      return this;
    }

    /** Returns a newly-created immutable set multimap. */
    @Override
    public ImmutableSetMultimap<K, V> build() {
      Collection<Map.Entry<K, Collection<V>>> mapEntries = builderMap.entrySet();
      if (keyComparator != null) {
        mapEntries = Ordering.from(keyComparator).<K>onKeys().immutableSortedCopy(mapEntries);
      }
      return fromMapEntries(mapEntries, valueComparator);
    }
  }

  /**
   * Returns an immutable set multimap containing the same mappings as {@code multimap}. The
   * generated multimap's key and value orderings correspond to the iteration ordering of the {@code
   * multimap.asMap()} view. Repeated occurrences of an entry in the multimap after the first are
   * ignored.
   *
   *
Despite the method name, this method attempts to avoid actually copying the data when it is
   * safe to do so. The exact circumstances under which a copy will or will not be performed are
   * undocumented and subject to change.
   *
   * @throws NullPointerException if any key or value in {@code multimap} is null
   */
  public static <K, V> ImmutableSetMultimap<K, V> copyOf(
      Multimap<? extends K, ? extends V> multimap) {
    return copyOf(multimap, null);
  }

  private static <K, V> ImmutableSetMultimap<K, V> copyOf(
      Multimap<? extends K, ? extends V> multimap, Comparator<? super V> valueComparator) {
    checkNotNull(multimap); // eager for GWT
    if (multimap.isEmpty() && valueComparator == null) {
      return of();
    }

    if (multimap instanceof ImmutableSetMultimap) {
      @SuppressWarnings("unchecked") // safe since multimap is not writable
      ImmutableSetMultimap<K, V> kvMultimap = (ImmutableSetMultimap<K, V>) multimap;
      if (!kvMultimap.isPartialView()) {
        return kvMultimap;
      }
    }

    return fromMapEntries(multimap.asMap().entrySet(), valueComparator);
  }

  /**
   * Returns an immutable multimap containing the specified entries. The returned multimap iterates
   * over keys in the order they were first encountered in the input, and the values for each key
   * are iterated in the order they were encountered. If two values for the same key are {@linkplain
   * Object#equals equal}, the first value encountered is used.
   *
   * @throws NullPointerException if any key, value, or entry is null
   * @since 19.0
   */
  @Beta
  public static <K, V> ImmutableSetMultimap<K, V> copyOf(
      Iterable<? extends Entry<? extends K, ? extends V>> entries) {
    return new Builder<K, V>().putAll(entries).build();
  }

  /** Creates an ImmutableSetMultimap from an asMap.entrySet. */
  static <K, V> ImmutableSetMultimap<K, V> fromMapEntries(
      Collection<? extends Map.Entry<? extends K, ? extends Collection<? extends V>>> mapEntries,
      @Nullable Comparator<? super V> valueComparator) {
    if (mapEntries.isEmpty()) {
      return of();
    }
    ImmutableMap.Builder<K, ImmutableSet<V>> builder =
        new ImmutableMap.Builder<>(mapEntries.size());
    int size = 0;

    for (Entry<? extends K, ? extends Collection<? extends V>> entry : mapEntries) {
      K key = entry.getKey();
      Collection<? extends V> values = entry.getValue();
      ImmutableSet<V> set = valueSet(valueComparator, values);
      if (!set.isEmpty()) {
        builder.put(key, set);
        size += set.size();
      }
    }

    return new ImmutableSetMultimap<>(builder.build(), size, valueComparator);
  }

  /**
   * Returned by get() when a missing key is provided. Also holds the comparator, if any, used for
   * values.
   */
  private final transient ImmutableSet<V> emptySet;

  ImmutableSetMultimap(
      ImmutableMap<K, ImmutableSet<V>> map,
      int size,
      @Nullable Comparator<? super V> valueComparator) {
    super(map, size);
    this.emptySet = emptySet(valueComparator);
  }

  // views

  /**
   * Returns an immutable set of the values for the given key. If no mappings in the multimap have
   * the provided key, an empty immutable set is returned. The values are in the same order as the
   * parameters used to build this multimap.
   */
  @Override
  public ImmutableSet<V> get(@Nullable K key) {
    // This cast is safe as its type is known in constructor.
    ImmutableSet<V> set = (ImmutableSet<V>) map.get(key);
    return MoreObjects.firstNonNull(set, emptySet);
  }

  @LazyInit @MonotonicNonNull @RetainedWith private transient ImmutableSetMultimap<V, K> inverse;

  /**
   * {@inheritDoc}
   *
   *
Because an inverse of a set multimap cannot contain multiple pairs with the same key and
   * value, this method returns an {@code ImmutableSetMultimap} rather than the {@code
   * ImmutableMultimap} specified in the {@code ImmutableMultimap} class.
   */
  @Override
  public ImmutableSetMultimap<V, K> inverse() {
    ImmutableSetMultimap<V, K> result = inverse;
    return (result == null) ? (inverse = invert()) : result;
  }

  private ImmutableSetMultimap<V, K> invert() {
    Builder<V, K> builder = builder();
    for (Entry<K, V> entry : entries()) {
      builder.put(entry.getValue(), entry.getKey());
    }
    ImmutableSetMultimap<V, K> invertedMultimap = builder.build();
    invertedMultimap.inverse = this;
    return invertedMultimap;
  }

  /**
   * Guaranteed to throw an exception and leave the multimap unmodified.
   *
   * @throws UnsupportedOperationException always
   * @deprecated Unsupported operation.
   */
  @CanIgnoreReturnValue
  @Deprecated
  @Override
  public ImmutableSet<V> removeAll(Object key) {
    throw new UnsupportedOperationException();
  }

  /**
   * Guaranteed to throw an exception and leave the multimap unmodified.
   *
   * @throws UnsupportedOperationException always
   * @deprecated Unsupported operation.
   */
  @CanIgnoreReturnValue
  @Deprecated
  @Override
  public ImmutableSet<V> replaceValues(K key, Iterable<? extends V> values) {
    throw new UnsupportedOperationException();
  }

  private transient @MonotonicNonNull ImmutableSet<Entry<K, V>> entries;

  /**
   * Returns an immutable collection of all key-value pairs in the multimap. Its iterator traverses
   * the values for the first key, the values for the second key, and so on.
   */
  @Override
  public ImmutableSet<Entry<K, V>> entries() {
    ImmutableSet<Entry<K, V>> result = entries;
    return result == null ? (entries = new EntrySet<>(this)) : result;
  }

  private static final class EntrySet<K, V> extends ImmutableSet<Entry<K, V>> {
    @Weak private final transient ImmutableSetMultimap<K, V> multimap;

    EntrySet(ImmutableSetMultimap<K, V> multimap) {
      this.multimap = multimap;
    }

    @Override
    public boolean contains(@Nullable Object object) {
      if (object instanceof Entry) {
        Entry<?, ?> entry = (Entry<?, ?>) object;
        return multimap.containsEntry(entry.getKey(), entry.getValue());
      }
      return false;
    }

    @Override
    public int size() {
      return multimap.size();
    }

    @Override
    public UnmodifiableIterator<Entry<K, V>> iterator() {
      return multimap.entryIterator();
    }

    @Override
    boolean isPartialView() {
      return false;
    }
  }

  private static <V> ImmutableSet<V> valueSet(
      @Nullable Comparator<? super V> valueComparator, Collection<? extends V> values) {
    return (valueComparator == null)
        ? ImmutableSet.copyOf(values)
        : ImmutableSortedSet.copyOf(valueComparator, values);
  }

  private static <V> ImmutableSet<V> emptySet(@Nullable Comparator<? super V> valueComparator) {
    return (valueComparator == null)
        ? ImmutableSet.<V>of()
        : ImmutableSortedSet.<V>emptySet(valueComparator);
  }

  private static <V> ImmutableSet.Builder<V> valuesBuilder(
      @Nullable Comparator<? super V> valueComparator) {
    return (valueComparator == null)
        ? new ImmutableSet.Builder<V>()
        : new ImmutableSortedSet.Builder<V>(valueComparator);
  }

  /**
   * @serialData number of distinct keys, and then for each distinct key: the key, the number of
   *     values for that key, and the key's values
   */
  @GwtIncompatible // java.io.ObjectOutputStream
  private void writeObject(ObjectOutputStream stream) throws IOException {
    stream.defaultWriteObject();
    stream.writeObject(valueComparator());
    Serialization.writeMultimap(this, stream);
  }

  @Nullable
  Comparator<? super V> valueComparator() {
    return emptySet instanceof ImmutableSortedSet
        ? ((ImmutableSortedSet<V>) emptySet).comparator()
        : null;
  }

  @GwtIncompatible // java serialization
  private static final class SetFieldSettersHolder {
    static final Serialization.FieldSetter<ImmutableSetMultimap> EMPTY_SET_FIELD_SETTER =
        Serialization.getFieldSetter(ImmutableSetMultimap.class, "emptySet");
  }

  @GwtIncompatible // java.io.ObjectInputStream
  // Serialization type safety is at the caller's mercy.
  @SuppressWarnings("unchecked")
  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    stream.defaultReadObject();
    Comparator<Object> valueComparator = (Comparator<Object>) stream.readObject();
    int keyCount = stream.readInt();
    if (keyCount < 0) {
      throw new InvalidObjectException("Invalid key count " + keyCount);
    }
    ImmutableMap.Builder<Object, ImmutableSet<Object>> builder = ImmutableMap.builder();
    int tmpSize = 0;

    for (int i = 0; i < keyCount; i++) {
      Object key = stream.readObject();
      int valueCount = stream.readInt();
      if (valueCount <= 0) {
        throw new InvalidObjectException("Invalid value count " + valueCount);
      }

      ImmutableSet.Builder<Object> valuesBuilder = valuesBuilder(valueComparator);
      for (int j = 0; j < valueCount; j++) {
        valuesBuilder.add(stream.readObject());
      }
      ImmutableSet<Object> valueSet = valuesBuilder.build();
      if (valueSet.size() != valueCount) {
        throw new InvalidObjectException("Duplicate key-value pairs exist for key " + key);
      }
      builder.put(key, valueSet);
      tmpSize += valueCount;
    }

    ImmutableMap<Object, ImmutableSet<Object>> tmpMap;
    try {
      tmpMap = builder.build();
    } catch (IllegalArgumentException e) {
      throw (InvalidObjectException) new InvalidObjectException(e.getMessage()).initCause(e);
    }

    FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap);
    FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize);
    SetFieldSettersHolder.EMPTY_SET_FIELD_SETTER.set(this, emptySet(valueComparator));
  }

  @GwtIncompatible // not needed in emulated source.
  private static final long serialVersionUID = 0;
}