// Dialogs.cs created with MonoDevelop
// User: klose at 18:10 12.03.2009
// CVS release: $Id: Dialogs.cs,v 1.25 2011-04-19 12:36:58 klose Exp $
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2009  Thomas Klose, Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Threading;
using Gtk;

using Hiperscan.FileIO;
using Hiperscan.Spectroscopy;
using Hiperscan.GUI.Figure;
using Hiperscan.Unix;


namespace Hiperscan.GUI
{
	
	public class QSFileChooserDialog : Gtk.FileChooserDialog
	{

		private class SpectrumPreviewWidget : Gtk.Table
		{
			private IPlotEngine plot;
			
			private ConstCheckButton absorbance_checkbutton;
			
			private Label device_label;
			private Label di_label;
			private Label date_label;
			private Label comment_label;
			
			
			public SpectrumPreviewWidget() : base(5, 2, false)
			{
				// plot
				this.plot = new NPlotEngine(PlotEngineType.Preview);
				this.plot.ReverseXAxes = UserInterface.Settings.Current.AxisType == AxisType.Wavecount;
				
				this.plot.GtkWidget.HeightRequest = 100;
				this.Attach(this.plot.GtkWidget, 0, 2, 0, 1, AttachOptions.Expand, AttachOptions.Expand, 0, 0);
				
				// absorbance
				Alignment align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(new Label(Catalog.GetString("Absorbance:")));
				this.Attach(align, 0, 1, 1, 2, AttachOptions.Fill, AttachOptions.Shrink, 0, 0);

				this.absorbance_checkbutton = new ConstCheckButton();
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(this.absorbance_checkbutton);
				this.Attach(align, 1, 2, 1, 2, AttachOptions.Fill, AttachOptions.Shrink, 3, 0);
				
				// device
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(new Label(Catalog.GetString("Device:")));
				this.Attach(align, 0, 1, 2, 3, AttachOptions.Fill, AttachOptions.Shrink, 0, 0);

				this.device_label = new Label();
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(this.device_label);
				this.Attach(align, 1, 2, 2, 3, AttachOptions.Fill, AttachOptions.Shrink, 3, 0);
				
				// dark intensity
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(new Label(Catalog.GetString("Dark intensity:")));
//				this.Attach(align, 0, 1, 3, 4, AttachOptions.Fill, AttachOptions.Shrink, 0, 0);

				this.di_label = new Label();
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(this.di_label);
//				this.Attach(align, 1, 2, 3, 4, AttachOptions.Fill, AttachOptions.Shrink, 3, 0);
				
				// date
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(new Label(Catalog.GetString("Date:")));
				this.Attach(align, 0, 1, 4, 5, AttachOptions.Fill, AttachOptions.Shrink, 0, 0);

				this.date_label = new Label();
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(this.date_label);
				this.Attach(align, 1, 2, 4, 5, AttachOptions.Fill, AttachOptions.Shrink, 3, 0);
				
				// comment
				this.comment_label = new Label();
				this.comment_label.LineWrap = true;
				this.comment_label.WidthRequest = 200;
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(this.comment_label);
				this.Attach(align, 0, 2, 5, 6, AttachOptions.Shrink, AttachOptions.Shrink, 0, 5);
				
				this.ShowAll();
			}
			
			public Spectrum Spectrum
			{
				set
				{
					this.plot.DataSets.Clear();
					DataSet ds;
					string id;
					if (value.HasAbsorbance)
					{
						ds = value.Absorbance;
						id = value.Absorbance.Id;
					}
					else
					{
						ds = value.Intensity;
						id = value.Intensity.Id;
					}
					if (UserInterface.Settings.Current.AxisType == AxisType.Wavecount)
						UserInterface.ConvertXAxisToWavecount(ref ds);
					this.plot.DataSets.Add(id, ds);
					this.plot.FitView();
					this.plot.RequestUpdate();
					this.plot.ProcessChanges();
					
					this.device_label.Text     = value.Serial;
					this.absorbance_checkbutton.Hold = false;
					this.absorbance_checkbutton.Active = value.HasAbsorbance;
					this.absorbance_checkbutton.Inconsistent = false;
					this.absorbance_checkbutton.Hold = true;
					this.di_label.Text         = value.HasDarkIntensity.ToString();
					this.date_label.Text       = value.Timestamp.ToString();
					
					if (value.Comment.Length < 100)
						this.comment_label.Text = value.Comment.Replace('\n', ' ');
					else
						this.comment_label.Text = value.Comment.Replace('\n', ' ').Substring(0, 97) + "...";
				}
			}
			
			public Spectrum[] Spectra
			{
				set
				{
					bool absorbance     = true;
					bool inconsistent   = false;
					bool dark_intensity = false;
					string serial = string.Empty;
					
					foreach (Spectrum spectrum in value)
					{
						if (!spectrum.HasAbsorbance)
							absorbance = false;
						
						if (spectrum.HasAbsorbance && !absorbance)
							inconsistent = true;
						
						if (spectrum.HasDarkIntensity)
							dark_intensity = true;
						
						if (string.IsNullOrEmpty(serial))
						{
							serial = spectrum.Serial;
						}
						else
						{
							if (serial != spectrum.Serial)
								serial = Catalog.GetString("n/a");
						}
					}
					
					this.plot.DataSets.Clear();
					
					foreach (Spectrum spectrum in value)
					{
						if (absorbance && spectrum.HasAbsorbance)
							this.plot.DataSets.Add(spectrum.Absorbance.Id, spectrum.Absorbance);
						else if (!absorbance)
							this.plot.DataSets.Add(spectrum.Intensity.Id, spectrum.Intensity);
					}
					
					this.plot.FitView();
					this.plot.RequestUpdate();
					this.plot.ProcessChanges();
					
					this.device_label.Text = serial;
					this.absorbance_checkbutton.Hold = false;
					this.absorbance_checkbutton.Active = absorbance;
					this.absorbance_checkbutton.Inconsistent = inconsistent;
					this.absorbance_checkbutton.Hold = true;
					this.di_label.Text   = dark_intensity.ToString();
					this.date_label.Text = Catalog.GetString("n/a");
					
					this.comment_label.Text = string.Empty;
				}
			}
		}
		
		private SpectrumPreviewWidget preview;
		private ComboBox output_culture_combo;
		
		private static QSFileChooserDialog current_instance = null;
		
		private string current_name = string.Empty;
		private Dictionary<FileFilter,string> extensions = new Dictionary<FileFilter,string>();
		private FileFilter current_filter = null;
		private static volatile bool running = true;
		

		public QSFileChooserDialog(string t, Window p, FileChooserAction a, params object[] b) : base(t, p, a)
		{
//			if (QSFileChooserDialog.current_instance != null)
//				throw new Exception("A QSFileChooserDialog can only run once at a time.");

			QSFileChooserDialog.current_instance = this;
			
			this.TransientFor = p;
			this.IconList = MainClass.QuickStepIcons;
			
			this.preview = new SpectrumPreviewWidget();
			this.PreviewWidget = this.preview;
			
			if (a == FileChooserAction.Save)
			{
				this.ActionArea.PackStart(new Label(Catalog.GetString("Output culture:")));
				
				CellRendererText text = new CellRendererText();
				this.output_culture_combo = new ComboBox();
				this.output_culture_combo.PackStart(text, false);
				this.output_culture_combo.SetAttributes(text, "text", 0);
				this.output_culture_combo.Model = new ListStore(typeof(string));
				this.output_culture_combo.AppendText(Catalog.GetString("English"));
				this.output_culture_combo.AppendText(Catalog.GetString("German"));
				this.output_culture_combo.AppendText(Catalog.GetString("System"));
				this.output_culture_combo.Active = (int)MainWindow.UI.StandardOutputCulture;
				this.ActionArea.PackStart(this.output_culture_combo);
				this.ActionArea.PackStart(new Label(""));
			}

			if (b.Length % 2 != 0)
				throw new ArgumentException("Invalid argument count.");
			
			for (int ix=0; ix < b.Length; ix += 2)
			{
				this.AddButton((string)b[ix], (ResponseType)b[ix+1]);
			}
			
			this.SelectionChanged += this.OnSelectionChanged;
			
			QSFileChooserDialog.running = true;
			GLib.Timeout.Add(100, QSFileChooserDialog.UpdateCurrentName);
			
			this.ShowAll();
		}
		
		private static bool UpdateCurrentName()
		{
			try
			{
				if (current_instance == null ||
				    string.IsNullOrEmpty(current_instance.current_name))
					return false;
				if (!QSFileChooserDialog.running)
					return false;
				if (current_instance.current_filter == current_instance.Filter)
					return true;
				if (!current_instance.extensions.ContainsKey(current_instance.Filter))
					return true;
				if (string.IsNullOrEmpty(current_instance.current_name))
					return true;
				string ext = current_instance.extensions[current_instance.Filter];
				string name = System.IO.Path.GetFileNameWithoutExtension(current_instance.current_name);
				if (System.IO.Path.GetFileName(current_instance.Filename) == name + "." + ext)
					return true;
				current_instance.CurrentName = name + "." + ext;
				current_instance.current_filter = current_instance.Filter;
				// reset folder to select filename without extension
				current_instance.SetCurrentFolder(current_instance.CurrentFolder);
				return true;
			}
			catch (ArgumentNullException)
			{
				// dialog may already be destroyed
				return false;
			}
		}
		
		public int _Run()
		{
			int response = this.Run();
			QSFileChooserDialog.running = false;
			return response;
		}

		private void OnSelectionChanged(object o, EventArgs e)
		{
			try
			{
				if (this.PreviewFilename.ToLower().EndsWith(".csv"))
				{
					this.preview.Spectrum = CSV.Read(this.PreviewFilename);
					this.PreviewWidgetActive = true;
				}
				else if (this.PreviewFilename.ToLower().EndsWith(".csvc"))
				{
					this.preview.Spectra = CSV.ReadContainer(this.PreviewFilename);
					this.PreviewWidgetActive = true;
				}
				else
				{
					this.PreviewWidgetActive = false;
				}
			}
			catch { this.PreviewWidgetActive = false; }
		}
		
		public void AddFilter(FileFilter filter, string extension)
		{
			this.AddFilter(filter);
			this.extensions.Add(filter, extension);
		}
		
		public string _CurrentName 
		{
			set 
			{
				this.current_name = value;
				this.CurrentName  = value;
			}
		}
		
		public OutputCulture OutputCulture
		{
			get { return (OutputCulture)this.output_culture_combo.Active; }
			set { this.output_culture_combo.Active = (int)value;          }
		}
	}
	
	internal class PasswordDialog
	{
		private string title;
		private Window parent;
		
		internal PasswordDialog(Window parent, string title)
		{
			this.parent = parent;
			this.title = title;
		}
		
		internal string Query()
		{
			Dialog dialog = new Dialog(this.title, this.parent, DialogFlags.Modal);
			dialog.TransientFor = this.parent;
			dialog.Resizable = false;
			dialog.AddButton(Stock.Ok, ResponseType.Ok);
			dialog.Default = dialog.ActionArea.Children[0];

			dialog.IconList = MainClass.QuickStepIcons;
			
			Alignment align;
			align = new Alignment(0f, 0.5f, 0f, 1f);
			align.Add(new Label(Catalog.GetString("Password:")));
			align.TopPadding    = 10;
			align.BottomPadding = 10;
			align.LeftPadding   = 8;
			align.RightPadding  = 8;
			
			Entry entry = new Entry();
			entry.Visibility = false;
			entry.ActivatesDefault = true;
			
			HBox hbox = new HBox();
			hbox.PackStart(align);
			hbox.PackStart(entry);

			dialog.VBox.PackStart(hbox);
			dialog.VBox.ShowAll();
			
			dialog.Run();
			string password = entry.Text;
			dialog.Destroy();
			
			return password;
		}
		
		internal bool Query(string hash)
		{
			string password = this.Query();
			
			// compute md5 hash
			MD5 md5 = new MD5CryptoServiceProvider();
			byte[] pass_buf = ASCIIEncoding.Default.GetBytes(password);
			byte[] hash_buf = md5.ComputeHash(pass_buf);

			//Console.WriteLine(BitConverter.ToString(hash_buf));
			
			return (BitConverter.ToString(hash_buf) == hash);
		}
	}
	
	internal class SNVBoundariesDialog
	{
		private double globalmin;
		private double globalmax;
		
		internal SNVBoundariesDialog(double globalmin, double globalmax)
		{
			this.globalmin = globalmin;
			this.globalmax = globalmax;

			if (double.IsInfinity(this.globalmin))
				this.globalmin = 0.0;
			
			if (double.IsInfinity(this.globalmax))
				this.globalmax = this.globalmin + 3000.0;
			
			Console.WriteLine(this.globalmin + ", " + this.globalmax);
		}
		
		internal UserInterface.SNVInfo Query()
		{
			Dialog dialog = new Dialog(Catalog.GetString("Specify SNV boundaries"),
			                           MainClass.MainWindow, DialogFlags.Modal);
			dialog.TransientFor = MainClass.MainWindow;
			dialog.AddButton(Stock.Ok, ResponseType.Ok);
			dialog.IconList = MainClass.QuickStepIcons;

			Alignment align;
			Adjustment adjust;
			double init_val;
			Table table;
			SpinButton spin1, spin2;
			
			if (UserInterface.Settings.Current.AxisType == AxisType.Wavecount)
			{
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(new Label(Catalog.GetString("Lower boundary for SNV transformation:")));
				align.LeftPadding  = 8;
				align.RightPadding = 8;
				
				if (UserInterface.WavelengthToWavecount(UserInterface.Settings.Current.SNVUpperBoundary) > this.globalmin)
					init_val = UserInterface.WavelengthToWavecount(UserInterface.Settings.Current.SNVUpperBoundary);
				else
					init_val = this.globalmin;
				
				adjust = new Adjustment(init_val, this.globalmin, this.globalmax-1f, 1f, 10f, 0f);
				spin1 = new SpinButton(adjust, 0f, 0);
				
				table = new Table(2, 2, false);
				table.Attach(align, 0, 1, 0, 1);
				table.Attach(spin1, 1, 2, 0, 1);
				
				
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(new Label(Catalog.GetString("Upper boundary for SNV transformation:")));
				align.LeftPadding  = 8;
				align.RightPadding = 8;
				
				if (UserInterface.WavelengthToWavecount(UserInterface.Settings.Current.SNVLowerBoundary) < this.globalmax)
					init_val = UserInterface.WavelengthToWavecount(UserInterface.Settings.Current.SNVLowerBoundary);
				else
					init_val = this.globalmax;

				adjust = new Adjustment(init_val, this.globalmin+1f, this.globalmax, 1f, 10f, 0f);
				spin2 = new SpinButton(adjust, 0f, 0);
			}
			else
			{
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(new Label(Catalog.GetString("Lower boundary for SNV transformation:")));
				align.LeftPadding  = 8;
				align.RightPadding = 8;
				
				if (UserInterface.Settings.Current.SNVLowerBoundary > this.globalmin)
					init_val = UserInterface.Settings.Current.SNVLowerBoundary;
				else
					init_val = this.globalmin;
				
				adjust = new Adjustment(init_val, this.globalmin, this.globalmax-1f, 1f, 10f, 0f);
				spin1 = new SpinButton(adjust, 0f, 0);
				
				table = new Table(2, 2, false);
				table.Attach(align, 0, 1, 0, 1);
				table.Attach(spin1, 1, 2, 0, 1);
				
				
				align = new Alignment(0f, 0.5f, 0f, 1f);
				align.Add(new Label(Catalog.GetString("Upper boundary for SNV transformation:")));
				align.LeftPadding  = 8;
				align.RightPadding = 8;
				
				if (UserInterface.Settings.Current.SNVUpperBoundary < this.globalmax)
					init_val = UserInterface.Settings.Current.SNVUpperBoundary;
				else
					init_val = this.globalmax;

				adjust = new Adjustment(init_val, this.globalmin+1f, this.globalmax, 1f, 10f, 0f);
				spin2 = new SpinButton(adjust, 0f, 0);
			}
			
			table.Attach(align, 0, 1, 1, 2);
			table.Attach(spin2, 1, 2, 1, 2);
			
			dialog.VBox.PackStart(table);
			dialog.VBox.ShowAll();
			
			spin1.ActivatesDefault = true;
			spin2.ActivatesDefault = true;
			dialog.ActionArea.Children[0].CanDefault = true;
			dialog.Default = dialog.ActionArea.Children[0];
			
			ResponseType result = (ResponseType)dialog.Run();
			
			UserInterface.SNVInfo snv_info;
			if (UserInterface.Settings.Current.AxisType == AxisType.Wavecount)
			{
				UserInterface.Settings.Current.SNVLowerBoundary = UserInterface.WavecountToWavelength(spin2.Value);
				UserInterface.Settings.Current.SNVUpperBoundary = UserInterface.WavecountToWavelength(spin1.Value);
				snv_info = new UserInterface.SNVInfo(UserInterface.WavecountToWavelength(spin2.Value), UserInterface.WavecountToWavelength(spin1.Value));				
			}
			else
			{
				UserInterface.Settings.Current.SNVLowerBoundary = spin1.Value;
				UserInterface.Settings.Current.SNVUpperBoundary = spin2.Value;
				snv_info = new UserInterface.SNVInfo(spin1.Value, spin2.Value);
			}
			
			dialog.Destroy();
			
			if (result == ResponseType.Ok)
				return snv_info; 
			else
				return new UserInterface.SNVInfo(false);
		}
	}

	internal class DeleteDialog : Gtk.MessageDialog
	{
		internal DeleteDialog(Window parent, string path)
			: base(parent, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, 
			       string.Format(Catalog.GetString("Are you sure you want to delete '{0}'?"), path))
		{
			this.TransientFor = parent;
			this.IconList = MainClass.QuickStepIcons;
		}
	}
	
	internal class DiscardSettingsDialog : Gtk.MessageDialog
	{
		internal DiscardSettingsDialog(Window parent)
			: base(parent, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, 
			       string.Format(Catalog.GetString("You did not apply changes in device settings. Do you want to apply now?\n\nIf you choose 'No' changes are discarded.")))
		{
			this.TransientFor = parent;
			this.IconList = MainClass.QuickStepIcons;
		}
	}
	
	internal class OverwriteConfigDialog : Gtk.MessageDialog
	{
		internal OverwriteConfigDialog(Window parent, string path)
			: base(parent, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, 
			       string.Format(Catalog.GetString("The configuration file seems to be invalid. " +
			                                       "To correct this problem it can be "           +
			                                       "replaced by a new default configuration. "    +
			                                       "Do you want to override the file\n'{0}' now?"),
			                     path))
		{
			this.TransientFor = parent;
			this.IconList = MainClass.QuickStepIcons;
		}
	}
	
	internal class OverwriteSpectrumDialog : Gtk.MessageDialog
	{
		internal OverwriteSpectrumDialog(Window parent, string path)
			: base(parent, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, 
			       string.Format(Catalog.GetString("A file named \"{0}\" already exists. " +
			                                       "Do you want to replace it?"),
			                     path))
		{
			this.TransientFor = parent;
			this.IconList = MainClass.QuickStepIcons;
		}
	}
	
	internal class DiscardOnExitDialog : Gtk.MessageDialog
	{
		internal DiscardOnExitDialog(Window parent)
			: base(parent, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo,
			       string.Format(Catalog.GetString("One or several spectra have not been saved. Do you want to quit {0} anyway?"),
			       MainClass.ApplicationName))
		{
			this.TransientFor = parent;
			this.IconList = MainClass.QuickStepIcons;    
		}
	}

	internal class ExtractedDataPlot : Gtk.Dialog 
	{
		private int  extraction_id;
		private bool run_dialog = false;
		private DirectoryInfo directory;
		
		private DataSet ds;       
		private Window  parent;
		
		private UserInterface ui;
		private ScrolledPlot  plot;

		
		internal ExtractedDataPlot(Window parent, UserInterface ui, DirectoryInfo dir)
			: base(Catalog.GetString("Extracted data characteristic") + " (" + dir.Name + ")",
			       parent, DialogFlags.Modal)
		{
			this.parent = parent;
			this.TransientFor = parent;
			this.ui = ui;
			this.directory = dir;
			this.IconList = MainClass.QuickStepIcons;
			
			Dialog dialog = new Dialog(Catalog.GetString("Extract data"),
			                           this.parent, DialogFlags.Modal);

			Alignment align1 = new Alignment(0f, 0.5f, 0f, 0f);
			Alignment align2 = new Alignment(0f, 0.5f, 1f, 0f);
			align1.LeftPadding   = 8;
			align1.RightPadding  = 8;
			align1.TopPadding    = 0;
			align1.Add(new Label(Catalog.GetString("Choose data to extract:")));
			align2.RightPadding = 5;
			
			ComboBox combo = new ComboBox(new string[] {
				"AddData 1",
				"AddData 2",
				"AddData 3",
				"AddData 4",
				"AddData 5",
				Catalog.GetString("Intensity FWHM")
			});
			combo.Active = 0;
			align2.Add(combo);
			
			Alignment align3 = new Alignment(0f, 0.5f, 0f, 0f);
			Alignment align4 = new Alignment(0f, 0.5f, 1f, 0f);
			align3.LeftPadding   = 8;
			align3.RightPadding  = 8;
			align3.BottomPadding = 0;
			align3.Add(new Label(Catalog.GetString("Choose time format:")));
			align4.RightPadding = 5;

			ComboBox combo2 = new ComboBox(new string[] {
				Catalog.GetString("Minutes (relative)"),
				Catalog.GetString("Unix time (absolute)")
			});
			combo2.Active = 0;
			align4.Add(combo2);

			Table table = new Table(2, 2, false);
			table.Attach(align1, 0, 1, 0, 1, AttachOptions.Fill, AttachOptions.Fill, 0, 0);
			table.Attach(align2, 1, 2, 0, 1, AttachOptions.Fill, AttachOptions.Fill, 0, 0);
			table.Attach(align3, 0, 1, 1, 2, AttachOptions.Fill, AttachOptions.Fill, 0, 0);
			table.Attach(align4, 1, 2, 1, 2, AttachOptions.Fill, AttachOptions.Fill, 0, 0);
			table.ShowAll();
			
			dialog.VBox.PackStart(table);
			dialog.AddButton(Stock.Cancel, ResponseType.Cancel);
			dialog.AddButton(Stock.Ok, ResponseType.Ok);			
			
			ResponseType choice = (ResponseType)dialog.Run();
			this.extraction_id = combo.Active;
			dialog.Destroy();
			
			if (choice != ResponseType.Ok)
				return;
			
			if (this.extraction_id < 5)
			{
				MessageDialog mdialog = new MessageDialog(parent, DialogFlags.Modal, MessageType.Info, ButtonsType.None, Catalog.GetString("Reading data ..."));
				mdialog.ShowAll();
				List<CSV.AddData> list = new List<CSV.AddData>();
				try
				{
					Exception ex = null;
					Thread t = new Thread(new ThreadStart(delegate {
						try
						{
							list = CSV.ReadAdditionalData(dir);
						}
						catch (Exception _ex)
						{
							ex = _ex;
						}
					}));
					t.Start();

					while (t.Join(50) == false)
					{
						UserInterface.ProcessEvents_();
					}
					mdialog.Destroy();

					if (ex != null)
						throw ex;
				}
				catch (Exception ex)
				{
					new InfoMessage(this.parent, 
					                Catalog.GetString("Cannot read additional data from directory:") +
					                " " + ex.Message);
					return;
				}
			
				// extract relevant data
				List<CSV.AddData> new_list = new List<CSV.AddData>();
				foreach (CSV.AddData add_data in list)
				{
					if (add_data.Data.Count > extraction_id)
						new_list.Add(add_data);
				}
				
				if (new_list.Count < 2)
				{
					new InfoMessage(this.parent, 
					                Catalog.GetString("No or not enough additional data available."));
					return;
				}
				
				// create data set
				new_list.Sort();
				List<double> time = new List<double>();
				List<double> data = new List<double>();
				DateTime unix_epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
				foreach (CSV.AddData add_data in new_list)
				{
					if (combo2.Active == 1)
					{
						time.Add((add_data.DateTime.ToUniversalTime() - unix_epoch).TotalSeconds);
					}
					else
					{
						TimeSpan span = add_data.DateTime - new_list[0].DateTime;
						time.Add(span.TotalSeconds);
					}
					data.Add((double)((int)add_data.Data[extraction_id]));
				}
				this.ds = new DataSet(time, data);
				this.ds.Label = Catalog.GetString("Additional Data") + " " + (this.extraction_id+1);
			}
			else
			{
				try
				{
					switch (this.extraction_id)
					{
						
					case 5:
						this.ds = this.ExtractFwhmData(dir);
						this.ds.Label = Catalog.GetString("Intensity FWHM");
						break;
						
					default:
						throw new Exception("Unknown data type.");
						
					}
				}
				catch (Exception ex)
				{
					new InfoMessage(this.parent, 
					                Catalog.GetString("Cannot extract data from directory:") +
					                " " + ex.Message);
					return;
				}
			}
			
			this.AddButton(Stock.Save,  0);
			this.AddButton(Stock.Print, 1);
			this.AddButton(Stock.Close, ResponseType.Close);
			
			this.Response += new ResponseHandler(this.OnDialogResponse);
			
			this.HasSeparator  = true;
			this.WidthRequest  = 780;
			this.HeightRequest = 400;
			
			this.plot = new ScrolledPlot(null);
			this.plot.Plot.BackgroundColor = MainWindow.UI.BackgroundColor;
			DataSet ds = this.ds / new DataSet.Operand(60.0, 1.0);
			ds.Label = this.ds.Label;
			this.plot.Plot.AddDataSet(ds);

			if (combo2.Active == 1)
				this.plot.Plot.XLabel = Catalog.GetString("Unix time / s");
			else
				this.plot.Plot.XLabel = Catalog.GetString("Relative time / min");

			this.plot.Plot.YLabel = Catalog.GetString("Extracted Data / a.u.");
			this.plot.Plot.Title = Catalog.GetString("Directory:") + " " + dir.FullName;
			this.VBox.PackStart(this.plot);
			this.plot.ShowAll();
			
			do
			{
				this.Run();
			} while (this.run_dialog);
			
			this.Destroy();
		}
		
		private DataSet ExtractFwhmData(DirectoryInfo dir)
		{
			FileInfo[] files = dir.GetFiles("*.csv", SearchOption.AllDirectories);

			List<double> time = new List<double>();
			List<double> data = new List<double>();
			
			foreach (FileInfo file in files)
			{
				Spectrum spectrum = CSV.Read(file.FullName);
				
				TimeSpan span = spectrum.Timestamp - DateTime.MinValue;
				time.Add(span.TotalSeconds);
				
				try
				{
					DataSet.FWHMResult result = spectrum.Intensity.FWHM();
					data.Add(result.FWHM);
				}
				catch (Exception ex)
				{
					string msg = string.Format(Catalog.GetString("Cannot calculate FWHM for spectrum '{0}': {1}"),
					                           file.FullName, ex.Message);
					throw new Exception(msg);
				}
			}
			
			DataSet ds = new DataSet(time, data);
			
			ds -= new DataSet.Operand(ds.XMin(), 0f);
			ds.Sort();
			
			return ds;
		}
		
		private void OnDialogResponse(object o, ResponseArgs e)
		{
			switch ((int)e.ResponseId)
			{
				
			case 0:
				FileChooserDialog chooser = new QSFileChooserDialog(Catalog.GetString("Specify file name to save additional data characteristic"), 
					                                                UserInterface.UI.MainWindow,
				                                                    FileChooserAction.Save,
				                                                    Stock.Save,
				                                                    ResponseType.Ok,
				                                                    Stock.Cancel,
				                                                    ResponseType.Cancel);
				FileFilter filter = new FileFilter();
				filter.Name = Catalog.GetString("CSV files");
				filter.AddPattern("*.csv");
				chooser.AddFilter(filter);
				
				chooser.CurrentName = "add_data_" + this.directory.Name + "_" +
					    DateTime.Now.ToString("yyyy-MM-dd_HH-mm") + ".csv";
				
				if (!string.IsNullOrEmpty(UserInterface.Settings.Current.SaveAddDataFilePath))
					chooser.SetCurrentFolder(UserInterface.Settings.Current.SaveAddDataFilePath);
				else
					chooser.SetCurrentFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
				
				try
				{
					ResponseType choice = (ResponseType)chooser.Run();
					
					if (choice == ResponseType.Ok)
					{
						chooser.Hide();
						
						CSV.Write(this.ds, chooser.Filename, this.ui.OutputCultureInfo());
						UserInterface.Settings.Current.SaveAddDataFilePath = chooser.CurrentFolder;
					}
				}
				catch (Exception ex)
				{
					new InfoMessage(null, 
					                Catalog.GetString("Error while saving additional data characteristic: {0}"),
					                ex.Message);
				}
				finally
				{
					chooser.Destroy();
				}
				this.run_dialog = true;
				break;
				
			case 1:
				new Printer(null, this.plot.Plot);
				this.run_dialog = true;
				break;

			default:
				this.run_dialog = false;
				break;
				
			}
		}
	}
	
	public class ConstCheckButton : Gtk.CheckButton
	{
		private volatile bool set_active = true;
		
		
		public ConstCheckButton() : base()
		{
		}
		
		protected override void OnClicked ()
		{
			if (this.set_active)
				base.OnClicked();
		}

		public bool Hold
		{
			set { this.set_active = !value; }
		}
	}	
}