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

import java.net.InetSocketAddress;
import java.util.Map;

import org.openstreetmap.osmosis.core.container.v0_6.ChangeContainer;
import org.openstreetmap.osmosis.core.task.v0_6.ChangeSink;
import org.openstreetmap.osmosis.core.task.v0_6.ChangeSinkChangeSource;
import org.openstreetmap.osmosis.core.task.v0_6.RunnableChangeSource;
import org.openstreetmap.osmosis.replicationhttp.v0_6.impl.ReplicationDataClientChannelPipelineFactory;
import org.openstreetmap.osmosis.replicationhttp.v0_6.impl.SequenceClient;
import org.openstreetmap.osmosis.replicationhttp.v0_6.impl.SequenceClientRestartManager;


/**
 * This task connects to a replication data server to obtain replication data,
 * then sends the replication data to the sink. This requires the change sink to
 * support the replication extensions allowing state persistence and multiple
 * invocations.
 * 
 * @author Brett Henderson
 */
public class ReplicationDataClient implements RunnableChangeSource {

	private NoReleaseChangeSinkWrapper changeSinkWrapper;
	private InetSocketAddress serverAddress;
	private String pathPrefix;


	/**
	 * Creates a new instance.
	 * 
	 * @param serverAddress
	 *            The server to connect to.
	 * @param pathPrefix
	 *            The base path to add to the URL. This is necessary if a data
	 *            server is sitting behind a proxy server that adds a prefix to
	 *            the request path.
	 */
	public ReplicationDataClient(InetSocketAddress serverAddress, String pathPrefix) {
		this.serverAddress = serverAddress;
		this.pathPrefix = pathPrefix;

		changeSinkWrapper = new NoReleaseChangeSinkWrapper();
	}


	@Override
	public void setChangeSink(ChangeSink changeSink) {
		changeSinkWrapper.setChangeSink(changeSink);
	}


	@Override
	public void run() {
		try {
			// Create a sequence client restart manager so that our sequence
			// client continues processing in the face of temporary connectivity
			// issues.
			SequenceClientRestartManager clientRestartManager = new SequenceClientRestartManager();
			
			// Create the client for receiving replication data.
			ReplicationDataClientChannelPipelineFactory pipelineFactory =
					new ReplicationDataClientChannelPipelineFactory(
							clientRestartManager.getControl(), changeSinkWrapper, serverAddress.getHostName(),
							pathPrefix);
			SequenceClient client = new SequenceClient(serverAddress, pipelineFactory);

			// Run the client and perform restarts if it fails. This call will
			// block.
			clientRestartManager.manageClient(client);

		} finally {
			changeSinkWrapper.realRelease();
		}
	}

	/**
	 * This acts as a proxy between the sequence client and the real change
	 * sink. The primary purpose is to prevent the release method from being
	 * called until all processing has completed.
	 */
	private static class NoReleaseChangeSinkWrapper implements ChangeSinkChangeSource {
		private ChangeSink changeSink;


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


		@Override
		public void initialize(Map<String, Object> metaData) {
			changeSink.initialize(metaData);
		}


		@Override
		public void process(ChangeContainer change) {
			changeSink.process(change);
		}


		@Override
		public void complete() {
			changeSink.complete();
		}


		@Override
		public void close() {
			// Do nothing.
		}


		/**
		 * Called by the main replication data client when all processing is
		 * complete. Unlike the release method which does nothing, this calls
		 * the change sink release method.
		 */
		public void realRelease() {
			changeSink.close();
		}
	}
}
