// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.pgsnapshot.v0_6.impl;

import java.util.List;
import java.util.NoSuchElementException;

import org.openstreetmap.osmosis.core.database.FeaturePopulator;
import org.openstreetmap.osmosis.core.domain.v0_6.Entity;
import org.openstreetmap.osmosis.core.lifecycle.ReleasableContainer;
import org.openstreetmap.osmosis.core.lifecycle.ReleasableIterator;


/**
 * Provides a single iterator based on data provided by underlying iterators from each of the
 * underlying entity and feature iterators. Each underlying iterator provides one component of the
 * overall entity.
 * 
 * @param <T>
 *            The type of entity provided by this iterator.
 */
public class EntityReader<T extends Entity> implements ReleasableIterator<T> {
	
	private boolean nextValueLoaded;
	private ReleasableContainer releasableContainer;
	private ReleasableIterator<T> entityIterator;
	private List<FeaturePopulator<T>> featurePopulators;
	private T nextValue;
	
	
	/**
	 * Creates a new instance.
	 * 
	 * @param entityIterator
	 *            The entity source.
	 * @param featurePopulators
	 *            Populators to add entity specific features to the generated entities.
	 */
	public EntityReader(ReleasableIterator<T> entityIterator, List<FeaturePopulator<T>> featurePopulators) {
		releasableContainer = new ReleasableContainer();
		
		this.entityIterator = releasableContainer.add(entityIterator);
		for (FeaturePopulator<T> featurePopulator : featurePopulators) {
			releasableContainer.add(featurePopulator);
		}
		this.featurePopulators = featurePopulators;
	}
	
	
	/**
	 * Consolidates the output of all readers so that entities are fully
	 * populated.
	 * 
	 * @return An entity record where the entity is fully populated.
	 */
	private T readNextEntity() {
		T entity;
		
		entity = entityIterator.next();
		
		// Add entity type specific features to the entity.
		for (FeaturePopulator<T> populator : featurePopulators) {
			populator.populateFeatures(entity);
		}
		
		return entity;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public boolean hasNext() {
		if (!nextValueLoaded && entityIterator.hasNext()) {
			nextValue = readNextEntity();
			nextValueLoaded = true;
		}
		
		return nextValueLoaded;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public T next() {
		T result;
		
		if (!hasNext()) {
			throw new NoSuchElementException();
		}
		
		result = nextValue;
		nextValueLoaded = false;
		
		return result;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void remove() {
		throw new UnsupportedOperationException();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void close() {
		releasableContainer.close();
	}
}
