/*******************************************************************************
 * Copyright (c) 2000, 2011 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.jdt.ui.leaktest.reftracker;

import java.util.Arrays;

/**
 *
 */
public class IdentityHashSet {

	private Object[] fObjects;
	private int fThreshold;
	private int fSize;

	public IdentityHashSet(int initialSize) {
		fObjects= new Object[initialSize];
		fThreshold= getThreshold(initialSize);
		fSize= 0;
	}

	private static int getThreshold(int initialSize) {
		return (initialSize * 5) / 6;
	}

	public boolean add(Object o) {
		if (o == null) {
			throw new IllegalArgumentException("Can not add null"); //$NON-NLS-1$
		}
		int insertionIndex= getInsertionIndex(o, fObjects);
		if (insertionIndex == -1) {
			return false; // already in set
		}
		insertElement(o, insertionIndex);
		return true;
	}

	private void insertElement(Object o, int index) {
		fObjects[index]= o;
		fSize++;
		if (fSize > fThreshold) {
			increaseSize();
		}
	}

	private static int getInsertionIndex(Object elem, Object[] elements) {
		int hash= getHash(elem, elements.length);
		Object entry= elements[hash];
		while (entry != null && entry != elem) {
			hash++;
			if (hash == elements.length) {
				hash= 0; // wrap
			}
			entry= elements[hash];
		}
		if (entry == null) {
			return hash;
		}
		return -1; // already in set
	}


	private void increaseSize() {
		int newSize= fObjects.length * 2;
		Object[] newArray= new Object[newSize];
		for (int i= 0; i < fObjects.length; i++) {
			Object curr= fObjects[i];
			if (curr != null) {
				int insertionIndex= getInsertionIndex(curr, newArray);
				newArray[insertionIndex]= curr;
				fObjects[i]= null; // avoid unnecessary references
			}
		}
		fObjects= newArray;
		fThreshold= getThreshold(newSize);
	}

	public boolean contains(Object o) {
		return getInsertionIndex(o, fObjects) == -1;
	}

	public int size() {
		return fSize;
	}

	public void clear() {
		Arrays.fill(fObjects, null);
		fSize= 0;
	}

    /**
     * Return index for Object x.
     * @param x the object
     * @param length the length of the hashtable
     * @return returns the hash
     */
    private static int getHash(Object x, int length) {
        return (System.identityHashCode(x) & 0x7FFFFFFF) % length;
    }
}
