package com.inivation.jfxaer;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Utility class to create a map where only the key type is defined,
 * while the value type is left flexible. While a normal Map is &lt;K, V&gt;,
 * this TypedMap only requires &lt;K&gt;, while V can be of different types,
 * and is stored/retrieved correctly by specifying the wanted type in
 * addition to the key.
 *
 * @author llongi
 *
 * @param <K>
 *            the key type.
 */
public class TypedMap<K> {
	private final Map<PairRO<K, Class<?>>, Object> keyAndTypeToObjectMap = new HashMap<>();

	public boolean isEmpty() {
		return keyAndTypeToObjectMap.isEmpty();
	}

	public int size() {
		return keyAndTypeToObjectMap.size();
	}

	public void clear() {
		keyAndTypeToObjectMap.clear();
	}

	public boolean containsKey(final K key, final Class<?> valueType) {
		return keyAndTypeToObjectMap.containsKey(PairRO.of(key, valueType));
	}

	public boolean containsValue(final Object value) {
		return keyAndTypeToObjectMap.containsValue(value);
	}

	public <V> V put(final K key, final Class<V> valueType, final V value) {
		return valueType.cast(keyAndTypeToObjectMap.put(PairRO.of(key, valueType), valueType.cast(value)));
	}

	public <V> V putIfAbsent(final K key, final Class<V> valueType, final V value) {
		return valueType.cast(keyAndTypeToObjectMap.putIfAbsent(PairRO.of(key, valueType), valueType.cast(value)));
	}

	public <V> V get(final K key, final Class<V> valueType) {
		return valueType.cast(keyAndTypeToObjectMap.get(PairRO.of(key, valueType)));
	}

	public <V> V remove(final K key, final Class<V> valueType) {
		return valueType.cast(keyAndTypeToObjectMap.remove(PairRO.of(key, valueType)));
	}

	public <V> V replace(final K key, final Class<V> valueType, final V value) {
		return valueType.cast(keyAndTypeToObjectMap.replace(PairRO.of(key, valueType), valueType.cast(value)));
	}

	public Set<Entry<PairRO<K, Class<?>>, Object>> entrySet() {
		return keyAndTypeToObjectMap.entrySet();
	}

	public Set<PairRO<K, Class<?>>> keySet() {
		return keyAndTypeToObjectMap.keySet();
	}

	public Collection<Object> values() {
		return keyAndTypeToObjectMap.values();
	}

	public <V> Collection<V> values(final Class<V> valueType) {
		final Collection<V> valueCollection = new ArrayList<>();

		for (final Object obj : values()) {
			try {
				final V value = valueType.cast(obj);
				valueCollection.add(value);
			}
			catch (final ClassCastException e) {
				// Ignore this one, it's expected for the values that are not
				// the ones corresponding to valueType's type.
			}
		}

		return valueCollection;
	}
}
