package theGhastModding.midiPlayer.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;

import theGhastModding.midiPlayer.main.TheGhastMidiPlayerMain;
import theGhastModding.midiPlayer.midi.MIDILoader;
import theGhastModding.synthesizer.main.TGMSynthesizer;

import javax.swing.JPanel;
import javax.swing.border.TitledBorder;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;

@SuppressWarnings("serial")
public class SettingsDialog extends JDialog {
	
	public static boolean fancyKeyboard = true;
	public static List<Color> trackColours;
	private SettingsDialog instance;
	private String customThemePath = "";
	private JCheckBox chckbxUseFancyPiano;
	public static boolean fancyNotes;
	private JCheckBox chckbxUseFancyNotes;
	private JComboBox<String> comboBox_2;
	private JLabel lblCustomTheme;
	private JPanel panel_1;
	private JCheckBox chckbxDisableSoundEffects;
	public static int renderingLimit = 0;
	public static boolean noteOff = false;
	public static boolean disableSoundEffects = false;
	private JCheckBox chckbxNoteoff;
	private JSpinner spinner;
	public static boolean channelColoring = false;
	private JCheckBox chckbxUseChannelColoring;
	private JSpinner notespeed;
	private JLabel lblSelectSynthesizer;
	private DefaultComboBoxModel<String> model;
	public static List<MidiDevice> availableDevices;
	public static int selectedDevice = 0;
	public static boolean useExternalSynth = false;
	private JCheckBox chckbxUseExternalSynth;
	private JComboBox<String> comboBox;
	private boolean midiSettingsLocked = false;
	private JLabel lblMaxEventsTo;
	private JSpinner maxEventsSpinner;
	public static int maxEventsToSynth = 250;
	public static boolean transparentNoets = false;
	private JCheckBox chckbxTransparentNotesunfancy;
	private JButton btnSendTelemetryData;
	
	public SettingsDialog(){
		super(TheGhastMidiPlayerMain.frame, "Settings");
		instance = this;
		trackColours = loadColorTheme("Default", true);
		getContentPane().setLayout(null);
		getContentPane().setPreferredSize(new Dimension(325,500));
		setLocationRelativeTo(TheGhastMidiPlayerMain.frame);
		
		JPanel panel = new JPanel();
		panel.setBorder(new TitledBorder(null, "Graphics", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		panel.setBounds(5, 9, 310, 224);
		getContentPane().add(panel);
		panel.setLayout(null);
		
		comboBox_2 = new JComboBox<String>();
		comboBox_2.setModel(new DefaultComboBoxModel<String>(new String[] {"Default", "Black and white", "Default desaturated", "Default more desaturated", "Emex", "Emex2", "Evil", "PFA", "Synthesia", "Custom"}));
		comboBox_2.setBounds(6, 62, 149, 20);
		panel.add(comboBox_2);
		
		chckbxUseFancyNotes = new JCheckBox("Use fancy Notes");
		chckbxUseFancyNotes.addChangeListener(new ChangeListener(){
			@Override
			public void stateChanged(ChangeEvent e) {
				fancyNotes = chckbxUseFancyNotes.isSelected();
			}
		});
		chckbxUseFancyNotes.setBounds(6, 17, 105, 23);
		panel.add(chckbxUseFancyNotes);
		chckbxUseFancyNotes.setSelected(true);
		
		JLabel lblColorTheme = new JLabel("Color Theme:");
		lblColorTheme.setBounds(6, 45, 64, 14);
		panel.add(lblColorTheme);
		
		lblCustomTheme = new JLabel("Custom Theme:");
		lblCustomTheme.setBounds(6, 88, 75, 14);
		panel.add(lblCustomTheme);
		
		JFileChooser imageSelector = new JFileChooser();
        FileFilter imageFilter = new FileNameExtensionFilter("Image files", 
                "png", "jpg", "jpeg", "gif");  
        imageSelector.setFileFilter(imageFilter);
		JButton btnLoadCustomTheme = new JButton("Load Custom Theme");
		btnLoadCustomTheme.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				imageSelector.setDialogTitle("Select custom color theme image");
				int option = imageSelector.showOpenDialog(instance);
				if(option == JFileChooser.APPROVE_OPTION){
					if(!imageSelector.getSelectedFile().exists()){
						JOptionPane.showMessageDialog(instance, "The selected file doesnt exist", "Error", JOptionPane.ERROR_MESSAGE);
						return;
					}
					customThemePath = imageSelector.getSelectedFile().getPath();
					lblCustomTheme.setText("Custom Theme: " + imageSelector.getSelectedFile().getName());
				}
			}
		});
		btnLoadCustomTheme.setBounds(6, 105, 129, 23);
		panel.add(btnLoadCustomTheme);
		
		chckbxUseFancyPiano = new JCheckBox("Use fancy piano texture");
		chckbxUseFancyPiano.addChangeListener(new ChangeListener(){
			@Override
			public void stateChanged(ChangeEvent e) {
				fancyKeyboard = chckbxUseFancyPiano.isSelected();
			}
		});
		chckbxUseFancyPiano.setSelected(true);
		chckbxUseFancyPiano.setBounds(143, 17, 141, 23);
		panel.add(chckbxUseFancyPiano);
		
		chckbxUseChannelColoring = new JCheckBox("Use channel coloring");
		chckbxUseChannelColoring.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent e) {
				channelColoring = chckbxUseChannelColoring.isSelected();
			}
		});
		chckbxUseChannelColoring.setToolTipText("Colors the notes by channel instead of by track");
		chckbxUseChannelColoring.setBounds(162, 61, 142, 23);
		panel.add(chckbxUseChannelColoring);
		
		JLabel lblNotespeed = new JLabel("Notespeed:");
		lblNotespeed.setBounds(162, 88, 122, 14);
		panel.add(lblNotespeed);
		
		notespeed = new JSpinner();
		notespeed.setModel(new SpinnerNumberModel(1, 1, 10, 1));
		notespeed.setBounds(160, 106, 124, 20);
		panel.add(notespeed);
		
		chckbxTransparentNotesunfancy = new JCheckBox("Transparent notes (unfancy notes only)");
		chckbxTransparentNotesunfancy.addChangeListener(new ChangeListener(){
			@Override
			public void stateChanged(ChangeEvent e) {
				transparentNoets = chckbxTransparentNotesunfancy.isSelected();
			}
		});
		chckbxTransparentNotesunfancy.setBounds(6, 135, 298, 23);
		panel.add(chckbxTransparentNotesunfancy);
		
		JCheckBox chckbxRoundNotesfancy = new JCheckBox("Round notes (fancy notes only)");
		chckbxRoundNotesfancy.setEnabled(false);
		chckbxRoundNotesfancy.setBounds(6, 161, 298, 23);
		panel.add(chckbxRoundNotesfancy);
		
		JCheckBox chckbxOtherShitYet = new JCheckBox("...");
		chckbxOtherShitYet.setEnabled(false);
		chckbxOtherShitYet.setBounds(6, 187, 298, 23);
		panel.add(chckbxOtherShitYet);
		
		JButton btnOk = new JButton("OK");
		btnOk.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				boolean previous = useExternalSynth;
				if(!midiSettingsLocked){
					if(chckbxUseExternalSynth.isSelected()){
						if(comboBox.getSelectedIndex() == 0){
							JOptionPane.showMessageDialog(instance, "You didn't select an external synthesizer", "Error", JOptionPane.ERROR_MESSAGE);
							return;
						}
						selectedDevice = comboBox.getSelectedIndex();
					}
					useExternalSynth = chckbxUseExternalSynth.isSelected();
				}
				saveSettings();
				if(comboBox_2.getSelectedIndex() == 9){
					trackColours = loadColorTheme(customThemePath, false);
				}else{
					trackColours = loadIntegratedColorThemeFromID(comboBox_2.getSelectedIndex());
				}
				MIDILoader.multiplier = Integer.parseInt(notespeed.getValue().toString());
				try {
					if(!useExternalSynth && TGMSynthesizer.isSynthStarted()){
						TGMSynthesizer.setRenderingLimit(renderingLimit);
						TGMSynthesizer.setUseFx((!disableSoundEffects));
						TGMSynthesizer.setOnlyReleaseOnOverlapingInstances(noteOff);
					}
				}catch(Exception e2){
					e2.printStackTrace();
					JOptionPane.showMessageDialog(instance, "Error applying one or more setting(s)", "Error", JOptionPane.ERROR_MESSAGE);
				}
				setVisible(false);
				maxEventsToSynth = Integer.parseInt(maxEventsSpinner.getValue().toString());
				if(useExternalSynth != previous){
					JOptionPane.showMessageDialog(instance, "In order to switch to an external/internal synth, the application has to be restarted", "Info", JOptionPane.INFORMATION_MESSAGE);
					System.exit(0);
				}
			}
		});
		btnOk.setBounds(5, 466, 89, 23);
		getContentPane().add(btnOk);
		
		panel_1 = new JPanel();
		panel_1.setBorder(new TitledBorder(null, "Audio", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		panel_1.setBounds(5, 244, 310, 211);
		getContentPane().add(panel_1);
		panel_1.setLayout(null);
		
		chckbxDisableSoundEffects = new JCheckBox("Disable sound effects");
		chckbxDisableSoundEffects.addChangeListener(new ChangeListener(){
			@Override
			public void stateChanged(ChangeEvent e) {
				disableSoundEffects = chckbxDisableSoundEffects.isSelected();
			}
		});
		chckbxDisableSoundEffects.setBounds(6, 17, 129, 23);
		panel_1.add(chckbxDisableSoundEffects);
		
		chckbxNoteoff = new JCheckBox("<html>Only release the oldest instance upon a note off event when there are overlapping instances of the note.");
		chckbxNoteoff.addChangeListener(new ChangeListener(){
			@Override
			public void stateChanged(ChangeEvent arg0) {
				noteOff = chckbxNoteoff.isSelected();
			}
		});
		chckbxNoteoff.setBounds(6, 43, 298, 31);
		panel_1.add(chckbxNoteoff);
		
		JLabel lblBassRenderingTime = new JLabel("Bass rendering time limit:");
		lblBassRenderingTime.setBounds(6, 86, 129, 14);
		panel_1.add(lblBassRenderingTime);
		
		spinner = new JSpinner();
		spinner.setToolTipText("0 = Infinite");
		spinner.addChangeListener(new ChangeListener(){
			@Override
			public void stateChanged(ChangeEvent arg0) {
				renderingLimit = Integer.parseInt(spinner.getValue().toString());
			}
		});
		spinner.setModel(new SpinnerNumberModel(0, 0, 100, 1));
		spinner.setBounds(145, 83, 53, 20);
		panel_1.add(spinner);
		
		JLabel label = new JLabel("%");
		label.setBounds(201, 86, 46, 14);
		panel_1.add(label);
		
		chckbxUseExternalSynth = new JCheckBox("Use external synth instead of internal synth");
		chckbxUseExternalSynth.setBounds(6, 109, 298, 23);
		panel_1.add(chckbxUseExternalSynth);
		
		lblSelectSynthesizer = new JLabel("Select synthesizer:");
		lblSelectSynthesizer.setBounds(6, 133, 298, 14);
		panel_1.add(lblSelectSynthesizer);
		
		model = new DefaultComboBoxModel<String>(new String[] {"null"});
		
		MidiDevice device = null;
		MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
		availableDevices = new ArrayList<MidiDevice>();
		for (int i = 0; i < infos.length; i++) {
		    try {
		    	device = MidiSystem.getMidiDevice(infos[i]);
		    	if(device.getMaxReceivers() == 0){
		    		continue;
		    	}
		        String name = device.getDeviceInfo().getName();
		        for(int j = 0; j < model.getSize(); j++){
		        	if(model.getElementAt(j).equals(name)){
		        		name = name + "_1";
		        		break;
		        	}
		        }
		        model.addElement(name);
		        availableDevices.add(MidiSystem.getMidiDevice(infos[i]));
		    } catch (Exception e) {
		          JOptionPane.showMessageDialog(TheGhastMidiPlayerMain.frame, "Error getting MIDI synthesizers", "Error", JOptionPane.ERROR_MESSAGE);
		          e.printStackTrace();
		          continue;
		    }
		}
		
		comboBox = new JComboBox<String>();
		comboBox.setModel(model);
		comboBox.setBounds(6, 158, 298, 20);
		panel_1.add(comboBox);
		
		lblMaxEventsTo = new JLabel("Max. events to send to synth in one frame:");
		lblMaxEventsTo.setBounds(6, 189, 216, 14);
		panel_1.add(lblMaxEventsTo);
		
		maxEventsSpinner = new JSpinner();
		maxEventsSpinner.setModel(new SpinnerNumberModel(10, 10, null, 1));
		maxEventsSpinner.setBounds(229, 186, 75, 20);
		panel_1.add(maxEventsSpinner);
		
		btnSendTelemetryData = new JButton("Send Telemetry Data");
		btnSendTelemetryData.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				JOptionPane.showMessageDialog(TheGhastMidiPlayerMain.frame, "Here, you can send your Telemetry (user statistics) to help optimize this application for your system. You can also use this to send a comment with suggestions, issues or your experiences with using the app. You can also report bugs here.", "Telemetry", JOptionPane.INFORMATION_MESSAGE);
				Telemetry t = new Telemetry();
				t.setVisible(true);
			}
		});
		btnSendTelemetryData.setBounds(182, 466, 133, 23);
		getContentPane().add(btnSendTelemetryData);
		
		setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
		loadSettings();
		setResizable(false);
		
		pack();
	}
	
	private void loadSettings(){
		try {
			File saveFile = new File("player_settings.dat");
			if(!saveFile.exists()){
				saveSettings();
				return;
			}
			DataInputStream dis = new DataInputStream(new FileInputStream(saveFile));
			fancyKeyboard = dis.readBoolean();
			chckbxUseFancyPiano.setSelected(fancyKeyboard);
			fancyNotes = dis.readBoolean();
			chckbxUseFancyNotes.setSelected(fancyNotes);
			customThemePath = dis.readUTF();
			File customTheme = new File(customThemePath);
			comboBox_2.setSelectedIndex(dis.readInt());
			if(comboBox_2.getSelectedIndex() == 9){
				if(!customTheme.exists()){
					JOptionPane.showMessageDialog(instance, "The custom color theme couldn't be found. Loading Default theme", "Error", JOptionPane.ERROR_MESSAGE);
					trackColours = loadIntegratedColorThemeFromID(0);
					comboBox_2.setSelectedIndex(0);
				}else{
					trackColours = loadColorTheme(customThemePath, false);
					lblCustomTheme.setText("Custom Theme: " + customTheme.getName());
				}
			}else{
				trackColours = loadIntegratedColorThemeFromID(comboBox_2.getSelectedIndex());
			}
			renderingLimit = dis.readInt();
			disableSoundEffects = dis.readBoolean();
			noteOff = dis.readBoolean();
			chckbxDisableSoundEffects.setSelected(disableSoundEffects);
			chckbxNoteoff.setSelected(noteOff);
			spinner.setValue(renderingLimit);
			chckbxUseChannelColoring.setSelected(dis.readBoolean());
			channelColoring = chckbxUseChannelColoring.isSelected();
			useExternalSynth = dis.readBoolean();
			maxEventsToSynth = dis.readInt();
			int selected = dis.readInt();
			if(!(selected >= model.getSize())){
				comboBox.setSelectedIndex(selected);
				selectedDevice = selected;
			}else{
				comboBox.setSelectedIndex(1);
				selectedDevice = 1;
			}
			maxEventsSpinner.setValue(maxEventsToSynth);
			chckbxUseExternalSynth.setSelected(useExternalSynth);
			transparentNoets = dis.readBoolean();
			chckbxTransparentNotesunfancy.setSelected(transparentNoets);
			dis.close();
		}catch(Exception e){
			e.printStackTrace();
			JOptionPane.showMessageDialog(instance, "Error loading settings", "Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		if(useExternalSynth){
			chckbxDisableSoundEffects.setEnabled(false);
			chckbxNoteoff.setEnabled(false);
			spinner.setEnabled(false);
		}
		try {
			if(!useExternalSynth){
				TGMSynthesizer.startSynth(44100, false);
				TGMSynthesizer.setVolume(100);
				TGMSynthesizer.setRenderingLimit(renderingLimit);
				TGMSynthesizer.setUseFx((!disableSoundEffects));
				TGMSynthesizer.setOnlyReleaseOnOverlapingInstances(noteOff);
			}
		}catch(Exception e2){
			e2.printStackTrace();
			JOptionPane.showMessageDialog(instance, "Error starting the internal synth and/or applying one or more setting(s)", "Error", JOptionPane.ERROR_MESSAGE);
		}
	}
	
	public void lockMidiSettings(){
		comboBox.setEnabled(false);
		chckbxUseExternalSynth.setEnabled(false);
		midiSettingsLocked = true;
	}
	
	public void unlockMidiSettings(){
		comboBox.setEnabled(true);
		chckbxUseExternalSynth.setEnabled(true);
		midiSettingsLocked = false;
	}
	
	private void saveSettings(){
		try{
			File saveFile = new File("player_settings.dat");
			DataOutputStream dos = new DataOutputStream(new FileOutputStream(saveFile));
			dos.writeBoolean(fancyKeyboard);
			dos.writeBoolean(fancyNotes);
			dos.writeUTF(customThemePath);
			dos.writeInt(comboBox_2.getSelectedIndex());
			dos.writeInt(renderingLimit);
			dos.writeBoolean(disableSoundEffects);
			dos.writeBoolean(noteOff);
			dos.writeBoolean(channelColoring);
			dos.writeBoolean(useExternalSynth);
			dos.writeInt(maxEventsToSynth);
			dos.writeInt(comboBox.getSelectedIndex());
			dos.writeBoolean(transparentNoets);
			dos.close();
		}catch(Exception e){
			e.printStackTrace();
			JOptionPane.showMessageDialog(instance, "Error saving settings", "Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
	}
	
	private List<Color> loadIntegratedColorThemeFromID(int id){
		if(id > 8){
			return null;
		}
		if(id == 0){
			return loadColorTheme("Default", true);
		}
		if(id == 1){
			return loadColorTheme("Black and White", true);
		}
		if(id == 2){
			return loadColorTheme("Default Desaturated", true);
		}
		if(id == 3){
			return loadColorTheme("Default More Desaturated", true);
		}
		if(id == 4){
			return loadColorTheme("Emex", true);
		}
		if(id == 5){
			return loadColorTheme("Emex2", true);
		}
		if(id == 6){
			return loadColorTheme("Evil", true);
		}
		if(id == 7){
			return loadColorTheme("PFA", true);
		}
		if(id == 8){
			return loadColorTheme("Synthesia", true);
		}
		return null;
	}
	
	private List<Color> loadColorTheme(String filename, boolean integrated) {
		BufferedImage colorTheme;
		List<Color> colors = new ArrayList<Color>();
		if(integrated){
			try {
				colorTheme = ImageIO.read(this.getClass().getResourceAsStream("/Color Themes/" + filename + ".png"));
			} catch(Exception e){
				JOptionPane.showMessageDialog(TheGhastMidiPlayerMain.frame, "Error loading color theme", "Error", JOptionPane.ERROR_MESSAGE);
				e.printStackTrace();
				return null;
			}
		}else{
			try {
				colorTheme = ImageIO.read(new File(filename));
			} catch(Exception e){
				JOptionPane.showMessageDialog(TheGhastMidiPlayerMain.frame, "Error loading color theme", "Error", JOptionPane.ERROR_MESSAGE);
				e.printStackTrace();
				return null;
			}
		}
		for(int i = 0; i < colorTheme.getHeight(); i++){
			for(int j = 0; j < colorTheme.getWidth(); j++){
				colors.add(new Color(colorTheme.getRGB(j, i)));
			}
		}
		return colors;
	}
}