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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.openstreetmap.osmosis.core.container.v0_6.ChangeContainer;
import org.openstreetmap.osmosis.core.domain.v0_6.EntityType;
import org.openstreetmap.osmosis.core.task.common.ChangeAction;
import org.openstreetmap.osmosis.core.task.v0_6.ChangeSink;
import org.openstreetmap.osmosis.core.task.v0_6.ChangeSinkChangeSource;


/**
 * Looks at a sorted change stream and condenses multiple changes for a single entity into a single
 * change.
 * 
 * @author Brett Henderson
 */
public class ChangeSimplifierImpl implements ChangeSinkChangeSource {

	private List<ChangeContainer> currentChanges;
	private ChangeSink changeSink;
	
	
	/**
	 * Creates a new instance.
	 */
	public ChangeSimplifierImpl() {
		currentChanges = new ArrayList<ChangeContainer>();
	}
	
	
	private void flushCurrentChanges() {
		ChangeContainer changeBegin;
		ChangeContainer changeEnd;
		ChangeAction actionBegin;
		ChangeAction actionEnd;
		ChangeAction actionResult;
		
		changeBegin = currentChanges.get(0);
		changeEnd = currentChanges.get(currentChanges.size() - 1);
		
		actionBegin = changeBegin.getAction();
		actionEnd = changeEnd.getAction();
		
		// If the final action is a delete we'll send a delete action regardless of whether the
		// first action was a create just in case the create should have been a modify.
		// If the first action is a create, then the result is always a create (except for delete
		// case above).
		// Everything else is treated as a modify.
		if (actionEnd.equals(ChangeAction.Delete)) {
			actionResult = ChangeAction.Delete;
		} else if (actionBegin.equals(ChangeAction.Create)) {
			actionResult = ChangeAction.Create;
		} else {
			actionResult = ChangeAction.Modify;
		}
		
		changeSink.process(new ChangeContainer(changeEnd.getEntityContainer(), actionResult));
		
		currentChanges.clear();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
    public void initialize(Map<String, Object> metaData) {
		changeSink.initialize(metaData);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void process(ChangeContainer change) {
		// If the current change is for a different entity to those in our current changes list,
		// then we must process the current changes.
		if (currentChanges.size() > 0) {
			long currentId;
			EntityType currentEntityType;
			currentId = currentChanges.get(0).getEntityContainer().getEntity().getId();
			currentEntityType = currentChanges.get(0).getEntityContainer().getEntity().getType();

			if (currentId != change.getEntityContainer().getEntity().getId()
					|| currentEntityType != change.getEntityContainer().getEntity().getType()) {
				flushCurrentChanges();
			}
		}
		currentChanges.add(change);
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public void complete() {
		if (!currentChanges.isEmpty()) {
			flushCurrentChanges();
		}
		
		changeSink.complete();
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public void close() {
		changeSink.close();
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setChangeSink(ChangeSink changeSink) {
		this.changeSink = changeSink;
	}
}
