package edu.berkeley.nlp.lm.collections;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Semaphore;

/**
 * Maintains a two-way map between a set of objects and contiguous integers from
 * 0 to the number of objects.
 * 
 * @author Dan Klein
 * @author Adam Pauls
 */
public class Indexer<E extends Comparable<E>> implements Serializable
{
	private static final long serialVersionUID = -8769544079136550516L;

	protected ArrayList<E> objects;

	protected TIntMap<E> indexes;

	protected boolean locked = false;

	private Semaphore sem;

	public void lock() {
		this.locked = true;
	}

	public E getObject(final int index) {
		return objects.get(index);
	}

	public boolean add(final E elem) {
		final int oldSize = size();
		return getIndex(elem) >= oldSize;
	}

	/**
	 * Returns the number of objects indexed.
	 */
	public int size() {
		return objects.size();
	}

	/**
	 * Returns the index of the given object, or -1 if the object is not present
	 * in the indexer.
	 * 
	 * @param o
	 * @return
	 */
	public int indexOf(final E o) {
		final int index = indexes.get(o, -1);

		return index;
	}

	/**
	 * Return the index of the element If doesn't exist, add it.
	 */
	public int getIndex(final E e) {
		if (e == null) return -1;

		if (sem != null) sem.acquireUninterruptibly();
		int index = indexes.get(e, -1);
		if (index < 0) {
			if (locked) throw new RuntimeException("Attempt to add to locked indexer");
			index = size();
			objects.add(e);
			assert size() >= 0 : "Too many objects in indexer";
			indexes.put(e, index);
		}
		if (sem != null) sem.release();
		return index;
	}

	public Indexer(final boolean sync) {
		objects = new ArrayList<E>();
		indexes = new TIntMap<E>();
		this.sem = sync ? new Semaphore(1) : null;
	}

	public Indexer() {
		this(false);
	}

	public Indexer(final Collection<? extends E> c) {
		this();
		for (final E a : c)
			getIndex(a);
	}

	/**
	 * Save some space my compacting underlying maps and lists.
	 */
	public void trim() {
		objects.trimToSize();
		//		indexes.switchToSortedList();
	}

	public Iterable<E> getObjects() {
		return Collections.unmodifiableList(objects);
	}

}
