package jp.hasc.hasctool.ui.views;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineEvent.Type;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.SourceDataLine;

import jp.hasc.hasctool.core.util.CoreUtil;

/**
 * WaveViewWidgetの選択位置と同期しながら、オーディオを再生します。
 * @author iwasaki
 */
public class WaveViewAudioPlayer {
	private static final double LINE_BUFFER_SECONDS = 0.25;

	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
			.getLog(WaveViewAudioPlayer.class);
	
	private AudioInputStream audioInputStream_;
	private double audioOffsetTime_;
	private WaveViewWidget waveViewWidget_;

	public WaveViewAudioPlayer(InputStream audioData,
			double audioOffsetTime, WaveViewWidget waveViewWidget) {
		super();
		
		waveViewWidget_ = waveViewWidget;
		audioOffsetTime_ = audioOffsetTime;
		try {
			ByteArrayOutputStream baout = new ByteArrayOutputStream();
			CoreUtil.copyStreamAndClose(audioData, baout);
			byte[] audioData2=baout.toByteArray();
			ByteArrayInputStream inps = new ByteArrayInputStream(audioData2);
			audioInputStream_ = AudioSystem.getAudioInputStream(inps);
			audioFormat_ = audioInputStream_.getFormat();
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}
	
	private boolean shutdown_=false;
	private Thread playThread_=null;
	private AudioFormat audioFormat_;
	private SourceDataLine sourceDataLine_;
	
	private long getBytesOfSeconds(double s) {
		return getFramesOfSeconds(s)*audioFormat_.getFrameSize();
	}
	private long getFramesOfSeconds(double s) {
		return (long)(s*audioFormat_.getFrameRate());
	}
	private double getSecondsOfFrames(long f) {
		return f/(double)audioFormat_.getFrameRate();
	}
	
	private void setAudioCursorTimeAsync(final double fs) {
		waveViewWidget_.getDisplay().asyncExec(new Runnable() {
			public void run() {
				waveViewWidget_.setAudioCursorTime(fs);
			}
		});
	}
	
	public void play() {
		if (playThread_!=null) return;
		LOG.info("Audio format: "+audioFormat_.toString());
		playThread_=new Thread() {
			private double skipSeconds_;

			private double getAudioTime(long fp) {
				return getSecondsOfFrames(fp)+audioOffsetTime_+skipSeconds_;
			}

			public void run() {
				try {
					// setup sourceDataLine
					DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class,
							audioFormat_);
					SourceDataLine sourceDataLine = (SourceDataLine)AudioSystem.getLine(
					                      dataLineInfo);
					int lineBuffSize = Math.max(256, (int)getBytesOfSeconds(LINE_BUFFER_SECONDS));
					sourceDataLine.open(audioFormat_, lineBuffSize);
					sourceDataLine.start();
					LineListener lineListener = new LineListener() {
						@Override
						public void update(LineEvent e) {
							if (e.getType()==Type.STOP) {
						    	final double fs=getAudioTime(e.getFramePosition());
					    		LOG.debug("stop fs:"+fs);
						    	setAudioCursorTimeAsync((fs));
						    }
						}
					};
					sourceDataLine.addLineListener(lineListener);
					sourceDataLine_=sourceDataLine;
					
					skipSeconds_ = waveViewWidget_.getAudioCursorTime();
					if (skipSeconds_>0 && skipSeconds_ - audioOffsetTime_>0 && 
							// 末尾から再生した場合は最初から
							skipSeconds_ - audioOffsetTime_ < getSecondsOfFrames(audioInputStream_.getFrameLength())-0.2
					) {
						skipSeconds_ = skipSeconds_ - audioOffsetTime_;
			    		LOG.debug("skip:"+skipSeconds_);
						audioInputStream_.skip(getBytesOfSeconds(skipSeconds_));
					}else{
						skipSeconds_=0;
					}
					
					// play
					byte buff[] = new byte[1024];
					double prevRefreshFs=audioOffsetTime_;
				    while(!shutdown_){
				    	final double fs=getAudioTime(sourceDataLine.getLongFramePosition());
			    		//LOG.debug("fs:"+fs);
			    		if (fs-prevRefreshFs> 1.0 / 4 /*fps*/) {
			    			if (!shutdown_) setAudioCursorTimeAsync((fs));
			    			prevRefreshFs=fs;
			    		}
			    		//
				    	int len = audioInputStream_.read(buff,0,buff.length);
				    	if (len<0) break;
				    	sourceDataLine.write(buff, 0, len);
				    }
				    audioInputStream_.close();
				    sourceDataLine.drain();
				    sourceDataLine.stop();
				    sourceDataLine.close();
				    sourceDataLine.removeLineListener(lineListener);
					LOG.info("Audio closed.");
					shutdown_=true;
				} catch (Exception ex) {
					LOG.warn("Exception",ex);
				}
			}
		};
		playThread_.start();
	}
	
	public boolean isClosed() {
		return shutdown_;
	}

	public void close() {
		if (shutdown_) return;
		shutdown_=true;
		try {
			if (playThread_!=null) {
				if (sourceDataLine_!=null) {
					sourceDataLine_.stop();
					sourceDataLine_.flush();
				}
				playThread_.join(3000);
			}
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

}
