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

import java.nio.channels.ClosedChannelException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.openstreetmap.osmosis.core.OsmosisRuntimeException;


/**
 * Netty handler for receiving replication sequence information and notifying
 * listeners.
 * 
 * @author Brett Henderson
 */
public abstract class SequenceClientHandler extends SimpleChannelHandler {

	private static final Logger LOG = Logger.getLogger(SequenceClientHandler.class.getName());

	private SequenceClientControl control;
	private String serverHost;
	private boolean midStream;
	private boolean active;


	/**
	 * Creates a new instance.
	 * 
	 * @param control
	 *            Provides the Netty handlers with access to the controller.
	 * @param serverHost
	 *            The name of the host system running the sequence server.
	 */
	public SequenceClientHandler(SequenceClientControl control, String serverHost) {
		this.control = control;
		this.serverHost = serverHost;
		
		active = true;
	}


	/**
	 * Gets the URI to request from the server to initialise message processing.
	 * 
	 * @return The request URI.
	 */
	protected abstract String getRequestUri();


	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		// Send a request to the server asking for sequence number
		// notifications.
		HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, getRequestUri());
		request.headers().add("Host", serverHost);
		Channels.write(ctx, e.getFuture(), request);

		midStream = false;
	}


	/**
	 * Processes the contents of a single HTTP chunk.
	 * 
	 * @param buffer
	 *            The data contained in the chunk.
	 */
	protected abstract void processMessageData(ChannelBuffer buffer);


	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		if (active) {
			ChannelBuffer buffer;
	
			if (!midStream) {
				HttpResponse response = (HttpResponse) e.getMessage();
				HttpResponseStatus status = response.getStatus();
				if (!HttpResponseStatus.OK.equals(status)) {
					throw new OsmosisRuntimeException("Received a " + status + " response from the server.");
				}
				buffer = response.getContent();
				midStream = true;
			} else {
				HttpChunk chunk = (HttpChunk) e.getMessage();
				buffer = chunk.getContent();
			}
	
			// Perform implementation specific processing of the buffer contents.
			if (buffer.readableBytes() > 0) {
				processMessageData(buffer);
			}
		}
	}


	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		control.channelClosed();
	}


	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		// When we close the channel we may still continue to receive some
		// data. Flag that this should be ignored.
		active = false;
		
		// Get the cause of the exception.
		Throwable t = e.getCause();

		// A ClosedChannelException occurs if the client disconnects and is not
		// an error scenario.
		if (!(t instanceof ClosedChannelException)) {
			LOG.log(Level.SEVERE, "Error during processing for channel " + ctx.getChannel() + ".", t);
		}

		// We must stop sending to this client if any errors occur during
		// processing.
		e.getChannel().close();
	}
}
