// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.core.filter.common;

import java.util.BitSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

import org.openstreetmap.osmosis.core.util.LongAsInt;


/**
 * Implements the IdTracker interface using an internal JDK BitSet. The current
 * implementation only supports 31 bit numbers, but will be enhanced if and when
 * required.
 * 
 * @author Brett Henderson
 */
public class BitSetIdTracker implements IdTracker {
	/**
	 * The positive ids are stored within a bitset which cannot hold negative
	 * values. It is not private to allow the IdIterator to access it
	 * efficiently.
	 */
	/* package */ BitSet positiveSet;
	/**
	 * The negative ids cannot be stored in the main bitset. They are stored in
	 * a list implementation because the number of negative values is expected
	 * to be small. It is not private to allow the IdIterator to access it
	 * efficiently.
	 */
	/* package */ ListIdTracker negativeSet;
	
	
	/**
	 * Creates a new instance.
	 */
	public BitSetIdTracker() {
		positiveSet = new BitSet();
		negativeSet = new ListIdTracker();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void set(long id) {
		int intId;
		
		intId = LongAsInt.longToInt(id);
		
		if (intId >= 0) {
			positiveSet.set(intId);
		} else {
			negativeSet.set(intId);
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public boolean get(long id) {
		int intId;
		boolean result;
		
		intId = LongAsInt.longToInt(id);
		
		if (intId >= 0) {
			result = positiveSet.get(intId);
		} else {
			result = negativeSet.get(intId);
		}
		
		return result;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Iterator<Long> iterator() {
		return new IdIterator();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setAll(IdTracker idTracker) {
		for (Long id : idTracker) {
			set(id);
		}
	}
	
	
	/**
	 * The iterator implementation for providing access to the list of ids.
	 * 
	 * @author Brett Henderson
	 */
	private class IdIterator implements Iterator<Long> {
		
		/**
		 * Tracks whether we're currently reading positive or negative bitsets.
		 */
		private boolean readingPositive;
		private long nextId;
		private boolean nextIdAvailable;
		private Iterator<Long> negativeIterator;
		/**
		 * The current bit offset in the positive bitset.
		 */
		private int positiveOffset;
		
		
		/**
		 * Creates a new instance.
		 */
		IdIterator() {
			readingPositive = false;
			nextIdAvailable = false;
			
			positiveOffset = 0;
		}
		
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean hasNext() {
			if (!nextIdAvailable) {
				if (!readingPositive) {
					// Create a negative set iterator if one doesn't already exist.
					if (negativeIterator == null) {
						negativeIterator = negativeSet.iterator();
					}
					
					// Get data from the negative iterator if available, if not
					// available switch to positive reading.
					if (negativeIterator.hasNext()) {
						nextId = negativeIterator.next();
						nextIdAvailable = true;
					} else {
						negativeIterator = null;
						readingPositive = true;
					}
				}
				
				if (readingPositive) {
					int nextBitOffset;
					
					nextBitOffset = positiveSet.nextSetBit(positiveOffset);
					
					if (nextBitOffset >= 0) {
						nextId = nextBitOffset;
						nextIdAvailable = true;
						positiveOffset = nextBitOffset + 1;
					}
				}
			}
			
			return nextIdAvailable;
		}
		
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public Long next() {
			if (!hasNext()) {
				throw new NoSuchElementException();
			}
			
			nextIdAvailable = false;
			
			return nextId;
		}
		
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
}
