/*
 *  Copyright (c) 2007 by Damien Di Fede <ddf@compartmental.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package ddf.minim.javasound;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.Control;
import javax.sound.sampled.SourceDataLine;

import ddf.minim.AudioEffect;
import ddf.minim.AudioListener;
import ddf.minim.AudioSignal;
import ddf.minim.Minim;
import ddf.minim.MultiChannelBuffer;
import ddf.minim.spi.AudioOut;
import ddf.minim.spi.AudioStream;

final class JSAudioOutput extends Thread implements AudioOut
{
	private AudioListener		listener;
	private AudioStream			stream;
	private AudioSignal			signal;
	private AudioEffect			effect;

	private SourceDataLine		line;
	private AudioFormat			format;
	private FloatSampleBuffer	buffer;
	private MultiChannelBuffer	mcBuffer;
	private int					bufferSize;
	private boolean				finished;
	private byte[]				outBytes;

	JSAudioOutput(SourceDataLine sdl, int bufferSize)
	{
		super();
		this.bufferSize = bufferSize;
		format = sdl.getFormat();

		buffer = new FloatSampleBuffer(format.getChannels(), bufferSize, format.getSampleRate());
		mcBuffer = new MultiChannelBuffer(bufferSize, format.getChannels());
		outBytes = new byte[buffer.getByteArrayBufferSize(format)];
		finished = false;
		line = sdl;
	}

	public void run()
	{
		line.start();
		while (!finished)
		{
			buffer.makeSilence();
			
			if ( signal != null )
			{
				readSignal();
			}
			else if ( stream != null )
			{
				readStream();
			}
			if (line.getFormat().getChannels() == Minim.MONO)
			{
				effect.process(buffer.getChannel(0));
				listener.samples(buffer.getChannel(0));
			}
			else
			{
				effect.process(buffer.getChannel(0), buffer.getChannel(1));
				listener.samples(buffer.getChannel(0), buffer.getChannel(1));
			}
			buffer.convertToByteArray(outBytes, 0, format);
			if ( line.available() == line.getBufferSize() )
			{
			  Minim.debug("Likely buffer underrun in AudioOutput.");
			}
			line.write(outBytes, 0, outBytes.length);
			try
			{
				Thread.sleep(1);
			}
			catch (InterruptedException e)
			{
			}
		}
		line.drain();
		line.stop();
		line.close();
		line = null;
	}

	// TODO: ditch readSignal eventually
	private void readSignal() 
	{
		if (line.getFormat().getChannels() == Minim.MONO)
		{
		    //long start = System.nanoTime();
			signal.generate(buffer.getChannel(0));
			//long end = System.nanoTime();
			//long elap = (end - start) / 1000;
			//System.out.println("Generated a buffer in " + elap + " microseconds.");
		}
		else
		{
			signal.generate(buffer.getChannel(0), buffer.getChannel(1));
		}
	}
	
	private void readStream()
	{
		stream.read(mcBuffer);
		for(int i = 0; i < mcBuffer.getChannelCount(); i++)
		{
			System.arraycopy(mcBuffer.getChannel(i), 0, buffer.getChannel(i), 0, buffer.getSampleCount());
		}
	}

	public void open()
	{
		start();
	}

	public void close()
	{
		finished = true;
	}

	public int bufferSize()
	{
		return bufferSize;
	}

	public AudioFormat getFormat()
	{
		return format;
	}

	public void setAudioEffect(AudioEffect effect)
	{
		this.effect = effect;
	}

	public void setAudioSignal(AudioSignal signal)
	{
		this.signal = signal;
	}

	public void setAudioListener(AudioListener listener)
	{
		this.listener = listener;
	}

	public Control[] getControls()
	{
		return line.getControls();
	}

	public void setAudioStream(AudioStream stream) 
	{
		this.stream = stream;	
	}
}
