package net.sf.fmj.media.multiplexer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.Buffer;
import javax.media.Format;
import javax.media.ResourceUnavailableException;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;

import net.sf.fmj.media.AbstractMultiplexer;
import net.sf.fmj.utility.LoggerSingleton;

/**
 * An abstract Multiplexer which may be used as a base class for multiplexers which need to 
 * simply write something to an output stream as each buffer comes in.
 * @author Ken Larson
 *
 */
public abstract class AbstractInputStreamMux extends AbstractMultiplexer
{
	private static final Logger logger = LoggerSingleton.logger;

	private PipedInputStream pipedInputStream;
	private PipedOutputStream pipedOutputStream;
	
	private InputStreamPushDataSource dataOutput;
	
	private final ContentDescriptor contentDescriptor;
	
	// TODO: deal with n tracks properly
	
	public AbstractInputStreamMux(final ContentDescriptor contentDescriptor)
	{
		super();
		this.contentDescriptor = contentDescriptor;
		
	}
	
	protected OutputStream getOutputStream()
	{	return pipedOutputStream;
	}
	
	/** Not a JMF public API method, just a way for subclasses to get the data output to do notifications. */
	protected InputStreamPushDataSource getDataOutputNoInit()
	{	return dataOutput;
	}

	public DataSource getDataOutput()
	{
		if (dataOutput == null)
			dataOutput = createInputStreamPushDataSource(outputContentDescriptor, 1, new InputStream[] {pipedInputStream});
		logger.finer(getClass().getSimpleName() + " getDataOutput");
		return dataOutput;
	}

	public abstract Format[] getSupportedInputFormats();

	@Override
	public void close()
	{
		logger.finer(getClass().getSimpleName() + " close");
		super.close();
		
		// mgodehardt: disabled, otherwise trailer is not written, sink writes async
		/*if (pipedInputStream != null)
		{
			try
			{
				pipedInputStream.close();
			} catch (IOException e)
			{
				logger.log(Level.WARNING, "" + e, e);
			}
			finally
			{	pipedInputStream = null;
			}
		}
		
		if (pipedOutputStream != null)
		{
			try
			{
				pipedOutputStream.close();
			} catch (IOException e)
			{
				logger.log(Level.WARNING, "" + e, e);
			}
			finally
			{	pipedOutputStream = null;
			}
		}*/

		
		if (dataOutput != null)
		{	try
			{
				dataOutput.stop();
			} catch (IOException e)
			{
				logger.log(Level.WARNING, "" + e, e);
			}
			dataOutput.disconnect();
		}
	}

	@Override
	public void open() throws ResourceUnavailableException
	{
		logger.finer(getClass().getSimpleName() + " open");
		super.open();
	}

	public ContentDescriptor[] getSupportedOutputContentDescriptors(Format[] inputs)
	{
		// TODO: should this match the # of entries in inputs?
		return new ContentDescriptor[] {contentDescriptor};
	}

	public int process(Buffer buffer, int trackID)
	{
		logger.finer(getClass().getSimpleName() +  " process " + buffer + " " + trackID + " length " + buffer.getLength());
		
		try
		{
			doProcess(buffer, trackID, pipedOutputStream);
		} catch (IOException e1)
		{
			logger.log(Level.SEVERE, "" + e1, e1);
			return BUFFER_PROCESSED_FAILED;
		}

		if (dataOutput != null)
			dataOutput.notifyDataAvailable(0);	// only 1 track
		
		return BUFFER_PROCESSED_OK;
	}
	
	// intended to be overridden.  This implementation makse this into a RawMux:
	protected void doProcess(Buffer buffer, int trackID, OutputStream os) throws IOException
	{
		if (buffer.isEOM())
		{	os.close();
			return;		// TODO: what if there is data in buffer?
		}
		os.write((byte []) buffer.getData(), buffer.getOffset(), buffer.getLength());
	}
	
	private static final int PIPE_SIZE = 200000;	// TODO: get from format.
	// TODO: this has to be twice as big as the biggest buffer we are going to process, if this is used
	// for the web server to stream, otherwise it will hang.
	// The reason is that the some data may need be read from the media to realize, but the 
	// piped input stream will not be read until after the realize.

	@Override
	public int setNumTracks(int numTracks)
	{
		numTracks = super.setNumTracks(numTracks);
		
		try
		{

			pipedInputStream = new BigPipedInputStream(PIPE_SIZE);
			pipedOutputStream = new PipedOutputStream(pipedInputStream);

		}
		catch (IOException e)
		{	throw new RuntimeException(e);
		}
		return numTracks;
	}
	

	protected InputStreamPushDataSource createInputStreamPushDataSource(ContentDescriptor outputContentDescriptor, int numTracks, InputStream[] inputStreams)
	{
		return new InputStreamPushDataSource(outputContentDescriptor, numTracks, inputStreams);
	}
	
    protected void writeInt(OutputStream os, long value) throws IOException
	{
        byte[] aBuffer = new byte[4];
        
        aBuffer[0] = (byte)((value >> 24) & 0xff);
        aBuffer[1] = (byte)((value >> 16) & 0xff);
        aBuffer[2] = (byte)((value >>  8) & 0xff);
        aBuffer[3] = (byte)(value & 0xff);
        
        os.write(aBuffer, 0, aBuffer.length);
    }
}
