/*******************************************************************************
 * Copyright (c) 2004, 2012 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:
 *     Andrew Niefer (IBM Corporation) - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.parser.util;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @noextend This class is not intended to be subclassed by clients.
 */
public class ObjectMap extends ObjectTable<Object> {
	/**
	 * An empty immutable {@code ObjectMap}.
	 */
	public static final ObjectMap EMPTY_MAP = new ObjectMap(0) {
		@Override
		public Object clone() {
			return this;
		}

		@Override
		public List<Object> toList() {
			return Collections.emptyList();
		}

		@Override
		public Object put(Object key, Object value) {
			throw new UnsupportedOperationException();
		}
	};

	private Object[] valueTable;

	public ObjectMap(int initialSize) {
		super(initialSize);
		valueTable = new Object[capacity()];
	}

	@Override
	public Object clone() {
		ObjectMap newMap = (ObjectMap) super.clone();
		newMap.valueTable = new Object[capacity()];
		System.arraycopy(valueTable, 0, newMap.valueTable, 0, valueTable.length);
		return newMap;
	}

	@Override
	final public void clear() {
		super.clear();
		Arrays.fill(valueTable, null);
	}

	@Override
	protected void resize(int size) {
		Object[] oldValueTable = valueTable;
		valueTable = new Object[size];
		System.arraycopy(oldValueTable, 0, valueTable, 0, oldValueTable.length);
		super.resize(size);
	}

	public Object put(Object key, Object value) {
		int i = add(key);
		Object oldvalue = valueTable[i];
		valueTable[i] = value;
		return oldvalue;
	}

	final public Object get(Object key) {
		int i = lookup(key);
		if (i >= 0)
			return valueTable[i];
		return null;
	}

	final public Object getAt(int i) {
		if (i < 0 || i > currEntry)
			return null;

		return valueTable[i];
	}

	final public Object remove(Object key) {
		if (key == null)
			return null;
		int i = lookup(key);
		if (i < 0)
			return null;

		Object value = valueTable[i];
		removeEntry(i);

		return value;
	}

	@Override
	final protected void removeEntry(int i) {
		// Remove the entry from the valueTable, shifting everything over if necessary
		if (i < currEntry)
			System.arraycopy(valueTable, i + 1, valueTable, i, currEntry - i);
		valueTable[currEntry] = null;

		// Make sure you remove the value before calling super where currEntry will change
		super.removeEntry(i);
	}

	@Override
	protected int partition(Comparator<Object> c, int p, int r) {
		Object x = keyTable[p];
		Object temp = null;
		int i = p;
		int j = r;

		while (true) {
			while (c.compare(keyTable[j], x) > 0) {
				j--;
			}
			if (i < j) {
				while (c.compare(keyTable[i], x) < 0) {
					i++;
				}
			}

			if (i < j) {
				temp = keyTable[j];
				keyTable[j] = keyTable[i];
				keyTable[i] = temp;

				temp = valueTable[j];
				valueTable[j] = valueTable[i];
				valueTable[i] = temp;
			} else {
				return j;
			}
		}
	}

	public Object[] valueArray() {
		Object[] vals = new Object[size()];
		System.arraycopy(valueTable, 0, vals, 0, vals.length);
		return vals;
	}

	public boolean isEquivalent(ObjectMap other, IObjectMatcher matcher) {
		if (!super.isEquivalent(other, matcher)) {
			return false;
		}
		for (int i = 0; i < keyTable.length; i++) {
			Object val1 = valueTable[i];
			Object val2 = other.valueTable[i];
			if (val1 != val2 && !matcher.isEquivalent(val1, val2)) {
				return false;
			}
		}
		return true;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("{"); //$NON-NLS-1$
		for (int i = 0; i < size(); i++) {
			Object key = keyAt(i);
			if (key != null) {
				if (sb.length() > 1) {
					sb.append(", "); //$NON-NLS-1$
				}
				Object value = valueTable[i];
				sb.append(String.valueOf(key));
				sb.append(": "); //$NON-NLS-1$
				sb.append(String.valueOf(value));
			}
		}
		sb.append("}"); //$NON-NLS-1$
		return sb.toString();
	}
}
