// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2017 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 Hiperscan.SGS;
using Hiperscan.Unix;
using Hiperscan.Spectroscopy;
using System.IO;
using System.Collections.Generic;
using Hiperscan.Extensions.ApoIdentCommon;
using Hiperscan.Common;


namespace Hiperscan.Extensions.ApoIdent
{
	
	public class CombinedFinder
	{
		public event EventHandler DeviceModeChanged 	= null;
		public event EventHandler InitDeviceFailed 		= null;
		public event EventHandler AverageSet			= null;

		public void AddExternalCalibrationStartedEvent(Finder.ExternalCalibrationStartedHandler handler)
		{
			if (this.StandardFinder != null)
				this.StandardFinder.ExternalCalibrationStarted += handler;
			if (this.InsertFinder != null)
				this.InsertFinder.ExternalCalibrationStarted += handler;
		}

		public void AddExternalCalibrationFinishedEvent(Finder.ExternalCalibrationFinishedHandler handler)
		{
			if (this.StandardFinder != null)
				this.StandardFinder.ExternalCalibrationFinished += handler;
			if (this.InsertFinder != null)
				this.InsertFinder.ExternalCalibrationFinished += handler;
		}

		public void AddInternalCalibrationStartedEvent(Finder.InternalCalibrationStarteddHandler handler)
		{
			if (this.StandardFinder != null)
				this.StandardFinder.InternalCalibrationStarted += handler;
			if (this.InsertFinder != null)
				this.InsertFinder.InternalCalibrationStarted += handler;
		}

		public void AddInternalCalibrationFinishedEvent(Finder.InternalCalibrationFinishedHandler handler)
		{
			if (this.StandardFinder != null)
				this.StandardFinder.InternalCalibrationFinished += handler;
			if (this.InsertFinder != null)
				this.InsertFinder.InternalCalibrationFinished += handler;
		}

		public void AddInternalCalibrationFailedEvent(Finder.InternalCalibrationFailedHandler handler)
		{
			if (this.StandardFinder != null)
				this.StandardFinder.InternalCalibrationFailed += handler;
			if (this.InsertFinder != null)
				this.InsertFinder.InternalCalibrationFailed += handler;
		}

		public void AddWaitForExternalBlackReferenceEvent(Finder.WaitForExternalEmptyReferenceHandler handler)
		{
			if (this.StandardFinder != null)
				this.StandardFinder.WaitForExternalEmptyReference += handler;
			if (this.InsertFinder != null)
				this.InsertFinder.WaitForExternalEmptyReference += handler;
		}

		public void AddWaitForExternalWhiteReferenceEvent(Finder.WaitForExternalWhiteReferenceHandler handler)
		{
			if (this.StandardFinder != null)
				this.StandardFinder.WaitForExternalWhiteReference += handler;
			if (this.InsertFinder != null)
				this.InsertFinder.WaitForExternalWhiteReference += handler;
		}
			
		public CombinedFinder ()
		{
			this.Serial 				= String.Empty;
			this.DeviceName				= String.Empty;
			this.ScanEngineVersion		= String.Empty;
			this.DeviceWasDisconnected	= true;
		}


		public ServerDeviceState InitDevice(DeviceList dev_list, IdleTaskHandler idle_task_handler)
		{
			Console.WriteLine("\tDevice...");
			this.DeviceMode = Catalog.GetString("Not ready");
			this.Task = idle_task_handler;
			this.QuitRequested = false;
			this.DeviceWasDisconnected = true;
            this.DimmerValue = 0;

			lock (dev_list)
			{
				dev_list.Update();
				CombinedFinder.DeviceList= new HashSet<string>(dev_list.Keys);

				foreach (Device dev in dev_list.Values)
				{
					bool was_disconnected = false;
					if (dev.IsConnected == false)
					{
						try
						{
							dev.Open();
							was_disconnected = true;
						}
						catch(Exception)
						{ 
							continue; 
						}
					}

					try
					{
						dev.WaitForIdle(15000);
					}
					catch (TimeoutException) {}

					if (dev.IsFinder)
					{
						this.Device = dev;
						break;
					}

					if (was_disconnected)
					{
						try
						{
							dev.Close();
						}
						catch (Exception) {}
					}
				}
			}

			if (this.Device == null)
			{
				string message = string.Format(Catalog.GetString("Device cannot be found. Please check if the Apo-Ident device is turned " +
				                                                 "on and the connection with the computer is established. Also check whether " +
				                                                 "another instance of {0} is already in use."), Plugin.name);

				int dev_cout = dev_list.Count;

				if (this.InitDeviceFailed != null)
					this.InitDeviceFailed(message, EventArgs.Empty);

				dev_list.Update();
				if (dev_list.Count > dev_cout)
					return ServerDeviceState.StartDeviceTryAgain;

				return ServerDeviceState.StartDeviceFailed;
			}
			else
			{
                ProbeType probe_type = this.Device.GetFinderProbeType();
                if (probe_type != ProbeType.FinderStandard && probe_type != ProbeType.FinderSDStandard)
                {
                    if (this.InitDeviceFailed != null)
                        this.InitDeviceFailed(Catalog.GetString("This device type is not supported by this application."), EventArgs.Empty);

                    return ServerDeviceState.StartDeviceFailed;
                }
                    
				this.Serial 			= this.Device.Info.SerialNumber;
				this.DeviceName			= this.Device.CurrentConfig.Name;
				this.ScanEngineVersion 	= this.Device.ScanEngineVersion.ToString();
                try
                {
                    this.DimmerValue    = this.Device.ReadDimmerParameter();
                }
                catch (NotImplementedException)
                {
                }

                try
                {
                    this.OemSerialNumber = this.Device.ReadOemSerialNumber();
                }
                catch (NotImplementedException)
                {
                }

				return ServerDeviceState.StartDeviceSuccess;
			}
		}

		public ServerDeviceState InitFinder(string creator)
		{
			try
			{
                this.InitStandardFinder(creator);
				this.InitInsertFinder(creator);
                this.Warmup = new ApoIdentCommon.DeviceWarmup(this.StandardFinder, this.Task);

				return ServerDeviceState.StartFinderSuccess;
			}
			catch(Exception ex)
			{
				this.Device 		= null;
				this.StandardFinder	= null;
				this.InsertFinder	= null;
				this.Current 		= null;

				string message = Catalog.GetString("Device cannot be found. Please check if the Apo-Ident device is turned " +
					"on and the connection with the computer is established.");

				if (this.InitDeviceFailed != null)
					this.InitDeviceFailed(message, EventArgs.Empty);
				
				Console.WriteLine(ex);
				return ServerDeviceState.StartFinderFailed;
			}
		}

        private void InitStandardFinder(string creator)
		{
            this.StandardFinder = new Finder(this.Device, ReferenceWheelVersion.Auto, ExternalWhiteReferenceType.Zenith, creator);
            this.StandardFinder.WhiteReferenceConversionMode = WhiteReferenceConversionMode.None;

			this.StandardFinder.SupportInfoMessage = Catalog.GetString("\n\nIn case of permanent problems please contact Hiperscan customer support " +
			                                                           "under kundenservice@apo-ident.de or +49 351 212 496 33 " +
			                                                           "for further information.");

            if (this.StandardFinder.ProbeType == ProbeType.FinderSDStandard)
                this.StandardFinder.TransformationType = TransformationTypes.FinderSD_ApoIdent;

            this.StandardFinder.PlausibilityChecks = ApoIdentEnvs.NoFinderPlausibilityCheckOff;

			this.StandardFinder.ReferenceWheelVersion = Finder.GetApoIdentReferenceWheelVersion(this.StandardFinder.ReferenceWheelVersion);

			this.StandardFinder.CorrectionMask 					= CorrectionMask.All ^ CorrectionMask.Adc;
			this.StandardFinder.ReferencingTimeoutMilliseconds 	= 1000 * 60 * 60;
			this.StandardFinder.IdleTaskHandler 				= this.Task;
            this.StandardFinder.LightSourceDamageThreshold 		= double.Parse(Environment.GetEnvironmentVariable(Common.Env.ApoIdent.LIGHT_SOURCE_DAMAGE_THRESHOLD) ?? "1.8", System.Globalization.CultureInfo.InvariantCulture);
			this.StandardFinder.ResetReferences();

            this.StandardFinder.InternalReferenceAveraging = this.GetInternalAveraging(false);
            this.StandardFinder.ExternalReferenceAveraging = this.GetExternalAveraging(false);
			this.Current = this.StandardFinder;

			try
			{
				if (this.Device.IsConnected == false)
					this.Device.Open();

				this.DeviceMode = Catalog.GetString("Initializing");
				this.Task();
				this.Device.WaitForIdle(10000);

				if (this.HardwareFeatures.HasFlag(HardwareFeatures.StartButton))
					this.Device.SwitchFinderStatusLED(false);
				else
					this.Device.SetFinderStatus(FinderStatusType.Idle);

				this.Device.SetFinderLight(false, this.Task);
				this.Idle();

				this.Device.StateChanged += this.OnDeviceStateChanged;
			}
			catch(Exception ex)
			{
				this.Reset();
				string message = string.Format(Catalog.GetString("Cannot initialize spectrometer: {0}"), ex.Message);

				if (this.InitDeviceFailed != null)
					this.InitDeviceFailed(message, EventArgs.Empty);
			}
		}

		private void InitInsertFinder(string creator)
		{
			if (this.FinderExists == false)
				return;

            this.InsertFinder = new Finder(this.Device, ReferenceWheelVersion.Auto, ExternalWhiteReferenceType.Zenith, creator);
			this.InsertFinder.WhiteReferenceConversionMode = WhiteReferenceConversionMode.ZenithToTitaniumdioxide;

			this.InsertFinder.SupportInfoMessage = Catalog.GetString("\n\nIn case of permanent problems please contact Hiperscan customer support " +
			                                                        "under kundenservice@apo-ident.de or +49 351 212 496 33 " +
			                                                        "for further information.");

			if (this.InsertFinder.ProbeType == ProbeType.FinderSDStandard)
            {
				this.InsertFinder.ProbeType = ProbeType.FinderSDInsert;
                this.InsertFinder.TransformationType = TransformationTypes.FinderSD_ApoIdent;
            }
			else
            {
				this.InsertFinder.ProbeType = ProbeType.FinderInsert;
            }

            this.InsertFinder.PlausibilityChecks = ApoIdentEnvs.NoFinderPlausibilityCheckOff;

			this.InsertFinder.ReferenceWheelVersion = Finder.GetApoIdentReferenceWheelVersion(this.InsertFinder.ReferenceWheelVersion);

			this.InsertFinder.CorrectionMask                 = CorrectionMask.All ^ CorrectionMask.Adc;
			this.InsertFinder.ReferencingTimeoutMilliseconds = 1000 * 60 * 60;
			this.InsertFinder.IdleTaskHandler                = this.Task;
            this.InsertFinder.LightSourceDamageThreshold     = double.Parse(Environment.GetEnvironmentVariable(Common.Env.ApoIdent.LIGHT_SOURCE_DAMAGE_THRESHOLD) ?? "1.8", System.Globalization.CultureInfo.InvariantCulture);
			this.InsertFinder.ResetReferences();

            this.InsertFinder.InternalReferenceAveraging = this.GetInternalAveraging(true);
            this.InsertFinder.ExternalReferenceAveraging = this.GetExternalAveraging(true);
		}

		public void Idle()
		{
			try
			{
				this.Device.WaitForIdle(1000);
				this.Device.SetFinderWheel(FinderWheelPosition.White, this.Task);
				if (this.Device.IsIdle)
					this.DeviceMode = Catalog.GetString("Ready");
			}
			catch(Exception)
			{
				this.DeviceMode = Catalog.GetString("Error");
			}
		}

        public void ResetFinderAutoSaveDirectory()
        {
            if (this.StandardFinder != null)
                this.StandardFinder.AutoSaveReferences = false;

            if (this.InsertFinder != null)
                this.InsertFinder.AutoSaveReferences = false;
        }

		public void SetFinderAutoSaveDirectory(string referencing_directory)
		{
			bool success = false;
			if (this.StandardFinder != null)
			{
				try
				{
					if (this.StandardFinder != null && this.StandardFinder.AutoSaveReferences == true)
						this.StandardFinder.ReferenceFileSet.CopyTo(referencing_directory);
				}
				catch(Exception)
				{					
				}

				if (Directory.Exists(referencing_directory) == true)
				{
					this.StandardFinder.AutoSaveDirectory 	= referencing_directory;
					this.StandardFinder.AutoSaveReferences 	= true;
					success = true;
				}
				else
				{
					this.StandardFinder.AutoSaveDirectory 	= String.Empty;
					this.StandardFinder.AutoSaveReferences 	= false;	
				}
			}

			if (this.InsertFinder != null)
			{
				try
				{
					if (this.InsertFinder != null && this.InsertFinder.AutoSaveReferences == true)
						this.InsertFinder.ReferenceFileSet.CopyTo(referencing_directory);
				}
				catch(Exception)
				{
					//Console.WriteLine(ex);
				}

				if (Directory.Exists(referencing_directory) == true)
				{
					this.InsertFinder.AutoSaveDirectory 	= referencing_directory;
					this.InsertFinder.AutoSaveReferences = true;
				}
				else
				{
					this.InsertFinder.AutoSaveDirectory 	= String.Empty;
					this.InsertFinder.AutoSaveReferences = false;
				}
			}

			if (success == false)
				return;
		}

        private ushort GetAveraging(bool insert)
        {
            ushort average = 500;
            if (insert == true)
                average = 2000;
            
                if (ApoIdentEnvs.AvergeOn)
            {
                try
                {
                    average = ApoIdentEnvs.Average;
                }
                catch (Exception)
                {
                    average = 500;
                }

                average = average > 2000 ? (ushort)2000 : average;
            }

            return average;
        }

        private ushort GetInternalAveraging(bool insert)
        {
            ushort average = 500;
            if (ApoIdentEnvs.AvergeOn)
            {
                try
                {
                    average = ApoIdentEnvs.Average;
                }
                catch (Exception)
                {
                    average = 500;
                }

                average = average > 500 ? (ushort)500 : average;
            }

            return average;
        }

        private ushort GetExternalAveraging(bool insert)
        {
            return 2000;
        }

        public ServerDeviceState DeviceWarmup()
		{
            this.Warmup.Start();

            if (this.Warmup.WarmupSuccess)
                return ServerDeviceState.DeviceWarmupSuccess;
            else
                return ServerDeviceState.DeviceWarmupFailed;
		}


		public bool IsDeviceReady
		{
			get
			{
				if (this.DeviceExists == false)
					return false;

				Device.State state = this.Device.GetState();
				return  (state != Device.State.Disconnected) &&
					(state != Device.State.Unprogrammed) &&
					(state != Device.State.Unknown);

			}
		}

		public void SwitchFinder(ProbeType probe_type)
		{
			switch(probe_type)
			{

			case ProbeType.FinderInsert:
			case ProbeType.FinderSDInsert:
				this.Current = this.InsertFinder;	
				break;

			case ProbeType.FinderStandard:
			case ProbeType.FinderSDStandard:
				this.Current = this.StandardFinder;
				break;

			default:
				break;

			}
		}

		public void Reset()
		{
			this.Device 		= null;
			this.Current 		= null;
			this.InsertFinder	= null;
			this.StandardFinder	= null;
			this.DeviceMode = Catalog.GetString("Not ready");
		}

		private void OnFinderStatusChanged(Device dev, FinderStatus status) 
		{
			if (this.FinderExists == true)
				this.DeviceMode = this.Current.GetStatusString(status);
			else
				this.DeviceMode = Catalog.GetString("Not ready");
		}

		private void OnDeviceStateChanged(Device dev) 
		{
			Device.State state = dev.GetState();
			Console.WriteLine(state);
			switch (state)
			{

			case Device.State.Disconnected:
				this.DeviceMode = Catalog.GetString("Not ready");
				break;

			case Device.State.Unknown:
			case Device.State.Unprogrammed:
				this.DeviceMode = Catalog.GetString("Error");
				break;

			case Device.State.ReadConfig:
			case Device.State.WriteConfig:
				this.DeviceMode = Catalog.GetString("Initializing");
				break;

			}
		}

		private string device_mode = String.Empty;
		public string DeviceMode
		{
			get
			{
				return this.device_mode;
			}

			set
			{
				if (this.Device == null)
					value = Catalog.GetString("Not ready");

				Console.WriteLine("Changing device mode to \"{0}\"", value);
			

				this.device_mode = value;

				if (this.DeviceModeChanged != null)
					this.DeviceModeChanged(value, EventArgs.Empty);
			}
		}

		public bool DeviceExists
		{
			get
			{
				return this.Device != null;
			}
		}

		public bool FinderExists
		{
			get
			{
				return this.Current != null;
			}
		}





		private Finder current = null;
		public Finder Current	
		{
			get
			{
				return this.current;
			}
			private set
			{
				if (value == this.current)
					return;

				if (this.current != null)
					this.current.FinderStatusChanged -= this.OnFinderStatusChanged;

				if (value != null)
					value.FinderStatusChanged += this.OnFinderStatusChanged;

				this.current = value;

				if (this.current != null && this.IsDeviceReady == true)
					this.DeviceMode = Catalog.GetString("Ready");
				else
					this.DeviceMode = Catalog.GetString("Not ready");

                if (current == null)
                    return;

                bool insert = this.current.ProbeType == ProbeType.FinderInsert || 
                                  this.current.ProbeType == ProbeType.FinderSDInsert;

                this.Device.WaitForIdle(5000);
                this.Device.CurrentConfig.Average = this.GetAveraging(insert);
                this.Device.WriteConfig();

                if (this.AverageSet != null)
                    this.AverageSet(this, EventArgs.Empty);
			}
		}

		public Device Device 							{get; private set;}
		private Finder StandardFinder					{get; set;}
		private Finder InsertFinder						{get; set;}
        public DeviceWarmup Warmup                      {get; private set;}

		private IdleTaskHandler Task					{get; set;}
		public bool QuitRequested						{get; set;}

		public string Serial							{get; private set;}
		public string DeviceName						{get; private set;}
		public string ScanEngineVersion					{get; private set;}

        public byte DimmerValue                         {get; private set;}

		public bool DeviceWasDisconnected	{get; set;}

		public HardwareFeatures HardwareFeatures
		{
			get { return this.Current.HardwareFeatures; }
		}

        public static HashSet<string> DeviceList {get; private set;} = new HashSet<string>();
        public string OemSerialNumber { get; private set; }
	}
}

