/*
 * HPPC
 *
 * Copyright (C) 2010-2024 Carrot Search s.c. and contributors
 * All rights reserved.
 *
 * Refer to the full license file "LICENSE.txt":
 * https://github.com/carrotsearch/hppc/blob/master/LICENSE.txt
 */
package com.carrotsearch.hppc;

import java.util.concurrent.atomic.AtomicInteger;

public final class HashContainers {
	/**
	 * Maximum array size for hash containers (power-of-two and still allocable in
	 * Java, not a negative int).
	 */
	public static final int MAX_HASH_ARRAY_LENGTH = 0x80000000 >>> 1;

	/** Minimum hash buffer size. */
	public static final int MIN_HASH_ARRAY_LENGTH = 4;

	/** Default load factor. */
	public static final float DEFAULT_LOAD_FACTOR = 0.75f;

	/** Minimal sane load factor (99 empty slots per 100). */
	public static final float MIN_LOAD_FACTOR = 1 / 100.0f;

	/** Maximum sane load factor (1 empty slot per 100). */
	public static final float MAX_LOAD_FACTOR = 99 / 100.0f;

	private static final AtomicInteger ITERATION_SEED = new AtomicInteger();

	/** */
	static void checkLoadFactor(double loadFactor, double minAllowedInclusive, double maxAllowedInclusive) {
		if (loadFactor < minAllowedInclusive || loadFactor > maxAllowedInclusive) {
			throw new BufferAllocationException("The load factor should be in range [%.2f, %.2f]: %f",
					minAllowedInclusive, maxAllowedInclusive, loadFactor);
		}
	}

	/** */
	static boolean checkPowerOfTwo(int arraySize) {
		// These are internals, we can just assert without retrying.
		assert arraySize > 1;
		assert BitUtil.nextHighestPowerOfTwo(arraySize) == arraySize;
		return true;
	}

	/** */
	static int expandAtCount(int arraySize, double loadFactor) {
		assert checkPowerOfTwo(arraySize);
		// Take care of hash container invariant (there has to be at least one empty
		// slot to ensure
		// the lookup loop finds either the element or an empty slot).
		return Math.min(arraySize - 1, (int) Math.ceil(arraySize * loadFactor));
	}

	/** Computes a hash iteration order increment based on the provided seed. */
	static int iterationIncrement(int seed) {
		return 29 + ((seed & 7) << 1); // Small odd integer.
	}

	/**
	 * Compute and return the maximum number of elements (inclusive) that can be
	 * stored in a hash container for a given load factor.
	 */
	public static int maxElements(double loadFactor) {
		checkLoadFactor(loadFactor, 0, 1);
		return expandAtCount(MAX_HASH_ARRAY_LENGTH, loadFactor) - 1;
	}

	/** */
	static int minBufferSize(int elements, double loadFactor) {
		if (elements < 0) {
			throw new IllegalArgumentException("Number of elements must be >= 0: " + elements);
		}

		long length = (long) Math.ceil(elements / loadFactor);
		if (length == elements) {
			length++;
		}
		length = Math.max(MIN_HASH_ARRAY_LENGTH, BitUtil.nextHighestPowerOfTwo(length));

		if (length > MAX_HASH_ARRAY_LENGTH) {
			throw new BufferAllocationException(
					"Maximum array size exceeded for this load factor (elements: %d, load factor: %f)", elements,
					loadFactor);
		}

		return (int) length;
	}

	/** */
	static int nextBufferSize(int arraySize, int elements, double loadFactor) {
		assert checkPowerOfTwo(arraySize);
		if (arraySize == MAX_HASH_ARRAY_LENGTH) {
			throw new BufferAllocationException(
					"Maximum array size exceeded for this load factor (elements: %d, load factor: %f)", elements,
					loadFactor);
		}

		return (int) arraySize << 1;
	}

	/**
	 * Provides the next hash iteration order seed. It is simply an incrementing
	 * atomic counter.
	 */
	static int nextIterationSeed() {
		return ITERATION_SEED.incrementAndGet();
	}
}
