package theGhastModding.tgmsynth.tester;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.filechooser.FileNameExtensionFilter;

import theGhastModding.midiPlayer.midi.MIDIEvent;
import theGhastModding.midiPlayer.midi.MIDILoader;
import theGhastModding.midiPlayer.midi.NoteOff;
import theGhastModding.midiPlayer.midi.NoteOn;
import theGhastModding.midiPlayer.midi.TempoEvent;
import theGhastModding.midiPlayer.midi.Track;
import theGhastModding.tgmsynth.synth.TGMSynth;

import javax.swing.SwingConstants;

@SuppressWarnings("serial")
public class SynthTesterPanel extends JPanel implements Runnable {
	
	private Thread t;
	private MIDILoader loader;
	private boolean isPlaying = false;
	private JProgressBar progressBar;
	private TGMSynth synth;
	private JLabel lblCurrentActiveVoices;
	
	public SynthTesterPanel(){
		super();
		setPreferredSize(new Dimension(700,210));
		setLayout(null);
		
		JLabel lblonlyTheSynth = new JLabel("a");
		lblonlyTheSynth.setBounds(10, 11, 680, 14);
		add(lblonlyTheSynth);
		
		JFileChooser midiChooser = new JFileChooser();
		midiChooser.setDialogTitle("Open MIDI...");
		midiChooser.setFileFilter(new FileNameExtensionFilter("MIDI files", "mid", "MID", "midi", "MIDI"));
		
		JLabel lblSelectedMidi = new JLabel("Selected MIDI: -");
		lblSelectedMidi.setBounds(10, 70, 680, 14);
		add(lblSelectedMidi);
		
		JLabel lblNoteCount = new JLabel("Note count: -");
		lblNoteCount.setBounds(10, 95, 680, 14);
		add(lblNoteCount);
		
		JButton btnOpenAFile = new JButton("Open a file here");
		btnOpenAFile.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(isPlaying){
					JOptionPane.showMessageDialog(SynthTester.frame, "Player is currently still playing a MIDI", "Error", JOptionPane.ERROR_MESSAGE);
					return;
				}
				if(midiChooser.showOpenDialog(SynthTester.frame) == JFileChooser.APPROVE_OPTION){
					if(!midiChooser.getSelectedFile().exists()){
						JOptionPane.showMessageDialog(SynthTester.frame, "The selected file doesn't exist", "Error", JOptionPane.ERROR_MESSAGE);
						return;
					}
					try {
						if(loader != null) loader.unload();
						loader = null;
						System.gc();
						loader = new MIDILoader(midiChooser.getSelectedFile());
						lblSelectedMidi.setText("Selected MIDI: " + midiChooser.getSelectedFile().getName());
						lblNoteCount.setText("Note count: " + Integer.toString(loader.getNoteCount()));
						System.gc();
					}catch(Exception ex){
						JOptionPane.showMessageDialog(SynthTester.frame, "Error loading MIDI: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
						ex.printStackTrace();
						loader = null;
						lblSelectedMidi.setText("Selected MIDI: -");
						lblNoteCount.setText("Note count: -");
						return;
					}
				}
			}
		});
		btnOpenAFile.setBounds(10, 36, 680, 23);
		add(btnOpenAFile);
		
		JButton btnPlay = new JButton("Play");
		btnPlay.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(isPlaying){
					JOptionPane.showMessageDialog(SynthTester.frame, "The player is allready playing", "Error", JOptionPane.ERROR_MESSAGE);
					return;
				}
				if(loader == null){
					JOptionPane.showMessageDialog(SynthTester.frame, "There is no MIDI loaded", "Error", JOptionPane.ERROR_MESSAGE);
					return;
				}
				stop = false;
				t = null;
				System.gc();
				t = a();
				t.start();
			}
		});
		btnPlay.setBounds(10, 120, 89, 23);
		add(btnPlay);
		
		JButton btnStop = new JButton("Stop");
		btnStop.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(!isPlaying){
					JOptionPane.showMessageDialog(SynthTester.frame, "The player is not playing anything to stop right now", "Error", JOptionPane.ERROR_MESSAGE);
					return;
				}
				lblCurrentActiveVoices.setText("Current active voices: 0/512");
				stop = true;
				noteOffToAll();
			}
		});
		btnStop.setBounds(109, 120, 89, 23);
		add(btnStop);
		
		progressBar = new JProgressBar();
		progressBar.setBounds(10, 154, 680, 14);
		progressBar.setMinimum(0);
		progressBar.setMaximum(680);
		add(progressBar);
		
		JButton btnExit = new JButton("Exit");
		btnExit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				synth.closeSynth();
				System.exit(0);
			}
		});
		btnExit.setBounds(601, 179, 89, 23);
		add(btnExit);
		
		JButton btnAbout = new JButton("About");
		btnAbout.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JOptionPane.showMessageDialog(SynthTester.frame, "This thing was made by TheGhastModding", "About", JOptionPane.INFORMATION_MESSAGE);
			}
		});
		btnAbout.setBounds(502, 179, 89, 23);
		add(btnAbout);
		
		lblCurrentActiveVoices = new JLabel("Current active voices: 0/512");
		lblCurrentActiveVoices.setHorizontalAlignment(SwingConstants.RIGHT);
		lblCurrentActiveVoices.setBounds(519, 70, 171, 14);
		add(lblCurrentActiveVoices);
		
		try {
			synth = new TGMSynth(512);
		} catch(Exception e){
			JOptionPane.showMessageDialog(SynthTester.frame, "Error starting GPGPU synth: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
			System.exit(1);
		}
	}
	
	private void noteOffToAll() {
		for(int i = 0; i < 16; i++){
			for(int j = 0; j < 128; j++){
				synth.noteOff(i, j, 0);
			}
		}
	}
	
	private Thread a(){
		return new Thread(this);
	}
	
	private double TPS;
	private double TPB;
	private int poly = 0;
	private boolean stop = false;
	
	@Override
	public void run() {
		try {
			isPlaying = true;
			List<List<MIDIEvent>> midiEvents = new ArrayList<List<MIDIEvent>>();
			for(Track t:loader.getTracks()){
				midiEvents.add(t.getEvents());
			}
			TempoEvent firstTempo = null;
			for(List<MIDIEvent> eventList:midiEvents){
				for(MIDIEvent event:eventList){
					if(event instanceof TempoEvent){
						if(event.getTick() == 0){
							firstTempo = (TempoEvent)event;
						}
					}
				}
			}
			TPB = loader.getTPB();
			TPS = (firstTempo.getBpm() / 60D) * TPB;
			double tickPosition = 0;
			double timerThen = System.nanoTime();
			double timerNow;
			boolean playing = true;
			int[] currentEvents = new int[midiEvents.size()];
			for(int i = 0; i < currentEvents.length; i++) currentEvents[i] = 0;
			long timr = System.currentTimeMillis();
			while(playing){
				if(stop){
					playing = false;
				}
				if(System.currentTimeMillis() - timr >= 100){
					lblCurrentActiveVoices.setText("Current active voices: " + Integer.toString(synth.getCurrentPolyphony()) + "/512");
				}
				poly = 0;
				timerNow = System.nanoTime();
			    tickPosition += (((double)timerNow - (double)timerThen) / 1000000000D) * TPS;
			    timerThen = timerNow;
			    if(tickPosition >= loader.getLengthInTicks()){
					playing = false;
				}
			    for(int i = 0; i < midiEvents.size(); i++){
				    if(currentEvents[i] >= midiEvents.get(i).size()){
				    	continue;
				    }
				    if(midiEvents.get(i).get(currentEvents[i]).getTick() <= tickPosition){
				    	processEvent(midiEvents.get(i).get(currentEvents[i]));
				    	currentEvents[i]++;
					    if(currentEvents[i] >= midiEvents.get(i).size()){
					    	continue;
					    }
				    	while(midiEvents.get(i).get(currentEvents[i]).getTick() <= tickPosition){
				    		processEvent(midiEvents.get(i).get(currentEvents[i]));
				    		currentEvents[i]++;
						    if(currentEvents[i] >= midiEvents.get(i).size()){
						    	break;
						    }
				    	}
				    }
			    }
			    int value = (int) (tickPosition / (double)loader.getLengthInTicks() * 680D);
			    if(value < 0) value = 0;
			    if(value > 680) value = 680;
			    progressBar.setValue(value);
			}
			progressBar.setValue(0);
			isPlaying = false;
			noteOffToAll();
		} catch(Exception e){
			JOptionPane.showMessageDialog(SynthTester.frame, "Error during playback: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
			isPlaying = false;
			progressBar.setValue(0);
		}
	}
	
	private void processEvent(MIDIEvent event) throws Exception {
		if(event instanceof TempoEvent){
			TPS = (((TempoEvent)event).getBpm() / 60) * TPB;
		}
		if(event instanceof NoteOn){
			if(poly <= 512){
				synth.noteOn(((NoteOn) event).getChannel(), ((NoteOn) event).getNoteValue() - 60, ((NoteOn) event).getVelocity());
				poly++;
			}
		}
		if(event instanceof NoteOff){
			synth.noteOff(((NoteOff) event).getChannel(), ((NoteOff) event).getNoteValue() - 60, ((NoteOff) event).getVelocity());
		}
	}
}