package kr.miditunemodel;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Track;

import kr.midireader.MidiAnalyzer;
import kr.miditunemodel.Event.Type;
import kr.util.Listener;
import kr.util.ListenerManager;
import kr.util.MidiUtils;
import kr.util.MidiUtils.TempoCache;
import kr.util.audio.AudioClip;
import kr.util.audio.GainAudioClip;
import kr.util.audio.MidiSongAudioClip;

import kr.util.Util;

/**
 * Keeps track of note events and other kr events
 */
public class MidiTuneModel implements Listener
{
	public enum MessageType {
		LENGTH_UPDATED, METRODOME_UPDATED
	}

	//min and max note were discovered by analyzing every song in KR. In no case does a note fall outside this range...
	//TODO: handle generic midi files with notes outside this range... 
	static public final byte MIN_NOTE = 40; 
	static public final byte MAX_NOTE = 85;
	
	private NoteEventGroup noteEvents = new NoteEventGroup();
	private EventGroup<KREvent> krEvents = new EventGroup<KREvent>();
	private EventGroup<MetrodomeEvent> mEvents = new EventGroup<MetrodomeEvent>();
	
	private MidiSongAudioClip midiSongAudioClip;
	
	private static final int MIDI_AUDIO_RATE = 48000;
	
	public static final int STANDARD_MICROS_PER_QUARTER_NOTE = 500000;
	
	//All KR files have a resolution of 480 ticks per quarter note
	private static final int RESOLUTION = 480;
	
	public MidiTuneModel()
	{
		midiSongAudioClip=new MidiSongAudioClip(MIDI_AUDIO_RATE, this);
		midiSongAudioClip.setName("Tune");
		//add a starting metrodome event incase there is none
		mEvents.addToEnd(new MetrodomeEvent(0, STANDARD_MICROS_PER_QUARTER_NOTE));
		
		ListenerManager.inst().registerListener(noteEvents, this);
		ListenerManager.inst().registerListener(krEvents, this);
		ListenerManager.inst().registerListener(mEvents, this);
	}
	
	public AudioClip getSynthAudioClip() {
		return midiSongAudioClip;
	}

	
	public NoteEventGroup getNoteEventGroup() {
		return noteEvents;
	}

	public int getMaxMilliSeconds() {
		return Math.max(mEvents.getMaxMilliSeconds(), Math.max(noteEvents.getMaxMilliSeconds(), krEvents.getMaxMilliSeconds()));
	}

	public void clear() {
		noteEvents.clear();
		krEvents.clear();
		mEvents.clear();
	}

	public void notifyListeners(MessageType changeType, boolean lengthChanged) {
		ListenerManager.inst().notify(this, changeType);
		
		if(lengthChanged)
			ListenerManager.inst().notify(this, MessageType.LENGTH_UPDATED);
	}

	public EventGroup<KREvent> getKREventGroup() {
		return krEvents;
	}

	public EventGroup<MetrodomeEvent> getMetrodomeEventGroup() {
		return mEvents;
	}


	public void notify(Object source, Object type, Object... values) {
		if((source == krEvents || source == mEvents || source == noteEvents) &&
				type == EventGroup.MessageType.LENGTH_CHANGED)
			ListenerManager.inst().notify(this, MessageType.LENGTH_UPDATED);
	}

	/**
	 * Reads a midi track of voice tone for the song. This
	 * track may be the same as the lyrics.
	 * 
	 * @param lyrics lyrics track. May be null. May be the same as tune.
	 * @param tune the track which contains the tune
	 * @param channel 0x00 through 0x0F, indicates channel(s) to use for voice
	 */
	public void readTune(Sequence sequence, Track lyrics, Track tune,
			boolean isKRMidi)
	{
		MidiAnalyzer.readTune(noteEvents, sequence, lyrics, tune, isKRMidi);
	}

	public void readTempoEvents(Sequence sequence, Track tempoTrack, boolean isKRMidi)
	{
		MidiAnalyzer.readTempoEvents(mEvents, sequence, tempoTrack, isKRMidi);	
	}

	public void readKREvents(Sequence sequence, Track krEventTrack, boolean isKRMidi)
	{
		MidiAnalyzer.readKREvents(krEvents, sequence, krEventTrack, isKRMidi);	
	}

	public void createDefaultKREvents() {
		MidiAnalyzer.createDefaultKREvents(this, krEvents);
	}
	
 
}

class MidiTextModelTest
{
	public static void main(String []args) throws InvalidMidiDataException, IOException
	{
		Sequence sequence = null;
		File file = new File(args[0]);

		sequence = MidiSystem.getSequence(file);

		Track [] tracks = sequence.getTracks();
		
		MidiTuneModel m = new MidiTuneModel();
		
		//m.readTune(sequence, tracks[1], sequence, tracks[1], (byte)0, (byte)1);
		
		//System.out.println(Util.listToString("Tune\n", m.getNoteEvents(), "\n", "\n"));
	}
}

