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

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.NoSuchElementException;

import org.openstreetmap.osmosis.core.lifecycle.ReleasableIterator;


/**
 * This iterator examines a list of sorted input sources and merges them into a
 * single sorted list.
 * 
 * @param <DataType>
 *            The object type to be sorted.
 * @author Brett Henderson
 */
public class MergingIterator<DataType> implements ReleasableIterator<DataType> {
	private List<ReleasableIterator<DataType>> sources;
	private Comparator<DataType> comparator;
	private List<DataType> sourceData;
	
	
	/**
	 * Creates a new instance.
	 * 
	 * @param sources
	 *            The list of data sources.
	 * @param comparator
	 *            The comparator to be used for sorting.
	 */
	public MergingIterator(List<ReleasableIterator<DataType>> sources, Comparator<DataType> comparator) {
		this.sources = new ArrayList<ReleasableIterator<DataType>>(sources);
		this.comparator = comparator;
	}
	
	
	/**
	 * Primes the sorting collections.
	 */
	private void initialize() {
		if (sourceData == null) {
			// Get the first entity from each source.  Delete any empty sources.
			sourceData = new ArrayList<DataType>(sources.size());
			for (int sourceIndex = 0; sourceIndex < sources.size();) {
				ReleasableIterator<DataType> source;
				
				source = sources.get(sourceIndex);
				
				if (source.hasNext()) {
					sourceData.add(source.next());
					sourceIndex++;
				} else {
					sources.remove(sourceIndex).close();
				}
			}
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public boolean hasNext() {
		initialize();
		
		return sourceData.size() > 0;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public DataType next() {
		DataType dataMinimum;
		int indexMinimum;
		ReleasableIterator<DataType> source;
		
		initialize();
		
		if (!hasNext()) {
			throw new NoSuchElementException();
		}
		
		dataMinimum = sourceData.get(0);
		indexMinimum = 0;
		
		// Find the minimum entity.
		for (int indexCurrent = 1; indexCurrent < sources.size(); indexCurrent++) {
			DataType dataCurrent = sourceData.get(indexCurrent);
			
			// Check if the current data entity is less than the existing minimum.
			if (comparator.compare(dataMinimum, dataCurrent) > 0) {
				dataMinimum = dataCurrent;
				indexMinimum = indexCurrent;
			}
		}
		
		// Get the next entity from the source if available.
		// Otherwise remove the source and its current data.
		source = sources.get(indexMinimum);
		if (source.hasNext()) {
			sourceData.set(indexMinimum, source.next());
		} else {
			sources.remove(indexMinimum).close();
			sourceData.remove(indexMinimum);
		}
		
		return dataMinimum;
	}
	
	
	/**
	 * Not supported. An UnsupportedOperationException is always thrown.
	 */
	public void remove() {
		throw new UnsupportedOperationException();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void close() {
		for (ReleasableIterator<DataType> source : sources) {
			source.close();
		}
	}
}
