package kr.gui;

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 kr.AudioModel;
import kr.miditunemodel.MidiTuneModel;
import kr.miditunemodel.NoteEvent;
import kr.util.Listener;
import kr.util.ListenerManager;
import kr.util.NotificationManager;

public class MidiPlayController implements Listener, Runnable {
	private GuiModel guiModel;

	private MidiTuneModel midiModel;

	private boolean stopPlaying;

	private Synthesizer synthesizer;

	private Receiver receiver;
	
	private int currTone = -1;
	private boolean playing = false;
	private Object playingLock = new Object();
	private ShortMessage m = new ShortMessage();
	
	private static final long MIDI_ADJUST_MICROS = 1000*250;

	public MidiPlayController() {
	}

	public void init(GuiModel guiModel, MidiTuneModel midiModel) {
		this.guiModel = guiModel;
		this.midiModel = midiModel;
		ListenerManager.inst().registerListener(guiModel, this);

		try {
			synthesizer = MidiSystem.getSynthesizer();
			synthesizer.open();
			receiver = synthesizer.getReceiver();
//			m.setMessage(0xB0, 0, 73, 0x7F); //This is supposed to change the attack, but does nothing
//			receiver.send(m, currTone);
		} catch (MidiUnavailableException e) {
			NotificationManager.inst().error("Internal error, restart asap!");
			throw new IllegalArgumentException(e);
		}/* catch (InvalidMidiDataException e) {
			throw new IllegalArgumentException(e);
		}*/

	}

	public void notify(Object source, Object type, Object... values) {
		if ((GuiModel.MessageType) type == GuiModel.MessageType.IS_PLAYING_UPDATED) {
			if (guiModel.isPlaying()) {
				playing = true;
				new Thread(this).start();
			} else // stopped playing
			{
				synchronized(playingLock)
				{
					playing = false;
					playingLock.notify();
				}
			}
		}

	}

	public void run() {
		System.out.println("hacked not to run");
		if(1==1)return;
/*		NoteEvent currNote = midiModel.getNextNoteEvent(guiModel.getNowMicros());
		
		long ourLatencyMicros = 0;
		long timeShouldBeReady = -1;
		boolean noteOn = false;
		try {
			while(playing)
			{
				if(currNote == null)
				{
					if(currNoteIndex >= midiModel.getNumNoteEvents()) break;
					
					currNote = midiModel.getNoteEvent(currNoteIndex);
				}
				
				//adjust the now time by the latency
				long nowMicros = guiModel.getNowMicros()+synthesizer.getLatency();
				long sleepUntilTime;
				
				if(timeShouldBeReady != -1)
					ourLatencyMicros = nowMicros - timeShouldBeReady + ourLatencyMicros;

				//if we have passed the current note
				if(nowMicros >= currNote.getEndMicros())
				{
					stopPlaying();
					currNote = null;
					currNoteIndex++;
					continue;
				}
				else if(nowMicros >= currNote.getMicros())
				{ 
					//System.out.println(nowMicros - currNote.getMicros());
					startPlaying(currNote.getNote());
					sleepUntilTime = currNote.getEndMicros();
					noteOn = false;
				}
				else {// not yet at the beginning of the next note
					sleepUntilTime = currNote.getMicros();
					noteOn = true;
				}


				synchronized(playingLock)
				{
					long sleepTime = sleepUntilTime - nowMicros - ourLatencyMicros;
					//System.out.println(String.format("latency is %5d sleepTime is %10d noteOn %5s",ourLatencyMicros,sleepTime, noteOn+""));
					timeShouldBeReady = sleepUntilTime;
					//wait for next event
					//System.out.println("st is "+sleepTime+" latency is "+ourLatencyMicros);
					if(sleepTime > 0)
						playingLock.wait(sleepTime/1000, (int)((sleepTime % 1000) * 1000));
				}
			}

			stopPlaying(); //stop playing the current note
			
		} catch (InvalidMidiDataException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		
		playing = Boolean.FALSE;*/
	}

	private void startPlaying(int tone) throws InvalidMidiDataException {
		if(currTone == tone) return; //don't stop and start the same note
		stopPlaying();
		
		this.currTone = tone;
	
		m.setMessage(ShortMessage.NOTE_ON, 0, tone, 0x7F);

		receiver.send(m, -1); // NOTE: setting a delay does not work! delay+synthesizer.getMicrosecondPosition()+10000000L);

	}

	private void stopPlaying() throws InvalidMidiDataException {
		if(currTone != -1)
		{
			m.setMessage(ShortMessage.NOTE_OFF, 0, currTone, 0x50);

			receiver.send(m, -1);  // NOTE: setting a delay does not work! delay+synthesizer.getMicrosecondPosition()+10000000L);	
			currTone = -1;
		}
	}
}