package theGhastModding.midiEditor.midi;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JOptionPane;

import theGhastModding.midiEditor.gui.MIDIEditorPanel;
import theGhastModding.midiEditor.gui.ProgressDialog;
import theGhastModding.midiEditor.main.InsertionSort;
import theGhastModding.midiEditor.main.MIDIEditorStart;

public class MIDIFile {
	
	private File file;
	private List<MIDIEvent> otherEvents;
	private List<TempoEvent> tempoEvents;
	private List<Slice> slices;
	private int resolution;
	private long midiLength;
	private boolean loadSucceded = false;
	private int trackCount = 0;
	private Thread midiLoaderThread = null;
	private boolean isLoading = false;
	
	public MIDIFile(){
		file = null;
	}
	
	public MIDIFile(String path){
		this.file = new File(path);
		load();
	}
	
	public File getFile(){
		return file;
	}
	
	public void save(File f){
		if(file == null){
			return;
		}
		try {
			List<Track> tracks = new ArrayList<Track>();
			tracks.add(new Track());
			tracks.get(0).addEvents(otherEvents);
			Track t;
			for(int i = 0; i < trackCount; i++){
				t = new Track();
				for(Slice s:slices){
					for(Note n:s.getNotes()){
						if(n.getTrack() == i){
							t.addEvent(new NoteOn((long)n.getStart(), n.getPitch(), n.getVelocity(), n.getChannel(), 0));
							t.addEvent(new NoteOff((long)n.getEnd(), n.getPitch(), 0, n.getChannel(), 0));
						}
					}
				}
				if(!t.getEvents().isEmpty()){
					tracks.add(t);
				}
			}
			MIDISaver saver = new MIDISaver(f, tracks, resolution);
			saver.cleanUp();
		}catch(Exception e){
			JOptionPane.showMessageDialog(MIDIEditorStart.frame, "Error saving MIDI: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
			return;
		}
	}
	
	public void load(){
		if(midiLoaderThread != null && midiLoaderThread.isAlive()){
			return;
		}
		midiLoaderThread = new Thread(new MIDILoaderThread());
		midiLoaderThread.start();
	}
	
	private class MIDILoaderThread implements Runnable {
		
		private MIDILoaderThread(){
			
		}
		
		@Override
		public void run() {
				isLoading = true;
				int loadednoets = 0;
				try {
					ProgressDialog progressDialog = ProgressDialog.showLoadDialog("Loading " + file.getName());
					MIDILoader ml = new MIDILoader(file);
					if(otherEvents != null){
						otherEvents.clear();
					}
					otherEvents = new ArrayList<MIDIEvent>();
					if(slices != null){
						slices.clear();
					}
					midiLength = ml.getLengthInTicks();
					slices = new ArrayList<Slice>();
					double numSlices = midiLength / MIDIEditorPanel.frameWidth;
					numSlices = Math.ceil(numSlices);
					for(int i = 0; i < (int)numSlices; i++){
						slices.add(new Slice(MIDIEditorPanel.frameWidth, i * MIDIEditorPanel.frameWidth));
					}
					System.out.println(numSlices);
					resolution = ml.getTPB();
					Track currentTrack = null;
					List<MIDIEvent> events = null;
					List<TempoEvent> tempos = new ArrayList<TempoEvent>();
					for(int i = 0; i < ml.getTrackCount(); i++){
						System.out.println("Preparing track " + Integer.toString(i) + " from " + Integer.toString(ml.getTrackCount()));
						ProgressDialog.status = "Preparing track " + Integer.toString(i + 1) + " from " + Integer.toString(ml.getTrackCount());
						ProgressDialog.progress = (int)((double)i / (double)ml.getTrackCount() * 50D) + 51;
						if(currentTrack != null){
							currentTrack.unload();
							currentTrack = null;
						}
						currentTrack = ml.getTracks().get(i);
						if(events != null){
							events.clear();
							events = null;
						}
						events = currentTrack.getEvents();
						InsertionSort.sortByTickTGMMIDIEvents(events);
						List<Note> tempNotes = new ArrayList<Note>();
						for(int o = 0; o < 128; o++){
							for(int l = 0; l < events.size(); l++){
								MIDIEvent event = events.get(l);
								if(event instanceof NoteOn){
									if(((NoteOn) event).getNoteValue() == o){
										tempNotes.add(new Note(event.getTick(), -1,o,i, ((NoteOn) event).getVelocity(), ((NoteOn) event).getChannel()));
									}
								}else if(event instanceof TempoEvent){
									boolean add = true;
									for(TempoEvent t:tempos){
										if(t.getTick() == event.getTick() && t.getBpm() == ((TempoEvent)event).getBpm()){
											add = false;
										}
									}
									if(add){
									tempos.add((TempoEvent)event);
									}
									
								}else if(event instanceof NoteOff){
									if(((NoteOff) event).getNoteValue() == o){
											if(!tempNotes.isEmpty()){
												int start = tempNotes.size();
												if(start != 0){
													start--;
												}
												for(int m = start; m > -1; m--){
													Note currentNote = tempNotes.get(m);
													if(!(currentNote.getEnd() >= 0) && currentNote.getStart() < event.getTick() && currentNote.getChannel() == ((NoteOff)event).getChannel()){
														currentNote.setEnd(event.getTick());
														break;
													}
												}
											}
									}
								}else if(event instanceof ProgramChangeEvent){
									otherEvents.add(event);
								}
							}
							if(!tempNotes.isEmpty()){
								for(int oo = 0; oo < (int)numSlices; oo++){
									for(Note n:tempNotes){
										if(n.getStart() >= 0 && n.getEnd() >= 0 && n.getEnd() > n.getStart()){
												if(n.getStart() >= oo * MIDIEditorPanel.frameWidth && n.getStart() < oo * MIDIEditorPanel.frameWidth + MIDIEditorPanel.frameWidth){
													slices.get(oo).addNote(n);
													loadednoets++;
												}
										}
									}
								}
								tempNotes.clear();
							}
						}
					}
					InsertionSort.sortByTickTGMTempos(tempos);
					otherEvents.addAll(tempos);
					int noteCount = ml.getNoteCount();
					trackCount = ml.getTrackCount();
					progressDialog.close();
					System.out.println("Done loading MIDI");
					JOptionPane.showMessageDialog(MIDIEditorStart.frame, "Finished loading MIDI. Note count: " + Long.toString((long)noteCount) + " Loaded notes: " + Integer.toString(loadednoets), "Message", JOptionPane.INFORMATION_MESSAGE);
					isLoading = false;
					loadSucceded = true;
				}catch(Exception e){
					e.printStackTrace();
					JOptionPane.showMessageDialog(MIDIEditorStart.frame, "Error loading MIDI: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
					isLoading = false;
					loadSucceded = false;
					return;
				}
		}
		
	}
	
	public List<MIDIEvent> getOtherEvents() {
		return otherEvents;
	}
	
	public List<TempoEvent> getTempoEvents() {
		return tempoEvents;
	}
	
	public List<Slice> getSlices() {
		return slices;
	}
	
	public int getResolution() {
		return resolution;
	}
	
	public long getMidiLength() {
		return midiLength;
	}
	
	public boolean hasLoadSucceded(){
		return loadSucceded;
	}
	
	public int getTrackCount() {
		return trackCount;
	}
	
	public boolean isLoading(){
		return isLoading;
	}
	
}