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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

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


/**
 * Implements the IdTracker interface using a list of ids. The current
 * implementation only supports 31 bit numbers, but will be enhanced if and when
 * required.
 * 
 * @author Brett Henderson
 */
public class ListIdTracker implements IdTracker {
	/**
	 * The internal list size is multiplied by this factor when more space is
	 * required.
	 */
	private static final double LIST_SIZE_EXTENSION_FACTOR = 1.5;
	
	
	/**
	 * This is the main list of id values. It is allocated in chunks and the
	 * maximum valid offset is defined by idOffset.
	 */
	/* package */ int[] idList;
	/**
	 * Flags where the maximum written id offset occurs in the list. If new
	 * values are added and the list is full, new space must be allocated.
	 */
	/* package */ int idOffset;
	private int maxIdAdded;
	private boolean sorted;
	
	
	/**
	 * Creates a new instance.
	 */
	public ListIdTracker() {
		// This is being initialised with a very small array size because this class is now also
		// used within the DynamicIdTracker which relies on the ListIdTracker being efficient for
		// small amounts of data.
		// When storing large amounts of data, the overhead of extending the array more often in the
		// early stages is relatively small compared to the overall processing. For small amounts of
		// data, the size efficiency is most important.
		idList = new int[1];
		idOffset = 0;
		maxIdAdded = Integer.MIN_VALUE;
		sorted = true;
	}
	
	
	/**
	 * Increases the size of the id list to make space for new ids.
	 */
	private void extendIdList() {
		int[] newIdList;
		int newListLength;
		
		newListLength = (int) (idList.length * LIST_SIZE_EXTENSION_FACTOR);
		if (newListLength == idList.length) {
			newListLength++;
		}
		
		newIdList = new int[newListLength];
		
		System.arraycopy(idList, 0, newIdList, 0, idList.length);
		
		idList = newIdList;
	}
	
	
	/**
	 * If the list is unsorted, this method will re-order the contents.
	 */
	private void ensureListIsSorted() {
		if (!sorted) {
			List<Integer> tmpList;
			int newIdOffset;
			
			tmpList = new ArrayList<Integer>(idOffset);
			
			for (int i = 0; i < idOffset; i++) {
				tmpList.add(Integer.valueOf(idList[i]));
			}
			
			Collections.sort(tmpList);
			
			newIdOffset = 0;
			for (int i = 0; i < idOffset; i++) {
				int nextValue;
				
				nextValue = tmpList.get(i).intValue();
				
				if (newIdOffset <= 0 || nextValue > idList[newIdOffset - 1]) {
					idList[newIdOffset++] = nextValue;
				}
			}
			idOffset = newIdOffset;
			
			sorted = true;
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void set(long id) {
		int integerId;
		
		integerId = LongAsInt.longToInt(id);
		
		// Increase the id list size if it is full.
		if (idOffset >= idList.length) {
			extendIdList();
		}
		
		idList[idOffset++] = integerId;
		
		// If ids are added out of order, the list will have to be sorted before
		// it can be searched using a binary search algorithm.
		if (integerId < maxIdAdded) {
			sorted = false;
		} else {
			maxIdAdded = integerId;
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public boolean get(long id) {
		int integerId;
		int intervalBegin;
		int intervalEnd;
		boolean idFound;
		
		integerId = LongAsInt.longToInt(id);
		
		// If the list is not sorted, it must be sorted prior to a search being
		// performed.
		ensureListIsSorted();
		
		// Perform a binary search splitting the list in half each time until
		// the requested id is confirmed as existing or not.
		intervalBegin = 0;
		intervalEnd = idOffset;
		idFound = false;
		for (boolean searchComplete = false; !searchComplete;) {
			int intervalSize;
			
			// Calculate the interval size.
			intervalSize = intervalEnd - intervalBegin;
			
			// Divide and conquer if the size is large, otherwise commence
			// linear search.
			if (intervalSize >= 2) {
				int intervalMid;
				int currentId;
				
				// Split the interval in two.
				intervalMid = intervalSize / 2 + intervalBegin;
				
				// Check whether the midpoint id is above or below the id
				// required.
				currentId = idList[intervalMid];
				if (currentId == integerId) {
					idFound = true;
					searchComplete = true;
				} else if (currentId < integerId) {
					intervalBegin = intervalMid + 1;
				} else {
					intervalEnd = intervalMid;
				}
				
			} else {
				// Iterate through the entire interval.
				for (int currentOffset = intervalBegin; currentOffset < intervalEnd; currentOffset++) {
					int currentId;
					
					// Check if the current offset contains the id required.
					currentId = idList[currentOffset];
					
					if (currentId == integerId) {
						idFound = true;
						break;
					}
				}
				
				searchComplete = true;
			}
		}
		
		return idFound;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Iterator<Long> iterator() {
		// If the list is not sorted, it must be sorted prior to data being
		// returned.
		ensureListIsSorted();
		
		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> {
		
		private int iteratorOffset;
		
		
		/**
		 * Creates a new instance.
		 */
		IdIterator() {
			iteratorOffset = 0;
		}
		
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean hasNext() {
			return (iteratorOffset < idOffset);
		}
		
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public Long next() {
			if (!hasNext()) {
				throw new NoSuchElementException();
			}
			
			return (long) idList[iteratorOffset++];
		}
		
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
		
	}
}
