package theGhastModding.midiToolbox.main;

import java.awt.Dimension;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

@SuppressWarnings("serial")
public class NoteCounterPanel extends JPanel {
	
	private int type = 0;
	private int trackCount = 0;
	private long notes = 0;
	private long length;
	private int TPB;
	private float initialBPM;
	
	public NoteCounterPanel(){
		super();
		setPreferredSize(new Dimension(700, 300));
		setFocusable(true);
		setLayout(null);
		
		JButton btnGetMidiInformation = new JButton("Get MIDI information");
		btnGetMidiInformation.setBounds(10, 11, 668, 23);
		add(btnGetMidiInformation);
		
		JLabel lblMidiInfo = new JLabel("MIDI info:");
		lblMidiInfo.setBounds(10, 66, 74, 16);
		add(lblMidiInfo);
		
		JLabel lblMidiName = new JLabel("MIDI Name:");
		lblMidiName.setBounds(34, 94, 333, 16);
		add(lblMidiName);
		
		JLabel lblNoteCount = new JLabel("Note Count:");
		lblNoteCount.setBounds(34, 122, 333, 16);
		add(lblNoteCount);
		
		JLabel lblTrackCount = new JLabel("Track Count:");
		lblTrackCount.setBounds(32, 150, 668, 16);
		add(lblTrackCount);
		
		JLabel lblMidiType = new JLabel("MIDI Type:");
		lblMidiType.setBounds(34, 179, 666, 16);
		add(lblMidiType);
		
		JLabel lblLengthInTicks = new JLabel("Length in Ticks:");
		lblLengthInTicks.setBounds(32, 207, 656, 16);
		add(lblLengthInTicks);
		
		JLabel lblTicksPerBeat = new JLabel("Ticks per beat:");
		lblTicksPerBeat.setBounds(377, 95, 301, 14);
		add(lblTicksPerBeat);
		
		JLabel lblInitialBpm = new JLabel("Initial BPM:");
		lblInitialBpm.setBounds(377, 123, 313, 14);
		add(lblInitialBpm);
		JFileChooser midiChooser = new JFileChooser();
		midiChooser.setDialogTitle("Select MIDI");
        FileFilter filter3 = new FileNameExtensionFilter("MIDI files", 
                "mid", "midi", "MID", "MIDI");  
        midiChooser.setFileFilter(filter3);
		btnGetMidiInformation.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int option = midiChooser.showOpenDialog(TGMMIDIToolbox.frame);
				if(option == JFileChooser.APPROVE_OPTION){
					File selectedMidi = midiChooser.getSelectedFile();
					if(!selectedMidi.exists()){
		        		JOptionPane.showMessageDialog(null, "The selected MIDI doesnt exist", "Error", JOptionPane.ERROR_MESSAGE);
		        		return;
					}
					initialBPM = -1;
					if(countNotes(selectedMidi) == 1){
						lblMidiName.setText("MIDI Name: " + selectedMidi.getName());
						lblNoteCount.setText("Note Count: " + Long.toString(notes));
						lblTrackCount.setText("Track Count: " + Integer.toString(trackCount));
						lblMidiType.setText("MIDI Type: " + Integer.toString(type));
						lblLengthInTicks.setText("Length in Ticks: " + Long.toString(length));
						lblTicksPerBeat.setText("Ticks per beat: " + Integer.toString(TPB));
						lblInitialBpm.setText("Initial BPM: " + (initialBPM == -1 ? "Unknown" : Float.toString(initialBPM)));
					}
				}
			}
		});
		requestFocus();
	}
	
	private int countNotes(File midiFile){
		notes = 0;
		type = 0;
		trackCount = 0;
		try {
		FileInputStream stream = new FileInputStream(midiFile);
		byte[] indentifier = new byte[4];
		stream.read(indentifier);
		String s = new String(indentifier);
		if(!s.equalsIgnoreCase("MThd")){
			JOptionPane.showMessageDialog(TGMMIDIToolbox.frame, "Error loading MIDI: File identifier not MThd", "Error", JOptionPane.ERROR_MESSAGE);
			stream.close();
			return 0;
		}
		stream.skip(4);
		byte[] header = new byte[0x06];
		stream.read(header);
		type = bytesToInt(Arrays.copyOfRange(header, 0, 2));
		trackCount = bytesToInt(Arrays.copyOfRange(header, 2, 4));
		TPB  = bytesToInt(Arrays.copyOfRange(header, 4, 6));
		length = 0;
		for(int i = 0; i < trackCount; i++){
			indentifier = new byte[4];
			stream.read(indentifier);
			s = new String(indentifier);
			if(!s.equalsIgnoreCase("MTrk")){
				JOptionPane.showMessageDialog(TGMMIDIToolbox.frame, "Error loading MIDI: Track identifier not MTrk", "Error", JOptionPane.ERROR_MESSAGE);
				stream.close();
				return 0;
			}
			byte[] headerSize = new byte[4];
			stream.read(headerSize);
			int size = bytesToInt(headerSize);
			byte[] data = new byte[size];
			stream.read(data);
			ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
			long total = 0;
			int lastStatus = 0;
			while(byteStream.available() > 0){
				long time = getVaribaleLengthValue(byteStream);
				byteStream.mark(1);
				total += time;
				if(total > length){
					length = total;
				}
				if(time < 0){
					break;
				}
				int meta = byteStream.read() & 0xFF;
				if(meta >= 128) {
					lastStatus = meta;
				}
				if(meta < 128 && lastStatus != 0) {
					meta = lastStatus;
					byteStream.reset();
					byteStream.mark(1);
				}
				if(meta >= 0x80 && meta <= 0xFE){
					if(meta >= 0xF4 && meta <= 0xFF){
						continue;
					}
					byteStream.read();
					int b = 0;
					if((!(meta >= 0xC0 && meta <= 0xDF)) && meta != 0xF3 && meta != 0xF1){
						b = byteStream.read() & 0xFF;
					}
					if(meta == 0x90 || meta == 0x91 || meta == 0x92 || meta == 0x93 || meta == 0x94 || meta == 0x95 || meta == 0x96 || meta == 0x97 || meta == 0x98 || meta == 0x99 || meta == 0x9A || meta == 0x9B || meta == 0x9C || meta == 0x9D || meta == 0x9E || meta == 0x9F){
						if(b != 0) notes++;
					}
				}else
				if(meta == 0xFF){
					int type = byteStream.read() & 0xFF;
					int length = (int)getVaribaleLengthValue(byteStream);
					if(length > 0) {
						byte[] data2 = new byte[length];
						if(length > 0){
							byteStream.read(data2);
						}
					}
					if(type == 0x51) {
						int mpqn = bytesToInt(Arrays.copyOfRange(data, 0, 3));
						if(mpqn > 0 && total == 0) {
							initialBPM = 60000000.0F / (float)mpqn;
						}
					}
				}else
				if(meta == 0xF0 || meta == 0xF7){
					int lol = 0;
					byteStream.read();
					while(lol != 0xF7){
						lol = byteStream.read();
					}
				}else{
					byteStream.reset();
					byteStream.read();
				}
			}
			/*if(notes > 100000000 && notes < 200000000){
				System.out.println(i + "," + notes);
			}*/
		}
		stream.close();
		} catch(Exception e){
			System.err.println("Error counting them notes");
			e.printStackTrace();
			return 0;
		}
		return 1;
	}
	
	private long getVaribaleLengthValue(ByteArrayInputStream byteStream){
		long n = 0;
		boolean loop = true;
		while(loop){
			int curByte = byteStream.read() & 0xFF;
			n = (n << 7) | (curByte & 0x7F);
			if((curByte & 0x80) == 0){
				loop = false;
			}
		}
		return n;
	}
	
	private int bytesToInt(byte[] lol){
	    int value = 0;
	    for (int i = 0; i < lol.length; i++) {
	        int shift = (lol.length - 1 - i) * 8;
	        value += (lol[i] & 0x000000FF) << shift;
	    }
	    return value;
	}
}
