package lpv.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.prefs.Preferences;

import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.filechooser.FileFilter;

import lpv.ErrorStore;
import net.sf.epsgraphics.ColorMode;
import net.sf.epsgraphics.EpsGraphics;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;

public class ErrorDialog extends JDialog implements Observer {

	private HashMap<String, DefaultCategoryDataset> datasets;
	private JCheckBox aaeBox;
	private JCheckBox areBox;
	private JCheckBox maeBox;
	private JCheckBox mreBox;
	private JCheckBox caeBox;
	private ErrorStore errors;
	private JButton saveImageButton;
	private JTabbedPane tabs;
	private JButton exportDataButton;
	private JButton importDataButton;

	public ErrorDialog(ErrorStore errors) {
		super((JFrame) null, "Error plot", false);

		this.errors = errors;
		errors.addObserver(this);
		datasets = new HashMap<String, DefaultCategoryDataset>();

		setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);

		setLayout(new BorderLayout());

		JPanel controlPanel = new JPanel();
		JPanel innerControlPanel = new JPanel();
		innerControlPanel.setLayout(new BoxLayout(innerControlPanel,
				BoxLayout.Y_AXIS));
		
		ActionListener checkBoxHandler = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				updateDatasets();
			}
		};
		
		innerControlPanel.add(aaeBox = new JCheckBox("Average absolute error",
				true));
		aaeBox.addActionListener(checkBoxHandler);
		
		innerControlPanel.add(areBox = new JCheckBox("Average relative error",
				true));
		areBox.addActionListener(checkBoxHandler);
		
		innerControlPanel
				.add(maeBox = new JCheckBox("Max absolute error", true));
		maeBox.addActionListener(checkBoxHandler);
		
		innerControlPanel
				.add(mreBox = new JCheckBox("Max relative error", true));
		mreBox.addActionListener(checkBoxHandler);
		
		innerControlPanel.add(caeBox = new JCheckBox(
				"Cumulative absolute error", true));
		caeBox.addActionListener(checkBoxHandler);
		
		innerControlPanel.add(saveImageButton = new JButton("Save image..."));
		saveImageButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				showSaveImageDialog();
			}});
		
		innerControlPanel.add(exportDataButton = new JButton("Export data..."));
		exportDataButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				showExportDataDialog();
			}});
		
		innerControlPanel.add(importDataButton = new JButton("Import data..."));
		importDataButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				showImportDataDialog();
			}});
		
		
		controlPanel.add(innerControlPanel);

		add(controlPanel, BorderLayout.WEST);
		tabs = new JTabbedPane();

		for (String source : errors.getSources()) {
			createDataset(source);
		}

		add(tabs, BorderLayout.CENTER);

		pack();
		updateDatasets();
	}

	private void createDataset(String source) {
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		datasets.put(source, dataset);
		JFreeChart chart = ChartFactory.createLineChart(source
				+ " error plot", "Sample point", "Error", dataset,
				PlotOrientation.VERTICAL, true, true, true);
		ChartPanel chartPanel = new ChartPanel(chart);
		chartPanel.setPreferredSize(new Dimension(640, 480));
		tabs.addTab(source, chartPanel);
	}

	private void updateDatasets() {
		EnumSet<ErrorStore.Type> types = EnumSet.noneOf(ErrorStore.Type.class);
		if (aaeBox.isSelected()) {
			types.add(ErrorStore.Type.aae);
		}
		if (areBox.isSelected()) {
			types.add(ErrorStore.Type.are);
		}
		if (maeBox.isSelected()) {
			types.add(ErrorStore.Type.mae);
		}
		if (mreBox.isSelected()) {
			types.add(ErrorStore.Type.mre);
		}
		if (caeBox.isSelected()) {
			types.add(ErrorStore.Type.cae);
		}
		for (Map.Entry<String, DefaultCategoryDataset> datasetEntry : datasets
				.entrySet()) {
			errors.getData(datasetEntry.getValue(), datasetEntry.getKey(),
					types);
		}
	}

	@Override
	public void update(Observable o, Object arg) {
		updateDatasets();
	}
	
	private void showSaveImageDialog() {
		Preferences pref = Preferences.userNodeForPackage(ErrorDialog.class);
		File old = new File(pref.get("error.save.path", ""));
		JFileChooser fileChooser;
		if(old.exists()) {
			fileChooser = new JFileChooser(old);
		} else {
			fileChooser = new JFileChooser();
		}
		
		fileChooser.setMultiSelectionEnabled(false);
		fileChooser.addChoosableFileFilter(ImageFileFilter.BMP);
		fileChooser.addChoosableFileFilter(ImageFileFilter.EPS);
		fileChooser.addChoosableFileFilter(ImageFileFilter.JPEG);
		fileChooser.addChoosableFileFilter(ImageFileFilter.PNG);
		fileChooser.setAcceptAllFileFilterUsed(false);
		
		int result = fileChooser.showSaveDialog(ErrorDialog.this);
		if(result == JFileChooser.APPROVE_OPTION) {
			File file = fileChooser.getSelectedFile();
			pref.put("error.save.path", file.getAbsolutePath());
			ChartPanel currentChartPanel = (ChartPanel)tabs.getSelectedComponent();
			if(currentChartPanel != null) {
				boolean proceed = true;
				if(file.exists()) {
					int option = JOptionPane.showConfirmDialog(ErrorDialog.this, "The file " + file.getName() + " already exists. \nDo you want to replace it?", "Replace file", JOptionPane.YES_NO_OPTION);
					if(option != JOptionPane.YES_OPTION) {
						proceed = false;
					}
				}
				if(proceed) {
					ImageFileFilter filter = (ImageFileFilter)fileChooser.getFileFilter();
					if(filter == ImageFileFilter.EPS) {
						try {
							OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
							EpsGraphics g = new EpsGraphics(currentChartPanel.getChart().getTitle().getText(), out, 0, 0, 640, 480, ColorMode.COLOR_RGB);
							currentChartPanel.getChart().draw(g, new Rectangle2D.Double(0.0, 0.0, 640.0, 480.0));
							g.flush();
							out.flush();
							out.close();
						} catch (IOException e) {
							JOptionPane.showMessageDialog(ErrorDialog.this, "Could not save file. " + e.getMessage());
							e.printStackTrace();
						}
					} else {
						BufferedImage img = currentChartPanel.getChart().createBufferedImage(1280, 720);
						
						try {
							if(!ImageIO.write(img, filter.getFormatName(), file)) {
								JOptionPane.showMessageDialog(ErrorDialog.this, "Could not save file. No appropriate writer could be found for " + filter.getFormatName() + ".");
							}
						} catch (IOException e) {
							JOptionPane.showMessageDialog(ErrorDialog.this, "Could not save file. " + e.getMessage());
							e.printStackTrace();
						}
					}
				}
			}
			
		} else {
			pref.put("error.save.path", fileChooser.getCurrentDirectory().getAbsolutePath());
		}
	}
	
	private void showExportDataDialog() {
		Preferences pref = Preferences.userNodeForPackage(ErrorDialog.class);
		File old = new File(pref.get("error.path", ""));
		JFileChooser fileChooser;
		if(old.exists()) {
			fileChooser = new JFileChooser(old);
		} else {
			fileChooser = new JFileChooser();
		}
		
		fileChooser.setMultiSelectionEnabled(false);
		fileChooser.addChoosableFileFilter(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				return !pathname.isFile() || pathname.getName().endsWith(".csv");
			}
			@Override
			public String getDescription() {
				return "Comma separated vector (*.csv)";
			}});
		
		int result = fileChooser.showSaveDialog(ErrorDialog.this);
		if(result == JFileChooser.APPROVE_OPTION) {
			File file = fileChooser.getSelectedFile();
			pref.put("error.path", file.getAbsolutePath());

			boolean proceed = true;
			if(file.exists()) {
				int option = JOptionPane.showConfirmDialog(ErrorDialog.this, "The file " + file.getName() + " already exists. \nDo you want to replace it?", "Replace file", JOptionPane.YES_NO_OPTION);
				if(option != JOptionPane.YES_OPTION) {
					proceed = false;
				}
			}
			if(proceed) {
				try {
					FileOutputStream fout = new FileOutputStream(file);
					int index = tabs.getSelectedIndex();
					if(index != -1) {
						errors.save(tabs.getTitleAt(index), fout);
					}
				} catch(IOException e) {
					JOptionPane.showMessageDialog(ErrorDialog.this, "Could not save file. " + e.getMessage());
					e.printStackTrace();
				}
			}
			
		} else {
			pref.put("error.path", fileChooser.getCurrentDirectory().getAbsolutePath());
		}
	}
	
	private void showImportDataDialog() {
		Preferences pref = Preferences.userNodeForPackage(ErrorDialog.class);
		File old = new File(pref.get("error.path", ""));
		JFileChooser fileChooser;
		if(old.exists()) {
			fileChooser = new JFileChooser(old);
		} else {
			fileChooser = new JFileChooser();
		}
		
		fileChooser.setMultiSelectionEnabled(true);
		fileChooser.addChoosableFileFilter(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				return !pathname.isFile() || pathname.getName().endsWith(".csv");
			}
			@Override
			public String getDescription() {
				return "Comma separated vector (*.csv)";
			}});
		
		int result = fileChooser.showOpenDialog(ErrorDialog.this);
		if(result == JFileChooser.APPROVE_OPTION) {
			File file = fileChooser.getSelectedFile();
			pref.put("error.path", file.getAbsolutePath());
			File[] files = fileChooser.getSelectedFiles();
		
				for(File f : files) {
					try {
						FileInputStream fin = new FileInputStream(f);
						errors.load(f.getName(), fin);
						if(!datasets.containsKey(f.getName())) {
							createDataset(f.getName());
						}
					} catch(IOException e) {
						JOptionPane.showMessageDialog(ErrorDialog.this, "Could not load file " + f.getName() + ". " + e.getMessage());
						e.printStackTrace();
						break;
					}
				}
				updateDatasets();
			
		} else {
			pref.put("error.path", fileChooser.getCurrentDirectory().getAbsolutePath());
		}
	}

	private static class ImageFileFilter extends FileFilter {
		public static ImageFileFilter PNG = new ImageFileFilter("PNG", ".png", "Portable Network Graphics (*.png)");
		public static ImageFileFilter BMP = new ImageFileFilter("BMP", ".bmp", "Bitmap Image File (*.bmp)");
		public static ImageFileFilter JPEG = new ImageFileFilter("JPEG", ".jpg", "Joint Photographic Experts Group (*.jpg)");
		public static ImageFileFilter EPS = new ImageFileFilter("EPS", ".eps", "Encapsulated PostScript (*.eps)");
		
		private String formatName;
		private String extension;
		private String description;
		
		private ImageFileFilter(String formatName, String extension, String description) {
			this.formatName = formatName;
			this.extension = extension;
			this.description = description;
		}
		
		public String getFormatName() {
			return formatName;
		}
		
		public String getExtension() {
			return extension;
		}
		
		@Override
		public boolean accept(File pathname) {
			return !pathname.isFile() || pathname.getName().endsWith(extension);
		}

		@Override
		public String getDescription() {
			return description;
		}
	}

}
