/* Copyright (c) 2012,
 * Mathias Kuhring, KuhringM@rki.de, Robert Koch-Institut, Germany, 
 * All rights reserved. For details, please note the license.txt.
 */

package de.rki.ng4.ipig.gui;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileNameExtensionFilter;

import de.rki.ng4.ipig.GeneControl;
import de.rki.ng4.ipig.exceptions.ExitException;
import de.rki.ng4.ipig.Ipig;
import de.rki.ng4.ipig.tools.Configurator;
import java.awt.Toolkit;

/**
 * <p>This is a GUI class for GeneControl. It offers fields and buttons for necessary 
 * file indications and options.</p> 
 * 
 * <p>The start button will create a new thread which runs the GeneControl main routine 
 * while the GeneControl output is redirected to a text area.</p>
 * 
 * @author Mathias Kuhring
 *
 */
public class GeneControlGui {

	private JFrame frmGeneControlGui;
	private JTextField tf_geneAnnoFile;
	private JButton btn_geneAnnoFile;
	private JLabel lbl_geneAnnoFile;
	private JTextField tf_geneAaSeqFile;
	private JTextField tf_refSeqPath;
	private JTextField tf_outputPath;
	private OutputJTextArea outputWindow;
	private JPanel panel_geneAnnoFile;
	private JPanel resources;
	private JPanel panel_geneAaSeqFile;
	private JButton btn_geneAaSeqFile;
	private JLabel lbl_geneAaSeqFile;
	private JPanel panel_refSeqPath;
	private JButton btn_refSeqPath;
	private JLabel lbl_refSeqPath;
	private JPanel panel_outputPath;
	private JPanel output;
	private JButton btn_outputPath;
	private JLabel lbl_outputPath;
	private JPanel execution;
	private JPanel buttonPanel;
	private JButton startButton;
	private JButton stopButton;
	private JButton exitButton;
	private JScrollPane scrollPane;
	private Thread control;
	private JButton helpButton;
	private JPanel config;
	private JPanel configButtons;
	private JButton loadButton;
	private JButton saveButton;
	private JPanel menu;
	private JPanel menuPanel;
	private JPanel options;
	private JPanel panel_similarity;
	private JLabel lblMinimalSimilarity;
	private JSpinner spinner;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					GeneControlGui window = new GeneControlGui();
					window.frmGeneControlGui.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public GeneControlGui() {
		initialize();
		updateFields();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		// try native look and feel
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch(Exception e) {
			System.out.println("Error setting native LAF: " + e);
		}

		frmGeneControlGui = new JFrame();
		frmGeneControlGui.setIconImage(Toolkit.getDefaultToolkit().getImage(GeneControlGui.class.getResource("/images/rki.png")));
		frmGeneControlGui.setResizable(false);
		frmGeneControlGui.setTitle("GeneControl GUI");
		frmGeneControlGui.setBounds(100, 100, 500, 600);
		frmGeneControlGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frmGeneControlGui.getContentPane().setLayout(null);

		resources = new JPanel();
		resources.setBorder(new TitledBorder(null, "Resources", TitledBorder.CENTER, TitledBorder.TOP, null, null));
		resources.setBounds(10, 11, 472, 152);
		resources.setLayout(null);
		frmGeneControlGui.getContentPane().add(resources);

		panel_geneAnnoFile = new JPanel();
		panel_geneAnnoFile.setLayout(null);
		panel_geneAnnoFile.setBounds(10, 14, 455, 36);
		resources.add(panel_geneAnnoFile);

		tf_geneAnnoFile = new JTextField();
		tf_geneAnnoFile.setEditable(false);
		tf_geneAnnoFile.setColumns(10);
		tf_geneAnnoFile.setBounds(0, 14, 356, 20);
		panel_geneAnnoFile.add(tf_geneAnnoFile);

		btn_geneAnnoFile = new JButton("Open");
		btn_geneAnnoFile.setBounds(366, 13, 89, 23);
		panel_geneAnnoFile.add(btn_geneAnnoFile);
		btn_geneAnnoFile.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				File file = chooseFile(new File(tf_geneAnnoFile.getText()), new FileNameExtensionFilter("Text file", "txt"));
				if (file != null){
					Configurator.setProperty("FgeneAnnoFile",file.getAbsolutePath());
					updateFields();
				}
			}
		});

		lbl_geneAnnoFile = new JLabel("UCSC/Ensembl Genes table file:");
		lbl_geneAnnoFile.setBounds(0, 0, 374, 14);
		panel_geneAnnoFile.add(lbl_geneAnnoFile);

		panel_geneAaSeqFile = new JPanel();
		panel_geneAaSeqFile.setLayout(null);
		panel_geneAaSeqFile.setBounds(10, 59, 455, 36);
		resources.add(panel_geneAaSeqFile);

		tf_geneAaSeqFile = new JTextField();
		tf_geneAaSeqFile.setText("");
		tf_geneAaSeqFile.setEditable(false);
		tf_geneAaSeqFile.setColumns(10);
		tf_geneAaSeqFile.setBounds(0, 14, 356, 20);
		panel_geneAaSeqFile.add(tf_geneAaSeqFile);

		btn_geneAaSeqFile = new JButton("Open");
		btn_geneAaSeqFile.setBounds(366, 13, 89, 23);
		btn_geneAaSeqFile.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				File file = chooseFile(new File(tf_geneAaSeqFile.getText()), new FileNameExtensionFilter("Text file", "txt"));
				if (file != null){
					Configurator.setProperty("FgeneAaSeqFile",file.getAbsolutePath());
					updateFields();
				}
			}
		});
		panel_geneAaSeqFile.add(btn_geneAaSeqFile);

		lbl_geneAaSeqFile = new JLabel("UCSC/Ensembl Amino Acid Sequences table file:");
		lbl_geneAaSeqFile.setBounds(0, 0, 374, 14);
		panel_geneAaSeqFile.add(lbl_geneAaSeqFile);

		panel_refSeqPath = new JPanel();
		panel_refSeqPath.setLayout(null);
		panel_refSeqPath.setBounds(10, 106, 455, 36);
		resources.add(panel_refSeqPath);

		tf_refSeqPath = new JTextField();
		tf_refSeqPath.setText("");
		tf_refSeqPath.setEditable(false);
		tf_refSeqPath.setColumns(10);
		tf_refSeqPath.setBounds(0, 14, 356, 20);
		panel_refSeqPath.add(tf_refSeqPath);

		btn_refSeqPath = new JButton("Open");
		btn_refSeqPath.setBounds(366, 13, 89, 23);
		btn_refSeqPath.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				File file = choosePath(new File(tf_refSeqPath.getText()));
				if (file != null){
					Configurator.setProperty("refSeqPath",file.getAbsolutePath());
					updateFields();
				}
			}
		});
		panel_refSeqPath.add(btn_refSeqPath);

		lbl_refSeqPath = new JLabel("Reference chromosomes path:");
		lbl_refSeqPath.setBounds(0, 0, 374, 14);
		panel_refSeqPath.add(lbl_refSeqPath);

		output = new JPanel();
		output.setBorder(new TitledBorder(null, "Output", TitledBorder.CENTER, TitledBorder.TOP, null, null));
		output.setLayout(null);
		output.setBounds(10, 174, 472, 59);
		frmGeneControlGui.getContentPane().add(output);

		panel_outputPath = new JPanel();
		panel_outputPath.setLayout(null);
		panel_outputPath.setBounds(10, 14, 455, 36);
		output.add(panel_outputPath);

		tf_outputPath = new JTextField();
		tf_outputPath.setText("");
		tf_outputPath.setEditable(false);
		tf_outputPath.setColumns(10);
		tf_outputPath.setBounds(0, 14, 356, 20);
		panel_outputPath.add(tf_outputPath);

		btn_outputPath = new JButton("Open");
		btn_outputPath.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				File file = choosePath(new File(tf_outputPath.getText()));
				if (file != null){
					Configurator.setProperty("FoutputPath",file.getAbsolutePath());
					updateFields();
				}
			}
		});
		btn_outputPath.setBounds(366, 13, 89, 23);
		panel_outputPath.add(btn_outputPath);

		lbl_outputPath = new JLabel("Output Path (optional):");
		lbl_outputPath.setBounds(0, 0, 374, 14);
		panel_outputPath.add(lbl_outputPath);

		options = new JPanel();
		options.setBorder(new TitledBorder(null, "Options", TitledBorder.CENTER, TitledBorder.TOP, null, null));
		options.setBounds(10, 244, 474, 44);
		frmGeneControlGui.getContentPane().add(options);
		options.setLayout(null);

		panel_similarity = new JPanel();
		panel_similarity.setOpaque(false);
		panel_similarity.setBounds(162, 14, 150, 20);
		options.add(panel_similarity);
		panel_similarity.setLayout(null);

		lblMinimalSimilarity = new JLabel("Minimal similarity:");
		lblMinimalSimilarity.setBounds(0, 3, 82, 14);
		panel_similarity.add(lblMinimalSimilarity);

		spinner = new JSpinner();
		spinner.setModel(new SpinnerNumberModel(0.95, 0.01, 1.0, 0.01));
		spinner.setBounds(92, 0, 58, 20);
		spinner.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent arg0) {
				Configurator.setProperty("minSimilarity", spinner.getValue().toString());
			}
		});
		panel_similarity.add(spinner);

		execution = new JPanel();
		execution.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Execution", TitledBorder.CENTER, TitledBorder.TOP, null, new Color(0, 70, 213)));
		execution.setLayout(null);
		execution.setBounds(10, 299, 147, 44);
		frmGeneControlGui.getContentPane().add(execution);

		buttonPanel = new JPanel();
		buttonPanel.setOpaque(false);
		buttonPanel.setLayout(null);
		buttonPanel.setBounds(6, 14, 135, 23);
		execution.add(buttonPanel);

		// start button creates and starts a control thread
		// parameters are the checked in GeneControl
		startButton = new JButton("Start");
		startButton.setBounds(0, 0, 65, 23);
		startButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				control = new Control();
				control.start();
			}
		});
		buttonPanel.add(startButton);

		// stop button sets a breakpoint, which is checked by GeneControl periodically.
		// if the control thread is already dead (e.g. crashed), the button restores the interface by enabling the other buttons itself.
		stopButton = new JButton("Stop");
		stopButton.setEnabled(false);
		stopButton.setBounds(70, 0, 65, 23);
		stopButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if (control != null && control.isAlive()){
					Configurator.setBreak(true);
					stopButton.setEnabled(false);
				}
				else{
					enableButtons(true);
					Configurator.setBreak(false);
					outputWindow.redirectSystemStreams(false, false);
				}	
			}
		});
		buttonPanel.add(stopButton);

		config = new JPanel();
		config.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Configuration", TitledBorder.CENTER, TitledBorder.TOP, null, new Color(0, 70, 213)));
		config.setLayout(null);
		config.setBounds(173, 299, 147, 44);
		frmGeneControlGui.getContentPane().add(config);

		configButtons = new JPanel();
		configButtons.setLayout(null);
		configButtons.setOpaque(false);
		configButtons.setBounds(6, 14, 135, 23);
		config.add(configButtons);

		// config load button
		loadButton = new JButton("Load");
		loadButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				File file = chooseFile(new File(""), new FileNameExtensionFilter("configuration file", "conf", "txt"));
				if (file != null){
					try {
						Configurator.loadProperties(file.getAbsolutePath());
						updateFields();
						outputWindow.setText("configuration loaded");
					} catch (ExitException e) {
						outputWindow.setText("error: couldn't load config (" + e.getMessage() + ")");
					}
				}
			}
		});
		loadButton.setBounds(0, 0, 65, 23);
		configButtons.add(loadButton);

		// config save button
		saveButton = new JButton("Save");
		saveButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				File file = saveFile(new File(""), new FileNameExtensionFilter("configuration file", "conf", "txt"));
				if (file != null){
					try {
						Configurator.saveProperties(file.getAbsolutePath());
						outputWindow.setText("configuration saved");
					} catch (ExitException e) {
						outputWindow.setText("error: couldn't save config (" + e.getMessage() + ")");
					}
				}
			}
		});
		saveButton.setBounds(70, 0, 65, 23);
		configButtons.add(saveButton);

		menu = new JPanel();
		menu.setBorder(new TitledBorder(null, "Menu", TitledBorder.CENTER, TitledBorder.TOP, null, null));
		menu.setLayout(null);
		menu.setBounds(335, 299, 147, 44);
		frmGeneControlGui.getContentPane().add(menu);

		menuPanel = new JPanel();
		menuPanel.setLayout(null);
		menuPanel.setOpaque(false);
		menuPanel.setBounds(6, 14, 135, 23);
		menu.add(menuPanel);

		// help button, loads a help text from the resources to the output window
		helpButton = new JButton("Help");
		helpButton.setBounds(0, 0, 65, 23);
		menuPanel.add(helpButton);
		helpButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				try {
					String help = "/help/genecontrolgui_help.txt";
					InputStream is = Ipig.class.getResourceAsStream(help);
					BufferedReader helpbf = new BufferedReader(new InputStreamReader(is));
					outputWindow.setText("");
					while (helpbf.ready()){
						outputWindow.append(helpbf.readLine() + "\n");
					}
					outputWindow.setCaretPosition(0); 
					is.close();
				} catch (IOException e) {
					outputWindow.append("error: " + e.getLocalizedMessage());
				}
			}
		});

		// exit button
		exitButton = new JButton("Exit");
		exitButton.setBounds(70, 0, 65, 23);
		menuPanel.add(exitButton);
		exitButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				System.exit(0);
			}
		});

		// the output window displays the progress or errors.
		// OutputJTextArea is a extended JTextArea which catches and displays the System.out stream,
		// so finally the output from the GeneControl.
		outputWindow = new OutputJTextArea();
		outputWindow.setEditable(false);
		outputWindow.setSize(473, 162);
		outputWindow.setLocation(10, 400);
		outputWindow.setLineWrap(true);

		scrollPane = new JScrollPane(outputWindow);
		scrollPane.setBounds(10, 354, 473, 203);
		frmGeneControlGui.getContentPane().add(scrollPane);
	}

	// a thread running GeneControl and controlling the output and buttons meanwhile
	class Control extends Thread{
		@Override public void run(){
			Configurator.setBreak(false);
			enableButtons(false);
			outputWindow.setText("");
			outputWindow.redirectSystemStreams(true, true);

			try {
				new GeneControl().run();
			} catch (ExitException e) {
				System.out.println("\n" + e.getMessage());
			}

			enableButtons(true);
			Configurator.setBreak(false);
			outputWindow.redirectSystemStreams(false, false);
		}
	}

	// enabling/disabling of buttons for running the control
	// stop button is always set contradictory
	private void enableButtons(boolean b){
		btn_geneAaSeqFile.setEnabled(b);
		btn_geneAnnoFile.setEnabled(b);
		btn_refSeqPath.setEnabled(b);
		btn_outputPath.setEnabled(b);
		startButton.setEnabled(b);
		stopButton.setEnabled(!b); // not b!
		loadButton.setEnabled(b);
		saveButton.setEnabled(b);
		helpButton.setEnabled(b);
		exitButton.setEnabled(b);
	}

	// updates the filename and parameter fields after selecting a file/folder or loading a configuration file
	private void updateFields(){
		tf_geneAnnoFile.setText(Configurator.getProperty("FgeneAnnoFile", ""));
		tf_geneAaSeqFile.setText(Configurator.getProperty("FgeneAaSeqFile", ""));
		tf_refSeqPath.setText(Configurator.getProperty("refSeqPath", ""));
		tf_outputPath.setText(Configurator.getProperty("FoutputPath", ""));

		try{
			spinner.setValue(Double.parseDouble(Configurator.getProperty("minSimilarity", "0.95")));
		}
		catch (NumberFormatException e){
			outputWindow.setText("error: couldn't parse \"minSimilarity\" from config file \n" +
					"(" + e.getMessage() + ") - set parameter manually!");
		}
	}

	// creation of a FileChooser for the file selection/open buttons
	private File chooseFile(File dir, FileNameExtensionFilter filter){
		JFileChooser fc = new JFileChooser();
		fc.setFileFilter(filter);
		fc.setCurrentDirectory(dir);

		int state = fc.showOpenDialog( null );

		if ( state == JFileChooser.APPROVE_OPTION )
		{
			return fc.getSelectedFile();
		}
		else
			return null;
	}

	// creation of a FileChooser for the config save button
	private File saveFile(File dir, FileNameExtensionFilter filter){
		JFileChooser fc = new JFileChooser();
		fc.setFileFilter(filter);
		fc.setCurrentDirectory(dir);
		fc.setSelectedFile( new File("mapper.conf") );

		while ( fc.showSaveDialog( null ) == JFileChooser.APPROVE_OPTION ){
			File file = fc.getSelectedFile();
			if (file.exists())  {  
				int answer = JOptionPane.showConfirmDialog(null, "Replace existing file?");  
				if (answer == JOptionPane.OK_OPTION)  
					return fc.getSelectedFile();
				if (answer == JOptionPane.CANCEL_OPTION)
					break;
			} 
			else
				return fc.getSelectedFile();
		}
		return null;
	}

	// creation of a FileChooser for the path selection buttons
	private File choosePath(File dir){
		JFileChooser fc = new JFileChooser();
		fc.setCurrentDirectory(dir);
		fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

		int state = fc.showOpenDialog( null );

		if ( state == JFileChooser.APPROVE_OPTION )
		{
			return fc.getSelectedFile();
		}
		else
			return null;
	}
}
