// 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.Iterator;
import java.util.List;
import java.util.NoSuchElementException;


/**
 * Implements the IdTracker interface using a combination of BitSet and ListId trackers. It breaks
 * the overall address space into segments and automatically switches between implementations for each
 * chunk depending on which is more efficient.
 * 
 * @author Brett Henderson
 */
public class DynamicIdTracker implements IdTracker {
	/**
	 * Defines the number of ids managed by a single segment.
	 */
	/* package */ static final int SEGMENT_SIZE = 1024;
	
	private List<DynamicIdTrackerSegment> segments;
	
	
	/**
	 * Creates a new instance.
	 */
	public DynamicIdTracker() {
		segments = new ArrayList<DynamicIdTrackerSegment>();
	}
	
	
	private int calculateOffset(long id) {
		int offset;
		
		// A long modulo an integer is an integer.
		offset = (int) (id % SEGMENT_SIZE);
		
		// If the number is negative, we need to shift the number relative to the base of the
		// segment.
		if (offset < 0) {
			offset = SEGMENT_SIZE + offset;
		}
		
		return offset;
	}
	
	
	private DynamicIdTrackerSegment getSegment(long base, boolean createIfMissing) {
		int intervalBegin;
		int intervalEnd;
		DynamicIdTrackerSegment segment;
		
		segment = null;
		
		// Perform a binary search splitting the list in half each time until
		// the requested id is confirmed as existing or not.
		intervalBegin = 0;
		intervalEnd = segments.size();
		for (boolean searchComplete = false; !searchComplete;) {
			int intervalSize;
			long currentBase;
			DynamicIdTrackerSegment currentSegment;
			
			// Calculate the interval size.
			intervalSize = intervalEnd - intervalBegin;
			
			// If no elements exist, we have no search to perform. If elements do exist then divide
			// and conquer while the size is large, then commence linear search once the size is
			// small.
			if (intervalSize == 0) {
				if (createIfMissing) {
					segment = new DynamicIdTrackerSegment(base);
					segments.add(intervalBegin, segment);
				}
				
				searchComplete = true;
				
			} else if (intervalSize >= 2) {
				int intervalMid;
				
				// Split the interval in two.
				intervalMid = intervalSize / 2 + intervalBegin;
				
				// Check whether the midpoint id is above or below the id
				// required.
				currentSegment = segments.get(intervalMid);
				currentBase = currentSegment.getBase();
				if (currentBase == base) {
					segment = currentSegment;
					searchComplete = true;
				} else if (currentBase < base) {
					intervalBegin = intervalMid + 1;
				} else {
					intervalEnd = intervalMid;
				}
				
			} else {
				// Iterate through the entire interval.
				for (; intervalBegin < intervalEnd; intervalBegin++) {
					
					// Check if the current offset contains the id required.
					currentSegment = segments.get(intervalBegin);
					currentBase = currentSegment.getBase();
					
					if (currentBase == base) {
						segment = currentSegment;
						break;
						
					} else if (currentBase > base) {
						// The requested segment should exist prior to the current segment.
						if (createIfMissing) {
							segment = new DynamicIdTrackerSegment(base);
							segments.add(intervalBegin, segment);
						}
						break;
					}
				}
				
				// The requested base is at the end of this interval where intervalBegin is currently pointing.
				if (segment == null && createIfMissing) {
					segment = new DynamicIdTrackerSegment(base);
					segments.add(intervalBegin, segment);
				}
				
				searchComplete = true;
			}
		}
		
		return segment;
	}
	

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean get(long id) {
		int offset;
		long base;
		DynamicIdTrackerSegment segment;
		
		offset = calculateOffset(id);
		base = id - offset;
		segment = getSegment(base, false);
		
		if (segment != null) {
			return segment.get(offset);
		} else {
			return false;
		}
	}

	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void set(long id) {
		int offset;
		long base;
		DynamicIdTrackerSegment segment;
		
		offset = calculateOffset(id);
		base = id - offset;
		segment = getSegment(base, true);
		
		segment.set(offset);
	}

	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setAll(IdTracker idTracker) {
		for (Long id : idTracker) {
			set(id);
		}
	}

	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Iterator<Long> iterator() {
		return new SegmentIdIterator(segments.iterator());
	}
	
	
	private static class SegmentIdIterator implements Iterator<Long> {
		private Iterator<DynamicIdTrackerSegment> segments;
		private Iterator<Long> currentSegmentIds;
		private long currentSegmentBase;
		
		
		/**
		 * Creates a new instance.
		 * 
		 * @param segments
		 *            The segments to iterate over.
		 */
		SegmentIdIterator(Iterator<DynamicIdTrackerSegment> segments) {
			this.segments = segments;
		}


		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean hasNext() {
			for (;;) {
				if (currentSegmentIds == null) {
					if (segments.hasNext()) {
						DynamicIdTrackerSegment segment = segments.next();
						
						currentSegmentIds = segment.iterator();
						currentSegmentBase = segment.getBase();
					} else {
						return false;
					}
				}
				
				if (currentSegmentIds.hasNext()) {
					return true;
				} else {
					currentSegmentIds = null;
				}
			}
		}


		/**
		 * {@inheritDoc}
		 */
		@Override
		public Long next() {
			if (hasNext()) {
				return currentSegmentIds.next() + currentSegmentBase;
				
			} else {
				throw new NoSuchElementException();
			}
		}


		/**
		 * {@inheritDoc}
		 */
		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
}
