/***********************************************************************************************************************
 * Copyright (C) 2010-2014 by the Stratosphere project (http://stratosphere.eu)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 **********************************************************************************************************************/

package eu.stratosphere.runtime.io.channels;

import eu.stratosphere.nephele.event.task.AbstractEvent;
import eu.stratosphere.nephele.event.task.AbstractTaskEvent;
import eu.stratosphere.nephele.jobgraph.JobID;
import eu.stratosphere.runtime.io.Buffer;
import eu.stratosphere.runtime.io.network.Envelope;
import eu.stratosphere.runtime.io.gates.OutputGate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.Arrays;


public class OutputChannel extends Channel {

	private static final Log LOG = LogFactory.getLog(OutputChannel.class);

	private final Object closeLock = new Object();
	
	private final OutputGate outputGate;

	private boolean senderCloseRequested;

	private boolean receiverCloseRequested;

	private int currentSeqNum;

	// -----------------------------------------------------------------------------------------------------------------

	/**
	 * Creates a new output channel object.
	 *
	 * @param outputGate the output gate this channel is connected to
	 * @param index the index of the channel in the output gate
	 * @param id the ID of the channel
	 * @param connectedId the ID of the channel this channel is connected to
	 * @param type the type of this channel
	 */
	public OutputChannel(OutputGate outputGate, int index, ChannelID id, ChannelID connectedId, ChannelType type) {
		super(index, id, connectedId, type);

		this.outputGate = outputGate;
	}

	// -----------------------------------------------------------------------------------------------------------------
	//                                           Data processing
	// -----------------------------------------------------------------------------------------------------------------

	public void sendBuffer(Buffer buffer) throws IOException, InterruptedException {
		checkStatus();

		// discard empty buffers
		if (buffer.size() == 0) {
			return;
		}

		Envelope envelope = createNextEnvelope();
		envelope.setBuffer(buffer);
		this.envelopeDispatcher.dispatchFromOutputChannel(envelope);
	}

	public void sendEvent(AbstractEvent event) throws IOException, InterruptedException {
		checkStatus();

		Envelope envelope = createNextEnvelope();
		envelope.serializeEventList(Arrays.asList(event));
		this.envelopeDispatcher.dispatchFromOutputChannel(envelope);
	}

	public void sendBufferAndEvent(Buffer buffer, AbstractEvent event) throws IOException, InterruptedException {
		checkStatus();

		Envelope envelope = createNextEnvelope();
		envelope.setBuffer(buffer);
		envelope.serializeEventList(Arrays.asList(event));
		this.envelopeDispatcher.dispatchFromOutputChannel(envelope);
	}

	// -----------------------------------------------------------------------------------------------------------------
	//                                          Event processing
	// -----------------------------------------------------------------------------------------------------------------

	@Override
	public void queueEnvelope(Envelope envelope) {
		if (envelope.hasBuffer()) {
			throw new IllegalStateException("Envelope for OutputChannel has Buffer attached.");
		}

		for (AbstractEvent event : envelope.deserializeEvents()) {
			if (event.getClass() == ChannelCloseEvent.class) {
				synchronized (this.closeLock) {
					this.receiverCloseRequested = true;
					this.closeLock.notifyAll();
				}
			} 
			else if (event instanceof AbstractTaskEvent) {
				if (LOG.isDebugEnabled()) {
					LOG.debug("OutputChannel received task event: " + event);
				}
				
				this.outputGate.deliverEvent((AbstractTaskEvent) event);
			}
			else {
				throw new RuntimeException("OutputChannel received an event that is neither close nor task event.");
			}
		}
	}

	// -----------------------------------------------------------------------------------------------------------------
	//                                              Shutdown
	// -----------------------------------------------------------------------------------------------------------------

	public void requestClose() throws IOException, InterruptedException {
		if (this.senderCloseRequested) {
			return;
		}

		this.senderCloseRequested = true;

		Envelope envelope = createNextEnvelope();
		envelope.serializeEventList(Arrays.asList(new ChannelCloseEvent()));
		this.envelopeDispatcher.dispatchFromOutputChannel(envelope);
	}

	@Override
	public boolean isClosed() {
		return this.senderCloseRequested && this.receiverCloseRequested;
	}
	
	public void waitForChannelToBeClosed() throws InterruptedException {
		synchronized (this.closeLock) {
			while (!this.receiverCloseRequested) {
				this.closeLock.wait(1000);
			}
		}
	}

	// -----------------------------------------------------------------------------------------------------------------

	@Override
	public boolean isInputChannel() {
		return false;
	}

	@Override
	public JobID getJobID() {
		return this.outputGate.getJobID();
	}
	
	private void checkStatus() throws IOException {
		if (this.senderCloseRequested) {
			throw new IllegalStateException(String.format("Channel %s already requested to be closed", getID()));
		}
		if (this.receiverCloseRequested) {
			throw new ReceiverAlreadyClosedException();
		}
	}

	private Envelope createNextEnvelope() {
		return new Envelope(this.currentSeqNum++, getJobID(), getID());
	}

	@Override
	public void transferEvent(AbstractEvent event) throws IOException, InterruptedException {
		// TODO remove with pending changes for input side
	}

	@Override
	public void releaseAllResources() {
		// nothing to do for buffer oriented runtime => TODO remove with pending changes for input side
	}

	@Override
	public void destroy() {
		// nothing to do for buffer oriented runtime => TODO remove with pending changes for input side
	}
}

@@ -2774,24 +2774,51 @@
 e() == 0) %7B%0A
+%09%09%09buffer.recycleBuffer();%0A
 %09%09%09return;%0A%09

Fix buffer leak in OutputChannel