package kr.gui;

import java.io.IOException;
import java.util.List;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Synthesizer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioFormat.Encoding;

import kr.AudioModel;
import kr.TimeKeeper;
import kr.miditunemodel.NoteEvent;
import kr.util.Listener;
import kr.util.ListenerManager;
import kr.util.audio.AudioClip;

//TODO: This may have threading issues
public class SamplePlayController extends AudioPlayController implements Runnable, TimeKeeper, Listener
{
	private GuiModel guiModel;
	private long startMicrosecondPosition;
	private Model model;
	
	public SamplePlayController()
	{
	}
	
	public void init(Model model, GuiModel guiModel)
	{
		this.model = model;
		this.guiModel = guiModel;
		ListenerManager.inst().registerListener(guiModel, this);
		setAudioOut(guiModel.getAudioOut());
		guiModel.turnOffSampledSources();
		new Thread(this).start();
	}

	public long getCurrMicros() {
		return (long) (guiModel.getLastStoppedMicros() + (getMicrosecondPosition() - startMicrosecondPosition) * getPlaybackRate());
	}

	public void notify(Object source, Object type, Object... values) {
		if ((GuiModel.MessageType) type == GuiModel.MessageType.PLAYING_SPEED_UPDATED) {
			setPlaybackRate(guiModel.getPlaybackRate());
		}
		else if ((GuiModel.MessageType) type == GuiModel.MessageType.IS_PLAYING_UPDATED) {
			if (guiModel.isPlaying()) {
				// since the sample player is the only thing that we don't
				// control how fast it plays,
				// we will use it to keep time for everything else
				guiModel.setTimeKeeper(this);
				startMicrosecondPosition = getMicrosecondPosition();
				
				if(guiModel.isPlaySelection())
				{
					guiModel.updateNowMicros(guiModel.getSelectionStart());
					this.setNotifyPos(guiModel.getAudioOut().getIndexFromMicros(guiModel.getSelectionEnd()));
				}
				else
				{
					//if we are within 1 ms of the end, don't play at all
					if(guiModel.getLastStoppedMicros() >= model.getMaxMilliSeconds()*1000 - 1000)
					{
						guiModel.stopPlaying();
						return;
					}
					
					this.setNotifyPos(guiModel.getAudioOut().getIndexFromMicros(model.getMaxMilliSeconds()*1000));
				}
				
				
				synchronized (this)
				{
					this.setCurPos(guiModel.getAudioOut().getIndexFromMicros(guiModel.getLastStoppedMicros()));
	
					guiModel.turnOnSampledSources();
				}
			}
			else //stopped playing
			{
				synchronized (this)
				{
					guiModel.turnOffSampledSources();
				}
			}
		}
	}
	
	protected void notifyCrossedPos()
	{
		if(guiModel.isPlayLoop() && guiModel.isPlaySelection())
		{
			synchronized (this)
			{
				startMicrosecondPosition = getMicrosecondPosition();
				this.setCurPos(guiModel.getAudioOut().getIndexFromMicros(guiModel.getSelectionStart()));
			}
		}
		else
			guiModel.stopPlaying();
	}

	
	protected void notifyStopped()
	{
		guiModel.stopPlaying();
	}
	
}

class NotePlayer implements Runnable {
	private List<NoteEvent> noteEvents;

	private GuiModel guiModel;

	private int currIndex = 0;

	public NotePlayer(List<NoteEvent> noteEvents,
			GuiModel guiModel) {
		this.noteEvents = noteEvents;
		this.guiModel = guiModel;
	}

	public void run() {
		try {
			Synthesizer synthesizer = MidiSystem.getSynthesizer();
			synthesizer.open();
			Receiver receiver = synthesizer.getReceiver();

			for (int i = 0; i < 100; i++) {
				int note = (int) (Math.random() * 128);

				ShortMessage m = new ShortMessage();

				m.setMessage(ShortMessage.NOTE_ON, 0, note, 0x50);

				receiver.send(m, -1);

				Thread.sleep(1000);

				m.setMessage(ShortMessage.NOTE_ON, 0, note, 0);

				receiver.send(m, -1);
			}

		} catch (MidiUnavailableException e) {
			e.printStackTrace();
		} catch (InvalidMidiDataException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}
}