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

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

import org.openstreetmap.osmosis.core.container.v0_6.ChangeContainer;
import org.openstreetmap.osmosis.core.container.v0_6.EntityContainer;
import org.openstreetmap.osmosis.core.lifecycle.ReleasableIterator;
import org.openstreetmap.osmosis.core.store.PeekableIterator;
import org.openstreetmap.osmosis.core.task.common.ChangeAction;


/**
 * Produces a snapshot at a point in time from a complete history stream.
 * 
 * @author Brett Henderson
 */
public class EntitySnapshotReader implements ReleasableIterator<EntityContainer> {
	
	private PeekableIterator<List<ChangeContainer>> sourceIterator;
	private Date snapshotInstant;
	private EntityContainer nextValue;
	private boolean nextValueLoaded;
	
	
	/**
	 * Creates a new instance.
	 * 
	 * @param sourceIterator
	 *            An iterator containing the full history for entities.
	 * @param snapshotInstant
	 *            The state of the entity at this point in time will be dumped.
	 *            This ensures a consistent snapshot.
	 */
	public EntitySnapshotReader(
			ReleasableIterator<ChangeContainer> sourceIterator, Date snapshotInstant) {
		this.sourceIterator = new PeekableIterator<List<ChangeContainer>>(new EntityHistoryListReader(sourceIterator));
		this.snapshotInstant = snapshotInstant;
		
		nextValueLoaded = false;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public boolean hasNext() {
		while (!nextValueLoaded && sourceIterator.hasNext()) {
			List<ChangeContainer> changeList;
			ChangeContainer changeContainer;
			
			// Get the next change list from the underlying stream.
			changeList = sourceIterator.next();
			
			// Loop until all history values for the current element are exhausted and get the
			// latest version of the entity that fits within the snapshot timestamp.
			changeContainer = null;
			for (ChangeContainer tmpChangeContainer : changeList) {
				
				// We're only interested in elements prior or equal to the snapshot point.
				if (tmpChangeContainer.getEntityContainer().getEntity()
						.getTimestamp().compareTo(snapshotInstant) <= 0) {
					// Replace the current change container with the later version.
					changeContainer = tmpChangeContainer;
				}
			}
			
			// We are not interested in items created after the snapshot timestamp (ie. null) or deleted items.
			if (changeContainer != null && !ChangeAction.Delete.equals(changeContainer.getAction())) {
				nextValue = changeContainer.getEntityContainer();
				nextValueLoaded = true;
			}
		}
		
		return nextValueLoaded;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public EntityContainer next() {
		if (!hasNext()) {
			throw new NoSuchElementException();
		}
		
		nextValueLoaded = false;
		
		return nextValue;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void remove() {
		throw new UnsupportedOperationException();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void close() {
		sourceIterator.close();
	}
}
