package common.util;

import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import common.collect.BiMap;
import common.collect.HashBiMap;

public class Mapping<V> implements Iterable<V> {
	private final Map<String, V> mapping = HashBiMap.<String, V>create();
	private final IdMap<V> idMap = new IdMap<V>();
	private final Map<V, String> nameMap;
	private final String defaultKey;
	
	private V defaultValue;
	private boolean registered;

	public Mapping(String def) {
		this.nameMap = ((BiMap)this.mapping).inverse();
		this.defaultKey = def;
	}

	public Mapping() {
		this(null);
	}

	public void register(int id, String key, V value) {
		if(this.registered)
			throw new IllegalStateException("Es können keine neuen Werte registriert werden");
		if(key == null)
			throw new NullPointerException("Schlüssel ist null");
		if(value == null)
			throw new NullPointerException("Wert ist null");
		if(this.mapping.containsKey(key))
			throw new IllegalArgumentException(
					"Schlüssel " + key + " ist bereits mit ID " + this.idMap.get(this.mapping.get(key)) + " registriert");
		if(this.idMap.getByValue(id) != null)
			throw new IllegalArgumentException("ID " + id + " ist bereits mit Name " + this.nameMap.get(this.idMap.getByValue(id)) + " registriert");
		this.idMap.put(value, id);
		this.mapping.put(key, value);
		if(key.equals(this.defaultKey))
			this.defaultValue = value;
	}

	public void finish() {
		if(this.defaultKey != null && this.defaultValue == null)
			throw new NullPointerException("Wert zu Standard-Schlüssel " + this.defaultKey + " ist nicht vorhanden");
		this.registered = true;
	}

	public Set<String> getKeys() {
		return Collections.<String>unmodifiableSet(this.mapping.keySet());
	}

	public V byName(String name) {
		V v = this.mapping.get(name);
		return v == null ? this.defaultValue : v;
	}

	public V byNameExact(String name) {
		return this.mapping.get(name);
	}

	public String getName(V value) {
		return this.nameMap.get(value);
	}

	public V byId(int id) {
		V v = this.idMap.getByValue(id);
		return v == null ? this.defaultValue : v;
	}

	public V byIdExact(int id) {
		return this.idMap.getByValue(id);
	}

	public int getId(V value) {
		return this.idMap.get(value);
	}

	public boolean has(String key) {
		return this.mapping.containsKey(key);
	}

	public Iterator<V> iterator() {
		return this.idMap.iterator();
	}
}
