// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2018 Thomas Klose & Torsten Schache, 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.IO;
using System.Collections.Generic;
using System.Threading;

using Hiperscan.SGS;
using Hiperscan.Spectroscopy;
using Hiperscan.GtkExtensions.Widgets;
using Hiperscan.Unix;
using Hiperscan.Extensions.ApoIdentCommon;
using Gtk;

namespace Hiperscan.Extensions.ApoIdent
{
	public delegate bool MissingArchiveHandler();

	public partial class FinderWindow
	{
        public static string GetCreator()
        {
            string creator = Plugin.name + " " + Plugin.version;
            if (IdentServerClient.Info != null)
                creator += ", IdentModule " + IdentServerClient.Info.ShortIdentModuleVersion;

            return creator;
        }

		private void InitDeviceEvents()
		{
            this.Finder = new CombinedFinder();
			this.Finder.DeviceModeChanged 	+= this.OnDeviceModeChanged;
			this.Finder.InitDeviceFailed   	+= this.OnInitDeviceFailed;
			this.Finder.AverageSet			+= this.OnAverageSet;
		}

		private void InitFinderEvents()
		{
			if (this.Finder?.Device == null)
				return;
			
			this.Finder.AddExternalCalibrationStartedEvent(this.OnExternalCalibrationStarted);
			this.Finder.AddExternalCalibrationFinishedEvent(this.OnExternalCalibrationFinished);
			
            this.Finder.AddWaitForExternalBlackReferenceEvent(this.OnWaitForExternalBlackReference);
			this.Finder.AddWaitForExternalWhiteReferenceEvent(this.OnWaitForExternalWhiteReference);

            this.Finder.Warmup.WarmupStateChanged += this.OnWarmupStateChanged;
            this.Finder.Warmup.QuitRequested += this.OnWarmupQuitRequested;
		}

        private void  AddFinderInternalCalibrationEvents()
        {
            this.Finder.AddInternalCalibrationStartedEvent(this.OnFinderInternalCalibrationStarted);
            this.Finder.AddInternalCalibrationFinishedEvent(this.OnFinderInternalCalibrationFinished);  
        }

		private void InitializeDeviceAndIdentModule()
		{
			this.InitDeviceEvents();
            this.Update(FinderUpdateState.StartIdentServerAndDevice);

			if (this.InitState == ServerDeviceState.None)
			{
				Console.WriteLine("\tStarting identserver...");

				this.Finder.Reset();

				int major = Int32.Parse(Plugin.version.Split(new char[]{'.'})[0]);
				int minor = Int32.Parse(Plugin.version.Split(new char[]{'.'})[1]);

				this.InitState = this.IdentServerClient.Start(this.Address, this.TcpPort, new System.Version(major, minor), 
                                                              this.host.IsRelease, true);
				if (this.InitState == ServerDeviceState.StartIdentModuleFailed)
				{
					Thread.Sleep(1000);
					this.InitState = this.IdentServerClient.Start(this.Address, this.TcpPort, new System.Version(major, minor), 
                                                                  this.host.IsRelease, false);
				}
				if (this.InitState == ServerDeviceState.StartIdentModuleFailed)
				{
					this.InitState = ServerDeviceState.None;
					return;
				}
			}

			Console.WriteLine("\tInitializing instrument ...");
			do
			{
				this.InitState = this.Finder.InitDevice(this.host.DeviceList, this.host.ProcessEvents);
			}
			while (this.InitState == ServerDeviceState.StartDeviceTryAgain);

			if (this.InitState == ServerDeviceState.StartDeviceFailed)
			{
				this.Finder.Reset();
				this.InitState = ServerDeviceState.StartIdentModuleSuccess;
				this.CheckIdentModule();
				return;
			}

            this.Finder.Device.NewException += this.OnLUTExceptionThrown;
            this.Finder.Device.NewException += this.OnInvalidSpectrumExceptionThrown;
            this.Finder.Device.StateChanged += this.OnDeviceStateChanged;

			Console.WriteLine("\tRegistering instrument at IdentModule ...");
			this.InitState = this.IdentServerClient.RegisterDevice(this.Finder.Device);
			if (this.InitState == ServerDeviceState.ConnectDeviceToIdentModuleFailed)
			{
				this.Finder.Reset();
				this.InitState = ServerDeviceState.StartIdentModuleSuccess;
				this.CheckIdentModule();
				return;
			}

			Console.WriteLine("\tInitializing Finder ...");
            this.InitState = this.Finder.InitFinder(FinderWindow.GetCreator());
			if (this.InitState == ServerDeviceState.StartFinderFailed)
			{
				this.Finder.Reset();
				this.InitState = ServerDeviceState.StartIdentModuleSuccess;
				this.CheckIdentModule();
				return;
			}
         
			this.InitFinderEvents();

			Console.WriteLine("\tLoading data from identmodule ...");
           
            this.InitSubstanceManagement();

            this.InitState = this.IdentServerClient.LoadData(this.SubstanceManagement);
			if (this.InitState == ServerDeviceState.LoadingSubstanceClassesFailed)
			{
				this.Finder?.Reset();
				this.InitState = ServerDeviceState.StartIdentModuleSuccess;
				this.CheckIdentModule();
				return;
			}

			Console.WriteLine("\tFinderButton ...");
			this.InitializeFinderButton();
			this.host.ProcessEvents();
			
			Console.WriteLine("\tWarm-up ...");
            if (this.Finder.DeviceWarmup() == ServerDeviceState.DeviceWarmupFailed)
			{
				this.Finder.Reset();
				this.InitState = ServerDeviceState.StartIdentModuleSuccess;
                this.CheckIdentModule();
				return;
			}

            this.AddFinderInternalCalibrationEvents();

            this.CheckIdentModule();
            this.InitState = ServerDeviceState.Success;
            this.Finder.DeviceMode = Catalog.GetString("Ready");
            this.Update(FinderUpdateState.StartIdentServerAndDeviceSuccess);
            this.CreateArchive(this.GtkWindow);
		}
			
		private void CheckIdentModule()
		{
            if (this.Finder.QuitRequested == true)
                return;
            
			Console.WriteLine("\tChecking IdentModule release version... ");
			this.InitState = this.IdentServerClient.CheckReleaseVersion();
			if (this.InitState == ServerDeviceState.CheckingSubstanceDatabaseSuccess == true)
			{
				Console.WriteLine("\tChecking IdentModule for possible update... ");
				this.IdentServerClient.CheckForPendingUpdate();
			}
		}
			
		private void OnDeviceModeChanged(object sender, EventArgs e)
		{
			this.Update(FinderUpdateState.DeviceStateChanged);
		}

		private void OnInitDeviceFailed(object sender, EventArgs e)
		{
			this.HideGreetingWindow();
            string message = (string) sender + FinderWindow.ServiceForRepeatedError;

			this.host.ShowInfoMessage(this.finder_window, message);
            this.Update(FinderUpdateState.StartDeviceFailed, message);
		}

		private void OnAcquisitionSettingsChanged(object sender, EventArgs e)
		{
			if (this.Config.AcquisitionSettings.IdentServerSettingsCanged)
				this.SetServer();
		}

		private void OnExternalCalibrationStarted()
		{
            this.CurrentTopLeveLWindow.UpdateExternalCalibrationStarted();		
		}

		private void OnExternalCalibrationFinished(bool success) 
		{
            if (this.Finder.Current.ReferencingTimeoutMilliseconds < 0)
                return;

            if (success == true)
			{
                GLib.Timeout.Add((uint)this.Finder.Current.ReferencingTimeoutMilliseconds + 1000, 
					delegate()
					{
                        this.CurrentTopLeveLWindow.UpdateDeviceAfterExternalCalibration();
						return false;
					});
			}
		}



		private void OnFinderInternalCalibrationStarted()
		{
            this.CurrentTopLeveLWindow.UpdateFinderInternalCalibrationStarted();
		}

		private void OnFinderInternalCalibrationFinished()
		{
            if (this.CurrentTopLeveLWindow.ClassifierIsFluid == true)
			{
				if (this.Finder.FinderExists == true)
					GLib.Timeout.Add(this.Finder.Current.RecalibrationTimeoutMilliseconds + 1000, 
						delegate()
						{
                    if (this.CurrentTopLeveLWindow.ClassifierIsFluid == true)
							{
                        this.CurrentTopLeveLWindow.UpdateDeviceAfterInternalCalibration();
							}
							return false;
						});
			}
		}

		private void WarmupBegin()
		{
			this.progressbar.Fraction = 0.0;
			this.progressbar.Visible = true;

			if (this.greeting_window != null)
            {
                this.greeting_window.KeepAbove = false;
				this.greeting_window.SetWarmupMode();
            }
		}

		private void WarmupProgressed(double fraction, double temperature)
		{
			if (this.greeting_window != null)
                this.greeting_window.SetTemperature(temperature);
            this.progressbar.Fraction = fraction;
		}

		private void WarmupFinished()
		{
			this.progressbar.Visible = false;

			if (this.Finder.QuitRequested == true)
			{
				GLib.Timeout.Add(1000, delegate {
					this.OnQuitButtonClicked(null, EventArgs.Empty);
    					return false;
				});
			}

			if (this.greeting_window != null)
				this.greeting_window.KeepAbove = true;
		}

        private void WarmupFailed(string error)
        {
            if (this.greeting_window != null)
                this.greeting_window.Hide();
            string message = String.Format(Catalog.GetString("The warming up of the device failed: {0}"),
                                           error);
            message += ServiceForRepeatedError;
            this.host.ShowInfoMessage(this.finder_window, message);
            this.Update(FinderUpdateState.DeviceWarmupFailed, message);
        }

        private void OnWarmupStateChanged(object sender, EventArgs e)
        {
            switch(this.Finder.Warmup.State)
            {
                case WarmupState.Begin:
                    this.WarmupBegin();
                    break;

                case WarmupState.Failed:
                    this.WarmupFailed((sender as DeviceWarmup).Exception.Message);
                    break;

                case WarmupState.Finsihed:
                    this.WarmupFinished();
                    break;

                case WarmupState.Warming:
                    DeviceWarmup warmup = sender as DeviceWarmup;
                    this.WarmupProgressed(warmup.Fraction, warmup.CurrentTemperature);
                    break;

                default:
                    break;
            }
        }

        private void OnWarmupQuitRequested(object sender, EventArgs e)
        {
            if (this.Finder.QuitRequested == true)
            {
                this.DetailSectionState.Set(DetailSectionStates.SaveReport, false);
                this.Finder.Warmup.State = WarmupState.Finsihed;
            }
        }

		private void OnAverageSet(object sender, EventArgs e)
		{
			this.host.UpdateProperties();
			this.Update(FinderUpdateState.AveragingSet);
		}

		private void OnWaitForExternalBlackReference()
		{
			FinderReferenceDialog dialog = new FinderReferenceDialog(this.CurrentTopLeveLWindow.GtkWindow, FinderReferenceType.ExternalBlack);
			dialog.Run();				
		}

		private void OnWaitForExternalWhiteReference()
		{
			FinderReferenceDialog dialog = null;
			
            if (this.Finder.Current.ProbeType == ProbeType.FinderInsert || this.Finder.Current.ProbeType == ProbeType.FinderSDInsert)
                dialog = new FinderReferenceDialog(this.CurrentTopLeveLWindow.GtkWindow, FinderReferenceType.ExternalInsert);
			else
                dialog = new FinderReferenceDialog(this.CurrentTopLeveLWindow.GtkWindow, FinderReferenceType.ExternalWhiteZenith);

            dialog.Run();
		}

		private void OnFinderSwitchCheckbuttonToggled(object sender, EventArgs e)
		{
			if (this.IsUpdating == true)
				return;
			
			this.insert_checkbutton.QueueDraw();
			this.host.ProcessEvents();

			if (this.insert_checkbutton.Active == true)
				this.Finder.SwitchFinder(ProbeType.FinderInsert);
			else
				this.Finder.SwitchFinder(ProbeType.FinderStandard);

            this.Update(FinderUpdateState.FinderChanged);
		}


		private void OnLUTExceptionThrown(Exception ex)
		{
            if (this.Finder.Warmup.State == WarmupState.Warming)
				return;
			
			if (ex is InvalidLUTException)
			{
				string message = Catalog.GetString("Please turn off the analysis device and turn it on again.\n" +
				                                   "Internal test failed: reading error.\n" + 
				                                   "Configurations will be loaded again.");
				message += ServiceForRepeatedError;
				this.host.ShowInfoMessage(this.finder_window, message);
			}
		}


		private void OnInvalidSpectrumExceptionThrown(Exception ex)
		{
            if (this.Finder.Warmup.State == WarmupState.Warming)
				return;
			
			if (ex is InvalidSpectrumException)
			{
				string message = String.Format(Catalog.GetString("Error while acquiring spectrum : {0}."), ex.Message);
				this.host.ShowInfoMessage(this.finder_window, message);
			}
		}

		private void OnDeviceStateChanged (Device dev) 
		{
			if (this.finder_window == null)
				return;

			if (this.Finder.IsDeviceReady == false)
			{
				string message = Catalog.GetString("Device is not connected or cannot be recognized. " + 
							                                 "Please check in the Qickstep program if the device " +
							                                 "is recognized and connected. " + 
							                                 "You can find these settings on the left side at the " + 
							                                 "region called \"Spectrometer\".");
				message += ServiceForRepeatedError;
				this.FinderAnimation.SetAnimation(AnimationType.Error);
				this.FinderAnimation.SetError(AnimationErrorType.DeviceNotReady, message);
				this.Finder.DeviceWasDisconnected = true;
			}
			else
			{
				if (this.Finder.DeviceWasDisconnected == true)
				{
					this.Finder.DeviceMode = Catalog.GetString("Ready");
					this.FinderAnimation.SetAnimation(AnimationType.None);
					this.Finder.DeviceWasDisconnected = false;
				}
			}

			this.Update(FinderUpdateState.DeviceReadyChanged);
		}
	
		void OnDeviceListChanged(DeviceList list) 
		{
			bool re_init = false; 
			lock (list)
			{
				if (list.Count > CombinedFinder.DeviceList.Count)
				{
					if (this.InitState != ServerDeviceState.Success)
						re_init = true;
				}
				else
				{
					//any device removed
					if (list.Contains(this.Finder.Serial) == false)
					{
						//current device is removed
						Console.WriteLine("Device {0} removed", this.Finder.Serial);
						if (this.InitState == ServerDeviceState.Success)
							this.InitState = ServerDeviceState.StartIdentModuleSuccess;
						else
							this.InitState = ServerDeviceState.None;

						this.Finder.Reset();
						this.Update(FinderUpdateState.DeviceRemoved);
					}
				}

				if (re_init == false)
				{
					foreach (string serial in list.Keys)
					{
						if (CombinedFinder.DeviceList.Contains(serial) == false)
							CombinedFinder.DeviceList.Add(serial);
					}

					foreach (string serial in new List<string>(CombinedFinder.DeviceList))
					{
						if (list.Contains(serial) == false)
							CombinedFinder.DeviceList.Remove(serial);
					}
				}
			}

			if (re_init == true)
				this.InitializeDeviceAndIdentModule();
		}
        
		private void InitializeFinderButton()
		{
			GLib.Timeout.Add(500, delegate() 
				{
					try
					{                   
						if (this.InitState != ServerDeviceState.Success)
							return true;
						
						if (this.Finder.IsDeviceReady										        &&
                            this.CurrentTopLeveLWindow.GtkWindow.HasToplevelFocus				    &&			
                            this.CurrentTopLeveLWindow.SubstanceManagmentReady == true			    &&
                            this.CurrentTopLeveLWindow.Busy == false)
						{
                        if (this.CurrentTopLeveLWindow.ClassifierIsFluid == true)
								this.PollFinderButton = this.Finder.Current.RecalibrationIsPending == false;
							else
								this.PollFinderButton = true;
						}
						else
						{
							this.PollFinderButton = false;
						}

						if (this.PollFinderButton && this.Finder.HardwareFeatures.HasFlag(HardwareFeatures.StartButton))
						{
							try
							{
								this.Finder.Device.StateNotification = false;
								if (this.Finder.Device.FinderButtonPressed())
								{
                                    this.CurrentTopLeveLWindow.StartButton.Activate();
								}
							}
							finally
							{
								this.Finder.Device.StateNotification = true;
							}
						}
					}
					catch (Exception ex)
					{
						Console.WriteLine("Exception in Finder-Button Loop (deactivate): " + ex.ToString());
						try {this.PollFinderButton = false;} catch(Exception){}
					}
					return true;
				});
		}

		private volatile bool poll_finder_button = false;

        public bool PollFinderButton
		{
            get { return this.poll_finder_button; }
			
			set
			{
				try
				{
                    if (this.InitState != ServerDeviceState.Success)
                        return;

                    if (value == this.poll_finder_button)
                        return;
                    
                    if (this.Finder.Device.GetState() != Device.State.Idle)
                        return;
                    
                    if (this.CurrentTopLeveLWindow.GtkWindow.Visible == false)
                        return;

                    Console.WriteLine("Setting PollFinderButton to {0}", value);
                    this.poll_finder_button = value;

					this.Finder.Device.WaitForIdle(1000);
					if (this.Finder.HardwareFeatures.HasFlag(HardwareFeatures.StartButton))
						this.Finder.Device.SwitchFinderStatusLED(value);

                    this.CurrentTopLeveLWindow.UpdateFinderStartButton();
				}
				catch(Exception ex)
				{ 
					Console.WriteLine("Poll finder button: {0}", ex.Message);
				}
			}
		}


        private void CheckArchive(Gtk.Window parent)
        {
            bool is_main_win = parent == this.GtkWindow;

            string message = String.Empty;
            this.Config.Profiles.NeedCheck = false;

            if (Directory.Exists(FilesAndDirs.Archive) == false)
            {
                this.Config.Profiles.NeedCheck = true;

                if (is_main_win == true)
                {
                    message = Catalog.GetString(
                    "The Apo-Ident archive directory \"{0}\" for configuration profile \"{1}\" is unavailable. " +
                    "This may be a temporary problem with a network resource or removable storage device.\n\n" +
                    "To create a new archive press \"OK\"." +
                    "Choose \"Cancel\" to open the Preferences dialogue. There you can modify settings for the archive directory.");
                }
                else
                {
                    message = Catalog.GetString(
                    "The Apo-Ident archive directory \"{0}\" for configuration profile \"{1}\" is unavailable. " +
                    "This may be a temporary problem with a network resource or removable storage device.\n\n" +
                    "To create a new archive press \"OK\"." +
                    "Otherwise current changes will not be applied. Please check settings for the archive directory in the Preferences dialogue.");
                }



                MessageDialog mdlg = new MessageDialog(parent,
                                                 DialogFlags.Modal,
                                                 Gtk.MessageType.Warning,
                                                       ButtonsType.OkCancel,
                                                 message,
                                                 this.Config.Profiles.Current.Archive,
                                                 this.Config.Profiles.Current.Name);

                mdlg.Title = Catalog.GetString("Missing Archive");
                mdlg.IconList = ApoIdentStock.FinderIcons;
                ResponseType answer = (ResponseType)mdlg.Run();
                mdlg.Destroy();

                if (answer == ResponseType.Ok)
                    this.Config.Profiles.NeedCheck = false;
            }
        }

        public void CreateArchive(Gtk.Window parent)
		{
            if (this.Config.Profiles.Current == null)
                return;


            this.DetailSectionState.Set(DetailSectionStates.ArchiveExists, Directory.Exists(FilesAndDirs.Archive));
            this.Config.Profiles.NeedCheck = false;

            if (this.Config.Profiles.Current.ArchiveCreated == true)
                this.CheckArchive(parent);

            if (this.Config.Profiles.NeedCheck == true)
                return;

            string error_message = String.Empty;

			try
			{
				if (Directory.Exists(FilesAndDirs.Archive) == false)
                    Directory.CreateDirectory(FilesAndDirs.Archive); 

			}
			catch(Exception ex)
			{
                error_message = ex.Message;
				Console.WriteLine("Cannot create archive '{0}': {1}", FilesAndDirs.Archive, ex);
			}
			
			if (Directory.Exists(FilesAndDirs.Archive) == true)
			{
				try
				{					
					if (Directory.Exists(FilesAndDirs.ArchiveReferencingDir) == false)
					{
                        Directory.CreateDirectory(FilesAndDirs.ArchiveReferencingDir);	
                        new DirectoryInfo(FilesAndDirs.ArchiveReferencingDir).Attributes = FileAttributes.Hidden;
					}

                    this.Finder.SetFinderAutoSaveDirectory(FilesAndDirs.ArchiveReferencingDir);

					if (Directory.Exists(FilesAndDirs.LocalizedMetaDir) == false)
					{
						Directory.CreateDirectory(FilesAndDirs.LocalizedMetaDir);
						new DirectoryInfo(Path.GetDirectoryName(FilesAndDirs.LocalizedMetaDir)).Attributes = FileAttributes.Hidden;
					}

					this.Config.Profiles.Current.AdditionalCompletionEntries = AdditionalTestEntries.Read();
                    if (this.Config.Profiles.Current.MergedAdditionalData == false && this.Config.AcquisitionSettings.Locale != I18n.Locale.LocaleType.Unknown)
                    {
                        AdditionalTestEntries.Merge(this.Config.Profiles.Current.AdditionalCompletionEntries);
                        this.Config.Profiles.Current.MergedAdditionalData = true;
                    }

					if (this.Config.Profiles.Current.SubstanceExtension == null)
                        this.Config.Profiles.Current.SubstanceExtension = 
                            new SubstanceExtension(this.IdentServerClient);

					if (this.InitState == ServerDeviceState.Success)
						this.SubstanceManagement.InitModelExtension();

                    this.Config.Profiles.Current.ArchiveCreated = true;
				}
				catch(Exception ex)
				{
                    error_message = ex.Message;                  
					Console.WriteLine("Cannot create referencing dir '{0}': {1}", FilesAndDirs.ArchiveReferencingDir, ex);
				}
			}

            if (String.IsNullOrEmpty(error_message) == false)
            {
                MessageDialog mdlg = new MessageDialog(this.finder_window,
                                                DialogFlags.Modal,
                                                          Gtk.MessageType.Error,
                                                          ButtonsType.Ok,
                                                          Catalog.GetString("Cannot create archive directory {0}: {1} " +
                                                                            "\n\n Please check the settings for the archive directory. Otherwise data can be lost."),
                                                       this.Config.Profiles.Current.Archive, error_message);

                mdlg.Title = Catalog.GetString("Creating Archive Failed");
                mdlg.IconList = ApoIdentStock.FinderIcons;
                ResponseType answer = (ResponseType)mdlg.Run();
                mdlg.Destroy();
                this.Finder.ResetFinderAutoSaveDirectory();
            }

			this.DetailSectionState.Set(DetailSectionStates.ArchiveExists, Directory.Exists(FilesAndDirs.Archive));
		}

		public CombinedFinder Finder			{get; private set;}
		public ServerDeviceState InitState		{get; set;}
	}
}

