package gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.ScrollPane;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedHashSet;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import javax.swing.border.TitledBorder;

import jebl.evolution.graphs.Node;
import jebl.evolution.io.ImportException;
import jebl.evolution.io.NexusImporter;
import jebl.evolution.trees.RootedTree;

import templates.MapBackground;
import templates.TimeSlicerToKML;
import templates.TimeSlicerToProcessing;
import utils.Utils;
import app.SpreadApp;
import checks.TimeSlicerSanityCheck;
import colorpicker.swing.ColorPicker;

@SuppressWarnings("serial")
public class TimeSlicerTab extends JPanel {

	// Shared frame
	private SpreadApp frame;
	
	// Sizing constants
	private final int leftPanelWidth = 260;
	private final int leftPanelHeight = 1000;
	private final int spinningPanelHeight = 20;
	private final int mapImageWidth = MapBackground.MAP_IMAGE_WIDTH;
	private final int mapImageHeight = MapBackground.MAP_IMAGE_HEIGHT;
	private final Dimension minimumDimension = new Dimension(0, 0);

	// Colors
	private Color backgroundColor;
	private Color polygonsMaxColor;
	private Color branchesMaxColor;
	private Color polygonsMinColor;
	private Color branchesMinColor;

	// Strings for paths
	private String treeFilename;
	private String treesFilename;
	private String sliceHeightsFilename;
	private File workingDirectory;

	// Radio buttons
	private JRadioButton firstAnalysisRadioButton;
	private JRadioButton secondAnalysisRadioButton;

	// Switchers
	private int analysisType;

	// Strings for radio buttons
	private String firstAnalysis;
	private String secondAnalysis;

	// Text fields
	private JTextField burnInParser;
	private JTextField numberOfIntervalsParser;
	private JTextField kmlPathParser;
	private JTextField maxAltMappingParser;
	private JTextField HPDParser;
	private JTextField timescalerParser;

	// Spinners
	private DateSpinner dateSpinner;

	// Buttons
	private JButton generateKml;
	private JButton openTree;
	private JButton openTrees;
	private JButton generateProcessing;
	private JButton saveProcessingPlot;
	private JButton polygonsMaxColorChooser;
	private JButton branchesMaxColorChooser;
	private JButton polygonsMinColorChooser;
	private JButton branchesMinColorChooser;
	private JButton loadTimeSlices;

	// Sliders
	private JSlider branchesWidthParser;
	private JSlider gridSizeParser;

	// Combo boxes
	private JComboBox eraParser;
	private JComboBox coordinatesNameParser;
	private JComboBox rateAttNameParser;
	private JComboBox precisionAttNameParser;
	
	// checkboxes
	private JCheckBox trueNoiseParser;

	// left tools pane
	private JPanel leftPanel;
	private JPanel tmpPanel;
	private SpinningPanel sp;
	private JPanel tmpPanelsHolder;

	// Processing pane
	private TimeSlicerToProcessing timeSlicerToProcessing;

	// Progress bar
	private JProgressBar progressBar;

	public TimeSlicerTab(SpreadApp spreadApp) {

		this.frame = spreadApp;
		
		// Setup miscallenous
		setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
		GridBagConstraints c = new GridBagConstraints();

		// Setup colors
		backgroundColor = new Color(231, 237, 246);
		polygonsMaxColor = new Color(50, 255, 255, 255);
		branchesMaxColor = new Color(255, 5, 50, 255);
		polygonsMinColor = new Color(0, 0, 0, 100);
		branchesMinColor = new Color(0, 0, 0, 255);

		// Setup strings for paths
		treeFilename = null;
		treesFilename = null;
		sliceHeightsFilename = null;
		workingDirectory = null;

		// Setup strings for radio buttons
		firstAnalysis = new String("MCC tree slice heights");
		secondAnalysis = new String("Custom slice heights");

		// Setup radio buttons
		firstAnalysisRadioButton = new JRadioButton(firstAnalysis);
		secondAnalysisRadioButton = new JRadioButton(secondAnalysis);

		// Setup switchers
		analysisType = TimeSlicerToKML.FIRST_ANALYSIS;

		// Setup text fields
		burnInParser = new JTextField("500", 10);
		coordinatesNameParser = new JComboBox(new String[] {"location"});
		rateAttNameParser = new JComboBox(new String[] {"rate"});
		precisionAttNameParser = new JComboBox(new String[] {"precision"});
		numberOfIntervalsParser = new JTextField("10", 5);
		maxAltMappingParser = new JTextField("500000", 5);
		kmlPathParser = new JTextField("output.kml", 10);
		HPDParser = new JTextField("0.8", 5);
		timescalerParser = new JTextField("1.0", 10);

		// Setup buttons
		generateKml = new JButton("Generate", SpreadApp.nuclearIcon);
		openTree = new JButton("Open", SpreadApp.treeIcon);
		openTrees = new JButton("Open", SpreadApp.treesIcon);
		generateProcessing = new JButton("Plot", SpreadApp.processingIcon);
		saveProcessingPlot = new JButton("Save", SpreadApp.saveIcon);
		polygonsMaxColorChooser = new JButton("Setup max");
		branchesMaxColorChooser = new JButton("Setup max");
		polygonsMinColorChooser = new JButton("Setup min");
		branchesMinColorChooser = new JButton("Setup min");
		loadTimeSlices = new JButton("Load", SpreadApp.timeSlicesIcon);

		// Setup sliders
		branchesWidthParser = new JSlider(JSlider.HORIZONTAL, 2, 10, 4);
		branchesWidthParser.setMajorTickSpacing(2);
		branchesWidthParser.setMinorTickSpacing(1);
		branchesWidthParser.setPaintTicks(true);
		branchesWidthParser.setPaintLabels(true);

		gridSizeParser = new JSlider(JSlider.HORIZONTAL, 100, 200, 100);
		gridSizeParser.setMajorTickSpacing(50);
		gridSizeParser.setMinorTickSpacing(10);
		gridSizeParser.setPaintTicks(true);
		gridSizeParser.setPaintLabels(true);

		// Setup progress bar & checkboxes
		progressBar = new JProgressBar();
		trueNoiseParser = new JCheckBox();

		// Left tools pane
		leftPanel = new JPanel();
		leftPanel.setBackground(backgroundColor);
		leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));

		// Action listeners
		openTree.addActionListener(new ListenOpenTree());
		openTrees.addActionListener(new ListenOpenTrees());
		generateKml.addActionListener(new ListenGenerateKml());
		generateProcessing.addActionListener(new ListenGenerateProcessing());
		saveProcessingPlot.addActionListener(new ListenSaveProcessingPlot());
		polygonsMaxColorChooser
				.addActionListener(new ListenPolygonsMaxColorChooser());
		branchesMaxColorChooser
				.addActionListener(new ListenBranchesMaxColorChooser());
		polygonsMinColorChooser
				.addActionListener(new ListenPolygonsMinColorChooser());
		branchesMinColorChooser
				.addActionListener(new ListenBranchesMinColorChooser());
		loadTimeSlices.addActionListener(new ListenLoadTimeSlices());

		// ////////////////////////////
		// ---CHOOSE ANALYSIS TYPE---//
		// ////////////////////////////

		tmpPanelsHolder = new JPanel();
		tmpPanelsHolder.setLayout(new BoxLayout(tmpPanelsHolder,
				BoxLayout.Y_AXIS));

		tmpPanel = new JPanel();
		tmpPanel.setLayout(new GridLayout(2, 1));
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Choose analysis type:"));

		ButtonGroup buttonGroup = new ButtonGroup();

		firstAnalysisRadioButton.setToolTipText(firstAnalysis);
		firstAnalysisRadioButton.setActionCommand(firstAnalysis);
		firstAnalysisRadioButton
				.addActionListener(new ChooseAnalysisTypeListener());
		firstAnalysisRadioButton.setSelected(true);
		buttonGroup.add(firstAnalysisRadioButton);
		tmpPanel.add(firstAnalysisRadioButton);

		secondAnalysisRadioButton.setToolTipText(secondAnalysis);
		secondAnalysisRadioButton.setActionCommand(secondAnalysis);
		secondAnalysisRadioButton
				.addActionListener(new ChooseAnalysisTypeListener());
		buttonGroup.add(secondAnalysisRadioButton);
		tmpPanel.add(secondAnalysisRadioButton);

		tmpPanelsHolder.add(tmpPanel);

		sp = new SpinningPanel(tmpPanelsHolder, "   Analysis", new Dimension(
				leftPanelWidth, spinningPanelHeight));
		sp.showBottom(false);
		leftPanel.add(sp);

		// /////////////
		// ---INPUT---//
		// /////////////

		tmpPanelsHolder = new JPanel();
		tmpPanelsHolder.setLayout(new BoxLayout(tmpPanelsHolder,
				BoxLayout.Y_AXIS));

		tmpPanel = new JPanel();
		tmpPanel.setLayout(new GridBagLayout());
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Load slice heights / tree file:"));
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = 0;
		loadTimeSlices.setEnabled(false);
		tmpPanel.add(loadTimeSlices, c);
		c.gridx = 2;
		c.gridy = 0;
		tmpPanel.add(openTree, c);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Load trees file:"));
		tmpPanel.add(openTrees);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setLayout(new GridBagLayout());
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Most recent sampling date:"));
		dateSpinner = new DateSpinner();
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = 0;
		tmpPanel.add(dateSpinner, c);
		String era[] = { "AD", "BC" };
		eraParser = new JComboBox(era);
		c.gridx = 2;
		c.gridy = 0;
		tmpPanel.add(eraParser, c);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Coordinate attribute name:"));
		tmpPanel.add(coordinatesNameParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Rate attribute name:"));
		tmpPanel.add(rateAttNameParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Precision attribute name:"));
		tmpPanel.add(precisionAttNameParser);
		tmpPanelsHolder.add(tmpPanel);
		
		sp = new SpinningPanel(tmpPanelsHolder, "   Input", new Dimension(
				leftPanelWidth, spinningPanelHeight));
		sp.showBottom(true);
		leftPanel.add(sp);

		// ////////////////////////
		// ---BRANCHES MAPPING---//
		// ////////////////////////

		tmpPanelsHolder = new JPanel();
		tmpPanelsHolder.setLayout(new BoxLayout(tmpPanelsHolder,
				BoxLayout.Y_AXIS));

		// Branches color mapping:
		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setLayout(new GridBagLayout());
		tmpPanel.setBorder(new TitledBorder("Branches color mapping:"));
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = 0;
		tmpPanel.add(branchesMinColorChooser, c);
		c.gridx = 2;
		c.gridy = 0;
		tmpPanel.add(branchesMaxColorChooser, c);
		tmpPanelsHolder.add(tmpPanel);

		// Branches width:
		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Branches width:"));
		tmpPanel.add(branchesWidthParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Maximal altitude mapping:"));
		tmpPanel.add(maxAltMappingParser);
		tmpPanelsHolder.add(tmpPanel);

		sp = new SpinningPanel(tmpPanelsHolder, "   Branches mapping",
				new Dimension(leftPanelWidth, spinningPanelHeight));
		sp.showBottom(false);
		leftPanel.add(sp);

		// ////////////////////////
		// ---POLYGONS MAPPING---//
		// ////////////////////////

		tmpPanelsHolder = new JPanel();
		tmpPanelsHolder.setLayout(new BoxLayout(tmpPanelsHolder,
				BoxLayout.Y_AXIS));

		// Polygons color mapping:
		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setLayout(new GridBagLayout());
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Polygons color mapping:"));
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = 0;
		tmpPanel.add(polygonsMinColorChooser, c);
		c.gridx = 2;
		c.gridy = 0;
		tmpPanel.add(polygonsMaxColorChooser);
		tmpPanelsHolder.add(tmpPanel);

		sp = new SpinningPanel(tmpPanelsHolder, "   Polygons mapping",
				new Dimension(leftPanelWidth, spinningPanelHeight));
		sp.showBottom(false);
		leftPanel.add(sp);

		// ////////////////////
		// ---COMPUTATIONS---//
		// ////////////////////

		tmpPanelsHolder = new JPanel();
		tmpPanelsHolder.setLayout(new BoxLayout(tmpPanelsHolder,
				BoxLayout.Y_AXIS));

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Use true noise:"));
		trueNoiseParser.setSelected(true);
		tmpPanel.add(trueNoiseParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Specify burn-in:"));
		tmpPanel.add(burnInParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("HPD:"));
		tmpPanel.add(HPDParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Time scale multiplier:"));
		tmpPanel.add(timescalerParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Number of intervals:"));
		tmpPanel.add(numberOfIntervalsParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Grid size:"));
		tmpPanel.add(gridSizeParser);
		tmpPanelsHolder.add(tmpPanel);

		sp = new SpinningPanel(tmpPanelsHolder, "   Computations",
				new Dimension(leftPanelWidth, spinningPanelHeight));
		sp.showBottom(false);
		leftPanel.add(sp);

		// //////////////
		// ---OUTPUT---//
		// //////////////

		tmpPanelsHolder = new JPanel();
		tmpPanelsHolder.setLayout(new BoxLayout(tmpPanelsHolder,
				BoxLayout.Y_AXIS));

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("KML name:"));
		tmpPanel.add(kmlPathParser);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setLayout(new GridBagLayout());
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Generate KML / Plot map:"));
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = 0;
		tmpPanel.add(generateKml, c);
		c.gridx = 2;
		c.gridy = 0;
		tmpPanel.add(generateProcessing, c);
		c.ipady = 7;
		c.gridwidth = 3;
		c.gridx = 0;
		c.gridy = 1;
		tmpPanel.add(progressBar, c);
		tmpPanelsHolder.add(tmpPanel);

		tmpPanel = new JPanel();
		tmpPanel.setMaximumSize(new Dimension(leftPanelWidth, 100));
		tmpPanel.setBackground(backgroundColor);
		tmpPanel.setBorder(new TitledBorder("Save plot:"));
		tmpPanel.add(saveProcessingPlot);
		tmpPanelsHolder.add(tmpPanel);

		sp = new SpinningPanel(tmpPanelsHolder, "   Output", new Dimension(
				leftPanelWidth, spinningPanelHeight));
		sp.showBottom(false);
		leftPanel.add(sp);

		// ////////////////////////
		// ---LEFT SCROLL PANE---//
		// ////////////////////////

		JScrollPane leftScrollPane = new JScrollPane(leftPanel,
				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		leftScrollPane.setMinimumSize(minimumDimension);
		leftScrollPane.setMaximumSize(new Dimension(leftPanelWidth,
				leftPanelHeight));

		/**
		 * Processing pane
		 * */
		timeSlicerToProcessing = new TimeSlicerToProcessing();
		timeSlicerToProcessing.setPreferredSize(new Dimension(mapImageWidth,
				mapImageHeight));

//		if (System.getProperty("java.runtime.name").toLowerCase().startsWith(
//				"openjdk")) {
//
//			JScrollPane rightScrollPane = new JScrollPane(
//					timeSlicerToProcessing,
//					JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
//					JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
//			rightScrollPane.setMinimumSize(minimumDimension);
//
//			SplitPane splitPane = new SplitPane(JSplitPane.HORIZONTAL_SPLIT,
//					leftScrollPane, rightScrollPane);
//			splitPane.setDividerLocation(leftPanelWidth);
//
//			this.add(splitPane);
//
//		} else {

			ScrollPane rightScrollPane = new ScrollPane(
					ScrollPane.SCROLLBARS_ALWAYS);
			rightScrollPane.add(timeSlicerToProcessing);
			rightScrollPane.setMinimumSize(minimumDimension);

			SplitPane splitPane = new SplitPane(JSplitPane.HORIZONTAL_SPLIT,
					leftScrollPane, rightScrollPane);
			splitPane.setDividerLocation(leftPanelWidth);

			this.add(splitPane);

//		}

	}// END: Constructor

	class ChooseAnalysisTypeListener implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			if (ev.getActionCommand() == firstAnalysis) {

				loadTimeSlices.setEnabled(false);
				openTree.setEnabled(true);
				branchesMinColorChooser.setEnabled(true);
				branchesMaxColorChooser.setEnabled(true);
				branchesWidthParser.setEnabled(true);
				maxAltMappingParser.setEnabled(true);
				numberOfIntervalsParser.setEnabled(true);

				analysisType = TimeSlicerToKML.FIRST_ANALYSIS;
				
				frame.setStatus(firstAnalysis + " analysis selected");

			} else if (ev.getActionCommand() == secondAnalysis) {

				loadTimeSlices.setEnabled(true);
				openTree.setEnabled(false);
				branchesMinColorChooser.setEnabled(false);
				branchesMaxColorChooser.setEnabled(false);
				branchesWidthParser.setEnabled(false);
				maxAltMappingParser.setEnabled(false);
				numberOfIntervalsParser.setEnabled(false);

				analysisType = TimeSlicerToKML.SECOND_ANALYSIS;
				
				frame.setStatus(secondAnalysis + " analysis selected");

			} else {
				throw new RuntimeException("Unimplemented analysis type selected!");
			}

		}// END: actionPerformed
	}// END: ChooseAnalysisTypeListener

	private class ListenLoadTimeSlices implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			try {

				JFileChooser chooser = new JFileChooser();
				chooser.setDialogTitle("Loading slice heights file...");
				chooser.setCurrentDirectory(workingDirectory);

				int returnVal = chooser.showOpenDialog(Utils.getActiveFrame());
				if (returnVal == JFileChooser.APPROVE_OPTION) {
				
				File file = chooser.getSelectedFile();
				sliceHeightsFilename = file.getAbsolutePath();
				
				frame.setStatus("Opened " + sliceHeightsFilename);

				File tmpDir = chooser.getCurrentDirectory();

				if (tmpDir != null) {
					workingDirectory = tmpDir;
				}

				} else {
					System.out.println("Could not Open!");
				}
				
			} catch (Exception e) {
				Utils.handleException(e, e.getMessage());
			}// END: try-catch block
				
		}
	}// END: ListenLoadTimeSlices

	private class ListenOpenTree implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			try {

				String[] treeFiles = new String[] { "tre", "tree", "trees" };

				JFileChooser chooser = new JFileChooser();
				chooser.setDialogTitle("Opening tree file...");
				chooser.setMultiSelectionEnabled(false);
				chooser.addChoosableFileFilter(new SimpleFileFilter(treeFiles,
						"Tree files (*.tree(s), *.tre)"));
				chooser.setCurrentDirectory(workingDirectory);

				int returnVal = chooser.showOpenDialog(Utils.getActiveFrame());
				if (returnVal == JFileChooser.APPROVE_OPTION) {
					
					File file = chooser.getSelectedFile();
					treeFilename = file.getAbsolutePath();
					frame.setStatus("Opened " + treeFilename + "\n");

					File tmpDir = chooser.getCurrentDirectory();

					if (tmpDir != null) {
						workingDirectory = tmpDir;
					}

				} else {
					System.out.println("Could not Open! \n");
				}

			} catch (Exception e) {
				Utils.handleException(e, e.getMessage());
			}// END: try-catch block

		}// END: actionPerformed
	}// END: ListenOpenTree

	//TODO: move all the tree parsing to separate tree parser class with getters for all the attributes we need
	private void populateAttributeComboboxes() {
		
		try {
		
			NexusImporter importer = new NexusImporter(new FileReader(treesFilename));
			RootedTree tree = (RootedTree) importer.importNextTree();
			
			LinkedHashSet<String> uniqueAttributes = new LinkedHashSet<String>();
			
			for (Node node : tree.getNodes()) {
				if (!tree.isRoot(node)) {
				
					uniqueAttributes.addAll(node.getAttributeNames());
					
				}
			}

			// re-initialise comboboxes
			ComboBoxModel coordinatesNameParserModel = new DefaultComboBoxModel(uniqueAttributes.toArray(new String[0]));			
			coordinatesNameParser.setModel(coordinatesNameParserModel);
			
			ComboBoxModel rateAttNameParserModel = new DefaultComboBoxModel(uniqueAttributes.toArray(new String[0]));			
			rateAttNameParser.setModel(rateAttNameParserModel);
	
			ComboBoxModel precisionAttNameParserModel = new DefaultComboBoxModel(tree.getAttributeNames().toArray(new String[0]));			
			precisionAttNameParser.setModel(precisionAttNameParserModel);
			
		} catch (FileNotFoundException e) {
			Utils.handleException(e, e.getMessage());
		} catch (IOException e) {
			Utils.handleException(e, e.getMessage());
		} catch (ImportException e) {
			Utils.handleException(e, e.getMessage());
		}
		
	}//END: populateAttributeCombobox
	
	private class ListenOpenTrees implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			try {

				String[] treesFiles = new String[] { "trees" };

				JFileChooser chooser = new JFileChooser();
				chooser.setDialogTitle("Loading trees file...");
				chooser.setMultiSelectionEnabled(false);
				chooser.addChoosableFileFilter(new SimpleFileFilter(treesFiles,
						"Tree files (*.trees)"));
				chooser.setCurrentDirectory(workingDirectory);

				int returnVal = chooser.showOpenDialog(Utils.getActiveFrame());
				if (returnVal == JFileChooser.APPROVE_OPTION) {

					File file = chooser.getSelectedFile();
					treesFilename = file.getAbsolutePath();
					
					frame.setStatus("Opened " + treesFilename + "\n");

					File tmpDir = chooser.getCurrentDirectory();

					if (tmpDir != null) {
						workingDirectory = tmpDir;
					}

					populateAttributeComboboxes();
					
				} else {
					frame.setStatus("Could not Open! \n");
				}

			} catch (Exception e) {
				Utils.handleException(e, e.getMessage());
			}// END: try-catch block

		}// END: actionPerformed
	}// END: ListenOpenTrees

	private class ListenPolygonsMinColorChooser implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			Color c = ColorPicker.showDialog(Utils.getActiveFrame(),
					"Choose minimum polygons color...", polygonsMinColor, true);

			if (c != null)
				polygonsMinColor = c;

		}
	}

	private class ListenPolygonsMaxColorChooser implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			Color c = ColorPicker.showDialog(Utils.getActiveFrame(),
					"Choose maximum polygons color...", polygonsMaxColor, true);

			if (c != null)
				polygonsMaxColor = c;

		}
	}

	private class ListenBranchesMinColorChooser implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			Color c = ColorPicker.showDialog(Utils.getActiveFrame(),
					"Choose minimum branches color...", branchesMinColor, true);

			if (c != null)
				branchesMinColor = c;

		}
	}

	private class ListenBranchesMaxColorChooser implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			Color c = ColorPicker.showDialog(Utils.getActiveFrame(),
					"Choose maximum branches color...", branchesMaxColor, true);

			if (c != null)
				branchesMaxColor = c;

		}
	}

	private class ListenGenerateKml implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			if (openTree.isEnabled() && treeFilename == null) {

				new ListenOpenTree().actionPerformed(ev);

			} else if (treesFilename == null) {

				new ListenOpenTrees().actionPerformed(ev);

			} else {

				generateKml.setEnabled(false);
				progressBar.setIndeterminate(true);

				SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

					// Executed in background thread
					public Void doInBackground() {

						try {

							if (new TimeSlicerSanityCheck().check(treeFilename,
									coordinatesNameParser.getSelectedItem().toString(),
									treesFilename, analysisType)) {

								TimeSlicerToKML timeSlicerToKML = new TimeSlicerToKML();

								timeSlicerToKML.setAnalysisType(analysisType);

								if (analysisType == TimeSlicerToKML.FIRST_ANALYSIS) {

									timeSlicerToKML.setTreePath(treeFilename);

									timeSlicerToKML
											.setNumberOfIntervals(Integer
													.valueOf(numberOfIntervalsParser
															.getText()));

									timeSlicerToKML
											.setMinBranchRedMapping(branchesMinColor
													.getRed());

									timeSlicerToKML
											.setMinBranchGreenMapping(branchesMinColor
													.getGreen());

									timeSlicerToKML
											.setMinBranchBlueMapping(branchesMinColor
													.getBlue());

									timeSlicerToKML
											.setMinBranchOpacityMapping(branchesMinColor
													.getAlpha());

									timeSlicerToKML
											.setMaxBranchRedMapping(branchesMaxColor
													.getRed());

									timeSlicerToKML
											.setMaxBranchGreenMapping(branchesMaxColor
													.getGreen());

									timeSlicerToKML
											.setMaxBranchBlueMapping(branchesMaxColor
													.getBlue());

									timeSlicerToKML
											.setMaxBranchOpacityMapping(branchesMaxColor
													.getAlpha());

									timeSlicerToKML
											.setBranchWidth(branchesWidthParser
													.getValue());

									timeSlicerToKML
											.setMaxAltitudeMapping(Double
													.valueOf(maxAltMappingParser
															.getText()));

								} else if (analysisType == TimeSlicerToKML.SECOND_ANALYSIS) {

									timeSlicerToKML
											.setCustomSliceHeightsPath(sliceHeightsFilename);

								} else {

									throw new RuntimeException(
											"Unknown analysis type selected");

								}

								timeSlicerToKML.setTreesPath(treesFilename);

								timeSlicerToKML.setHPD(Double.valueOf(HPDParser
										.getText()));

								timeSlicerToKML.setGridSize(gridSizeParser
										.getValue());

								timeSlicerToKML.setBurnIn(Integer
										.valueOf(burnInParser.getText()));

								timeSlicerToKML
										.setLocationAttributeName(coordinatesNameParser
												.getSelectedItem().toString());

								timeSlicerToKML
										.setRateAttributeName(rateAttNameParser
												.getSelectedItem().toString());

								timeSlicerToKML
										.setPrecisionAttName(precisionAttNameParser
												.getSelectedItem().toString());

								timeSlicerToKML.setUseTrueNoise(trueNoiseParser
										.isSelected());

								timeSlicerToKML
										.setMrsdString(dateSpinner.getValue()
												+ " "
												+ (eraParser.getSelectedIndex() == 0 ? "AD"
														: "BC"));

								timeSlicerToKML.setTimescaler(Double
										.valueOf(timescalerParser.getText()));

								timeSlicerToKML
										.setKmlWriterPath(workingDirectory
												.toString()
												.concat("/")
												.concat(kmlPathParser.getText()));

								timeSlicerToKML
										.setMinPolygonRedMapping(polygonsMinColor
												.getRed());

								timeSlicerToKML
										.setMinPolygonGreenMapping(polygonsMinColor
												.getGreen());

								timeSlicerToKML
										.setMinPolygonBlueMapping(polygonsMinColor
												.getBlue());

								timeSlicerToKML
										.setMinPolygonOpacityMapping(polygonsMinColor
												.getAlpha());

								timeSlicerToKML
										.setMaxPolygonRedMapping(polygonsMaxColor
												.getRed());

								timeSlicerToKML
										.setMaxPolygonGreenMapping(polygonsMaxColor
												.getGreen());

								timeSlicerToKML
										.setMaxPolygonBlueMapping(polygonsMaxColor
												.getBlue());

								timeSlicerToKML
										.setMaxPolygonOpacityMapping(polygonsMaxColor
												.getAlpha());

								timeSlicerToKML.GenerateKML();

								System.out.println("Finished in: "
										+ timeSlicerToKML.time + " msec \n");

							}// END: check

						} catch (final OutOfMemoryError e) {

							Utils.handleException(e, "Increase Java Heap Space");
							
						} catch (final Exception e) {

							Utils.handleException(e, e.getMessage());

						}

						return null;
					}// END: doInBackground()

					// Executed in event dispatch thread
					public void done() {

						generateKml.setEnabled(true);
						progressBar.setIndeterminate(false);

						frame.setStatus("Generated "
								+ workingDirectory.toString().concat("/")
										.concat(kmlPathParser.getText()));

						System.gc();

					}
				};

				worker.execute();

			}// END: if not loaded

		}// END: actionPerformed
	}// END: ListenGenerateKml

	private class ListenGenerateProcessing implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			if (openTree.isEnabled() && treeFilename == null) {

				new ListenOpenTree().actionPerformed(ev);

			} else if (treesFilename == null) {

				new ListenOpenTrees().actionPerformed(ev);

			} else {

				generateProcessing.setEnabled(false);
				progressBar.setIndeterminate(true);

				SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

					// Executed in background thread
					public Void doInBackground() {

						try {

							if (new TimeSlicerSanityCheck().check(treeFilename,
									coordinatesNameParser.getSelectedItem().toString(),
									treesFilename, analysisType)) {

								timeSlicerToProcessing
										.setAnalysisType(analysisType);

								if (analysisType == TimeSlicerToProcessing.FIRST_ANALYSIS) {

									timeSlicerToProcessing
											.setTreePath(treeFilename);

									timeSlicerToProcessing
											.setNumberOfIntervals(Integer
													.valueOf(numberOfIntervalsParser
															.getText()));

									timeSlicerToProcessing
											.setMinBranchRedMapping(branchesMinColor
													.getRed());

									timeSlicerToProcessing
											.setMinBranchGreenMapping(branchesMinColor
													.getGreen());

									timeSlicerToProcessing
											.setMinBranchBlueMapping(branchesMinColor
													.getBlue());

									timeSlicerToProcessing
											.setMinBranchOpacityMapping(branchesMinColor
													.getAlpha());

									timeSlicerToProcessing
											.setMaxBranchRedMapping(branchesMaxColor
													.getRed());

									timeSlicerToProcessing
											.setMaxBranchGreenMapping(branchesMaxColor
													.getGreen());

									timeSlicerToProcessing
											.setMaxBranchBlueMapping(branchesMaxColor
													.getBlue());

									timeSlicerToProcessing
											.setMaxBranchOpacityMapping(branchesMaxColor
													.getAlpha());

									timeSlicerToProcessing
											.setBranchWidth(branchesWidthParser
													.getValue() / 2);

								} else if (analysisType == TimeSlicerToProcessing.SECOND_ANALYSIS) {

									timeSlicerToProcessing
											.setCustomSliceHeightsPath(sliceHeightsFilename);

								} else {

									throw new RuntimeException(
											"Unknown analysis type selected");

								}

								timeSlicerToProcessing
										.setTreesPath(treesFilename);

								timeSlicerToProcessing.setHPD(Double
										.valueOf(HPDParser.getText()));

								timeSlicerToProcessing
										.setGridSize(gridSizeParser.getValue());

								timeSlicerToProcessing.setBurnIn(Integer
										.valueOf(burnInParser.getText()));

								timeSlicerToProcessing
										.setCoordinatesName(coordinatesNameParser
												.getSelectedItem().toString());

								timeSlicerToProcessing
										.setRateAttributeName(rateAttNameParser
												.getSelectedItem().toString());

								timeSlicerToProcessing
										.setPrecisionAttributeName(precisionAttNameParser
												.getSelectedItem().toString());

								timeSlicerToProcessing
										.setUseTrueNoise(trueNoiseParser
												.isSelected());

								timeSlicerToProcessing
										.setMrsdString(dateSpinner.getValue()
												+ " "
												+ (eraParser.getSelectedIndex() == 0 ? "AD"
														: "BC"));

								timeSlicerToProcessing.setTimescaler(Double
										.valueOf(timescalerParser.getText()));

								timeSlicerToProcessing
										.setMinPolygonRedMapping(polygonsMinColor
												.getRed());

								timeSlicerToProcessing
										.setMinPolygonGreenMapping(polygonsMinColor
												.getGreen());

								timeSlicerToProcessing
										.setMinPolygonBlueMapping(polygonsMinColor
												.getBlue());

								timeSlicerToProcessing
										.setMinPolygonOpacityMapping(polygonsMinColor
												.getAlpha());

								timeSlicerToProcessing
										.setMaxPolygonRedMapping(polygonsMaxColor
												.getRed());

								timeSlicerToProcessing
										.setMaxPolygonGreenMapping(polygonsMaxColor
												.getGreen());

								timeSlicerToProcessing
										.setMaxPolygonBlueMapping(polygonsMaxColor
												.getBlue());

								timeSlicerToProcessing
										.setMaxPolygonOpacityMapping(polygonsMaxColor
												.getAlpha());

								timeSlicerToProcessing.analyzeTrees();

								timeSlicerToProcessing.init();

//								System.out.println("Finished. \n");

							}// END: check

						} catch (final OutOfMemoryError e) {

							Utils.handleException(e, "Increase Java Heap Space");
							
						} catch (final Exception e) {

							Utils.handleException(e, null);

						}

						return null;
					}// END: doInBackground()

					// Executed in event dispatch thread
					public void done() {

						generateProcessing.setEnabled(true);
						progressBar.setIndeterminate(false);
//						frame.setStatus("Finished. \n");
						System.gc();

					}// END: done
				};

				worker.execute();

			}// END: if not loaded

		}// END: actionPerformed
	}// END: ListenGenerateProcessing

	private class ListenSaveProcessingPlot implements ActionListener {
		public void actionPerformed(ActionEvent ev) {

			try {

				JFileChooser chooser = new JFileChooser();
				chooser.setDialogTitle("Saving as png file...");

				int returnVal = chooser.showSaveDialog(Utils.getActiveFrame());
				if (returnVal == JFileChooser.APPROVE_OPTION) {
					
					File file = chooser.getSelectedFile();
					String filename = file.getAbsolutePath();

					timeSlicerToProcessing.save(filename);
					
					frame.setStatus("Saved " + filename + "\n");

				} else {
					frame.setStatus("Could not Save! \n");
				}

			} catch (Exception e) {
				Utils.handleException(e, e.getMessage());
			}// END: try-catch block

		}// END: actionPerformed
	}// END: ListenSaveProcessingPlot

}// END class

