// 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.AnalysisModule;
using Hiperscan.ApoIdentCore.IO;
using Hiperscan.ApoIdentCore.Misc;
using Hiperscan.Common.SearchIpAddress;
using Hiperscan.ApoIdentCommon;
using Hiperscan.ApoIdentCommon.ApoConfig;
using Hiperscan.ApoIdentCommon.ApoConfig.IO;
using Hiperscan.SGS;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Benchtop.FinderButtonPolling;
using Hiperscan.SGS.Benchtop.Transformations;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;

namespace Hiperscan.ApoIdentCore.ComplexFinder
{
    public partial class CombinedFinder
    {
        public const string NO_CREATOR = "NO CREATOR";

        public CombinedFinder(DeviceList device_list, 
                              ApoIdentFileSystem file_system,
                              DeviceSettings device_settings)
        {
            this.DeviceList = device_list;
            this.FileSystem = file_system;
            this.DeviceSettings = device_settings;
            this.FinderButton = new FinderButton<FinderButtonState>();

            this.Reset();
        }

        private SearchIPAdr.Parameter InitSearchParameter()
        {
            SearchIPAdr.Parameter parameter = new SearchIPAdr.Parameter();
            parameter.WindowsMdnsSearchPath = this.FileSystem.MdnsServicePath;
            parameter.Restrictions = SearchIPAdr.SearchRestrictions.OnlyFirstDevice;
            parameter.AddServiceName(SearchIPAdr.ServiceTypes.MDNS_AILINK);

            if (this.DeviceSettings.SearchMode == DeviceSearchModes.Custom)
                parameter.UserdefinedAddress = new SearchIPAdr.FullAddress(this.DeviceSettings.Url, (uint)this.DeviceSettings.TcpPort);


            if (String.IsNullOrEmpty(this.DeviceSettings.LastUrl) == false)
                parameter.LastChanceAddress = new SearchIPAdr.FullAddress(this.DeviceSettings.LastUrl, (uint)this.DeviceSettings.LastTcpPort);

            return parameter;
        }

        public bool InitDevice(IdleTaskHandler task = null)
        {
            this.Reset();

            try
            {
                this.RemoteDevices = new RemoteDevices(10000, false);
                Console.WriteLine("Searchhing devices...");
                SearchIPAdr.Parameter parameter = this.InitSearchParameter();                
                Console.WriteLine(parameter);

                this.RemoteDevices.SearchDevices(parameter);
                this.Device = this.RemoteDevices.FirstDevice;

                if (this.RemoteDevices.LastUsedRemoteAdress != null)
                {
                    this.DeviceSettings.LastUrl = this.RemoteDevices.LastUsedRemoteAdress.Address;
                    this.DeviceSettings.LastTcpPort = (int) this.RemoteDevices.LastUsedRemoteAdress.Port;
                }

                if (this.DeviceExists && this.Device.IsConnected == false)
                    this.Device.Open();
            }
            catch (Exception ex)
            {
                this.Reset();
                this.Exception = ex;
            }

            if (this.Device == null)
            {
                this.ErrorTitle = Catalog.GetString("No device found");
                this.ErrorMessage = string.Format(Catalog.GetString("Cannot find device. Please check if it is turned on and a connection with the computer is established. Please check also if another instance of {0} is already running."), ApoIdentInfo.NAME);

                return false;
            }
            else
            {
                ProbeType probe_type;
                try
                {
                    probe_type = this.Device.GetFinderProbeType();
                    if (probe_type != ProbeType.FinderStandard && probe_type != ProbeType.FinderSDStandard)
                    {
                        this.Exception = null;
                        this.ErrorTitle = Catalog.GetString("Unsupported probe type");
                        this.ErrorMessage = Catalog.GetString("Device cannot be initialized. This device type is not supported by this application.");
                        return false;
                    }
                }
                catch (Exception ex)
                {
                    this.Exception = ex;
                    this.ErrorTitle = Catalog.GetString("No probe type found");
                    this.ErrorMessage = String.Format(Catalog.GetString("Device cannot be initialized. Cannot read probe type: {0}"), ex.Message);
                    return false;
                }

                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) { }
                catch (NotSupportedException) { }

                catch (Exception ex)
                {
                    this.Exception = ex;
                    this.ErrorTitle = Catalog.GetString("No dimmer parameter found");
                    this.ErrorMessage = String.Format(Catalog.GetString("Device cannot be initialised. Cannot read dimmer parameter: {0}"), ex.Message);
                    return false;
                }

                try
                {
                    if (probe_type == ProbeType.FinderSDStandard)
                        this.OemSerialNumber = this.Device.ReadOemSerialNumber();
                }
                catch (NotImplementedException) { }
                catch (NotSupportedException) { }
                catch (Exception ex)
                {
                    this.Exception = ex;
                    this.ErrorTitle = Catalog.GetString("No OEM serial number found");
                    this.ErrorMessage = String.Format(Catalog.GetString("Device cannot be initialised. Cannot read serial number: {0}"), ex.Message);
                    return false;
                }

                this.AddDeviceEvents();
                System.Threading.Thread.Sleep(500);
                return true;
            }
        }

        public bool InitFinder()
        {
            try
            {
                this.InitStandardFinder();
                this.InitInsertFinder();
                this.DeviceTypeDetected?.Invoke(this, EventArgs.Empty);
                this.AddFinderEvents();

                this.Warmup = new DeviceWarmup(this.StandardFinder, this.Task);
                this.AddWarmUpEvents();

                this.InitFinderButton();
                return true;
            }
            catch (Exception ex)
            {
                this.Exception = ex;
                this.ErrorTitle = Catalog.GetString("Initialisation of Finder failed");
                this.ErrorMessage = Catalog.GetString("Cannot find device. Please check if the spectrometer is turned on and the connection with the computer is established.");

                return false;
            }
        }

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

            if (this.StandardFinder.ProbeType == ProbeType.FinderSDStandard)
            {
                this.StandardFinder.TransformationType = TransformationTypes.FinderSD_ApoIdent;
                this.IsFinderSD = true;
            }


            this.StandardFinder.PlausibilityChecks = ApoIdentEnvs.NoFinderPlausibilityCheckOff;

            if (this.ProductId != SGS.ProductId.APOIDENT_2X_ES20)
                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.SetLightSourceDamageThreshold();
            this.StandardFinder.ResetReferences();
            this.StandardFinder.AutoReferencing = true;
            this.StandardFinder.AutoSaveReferences = false;

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

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

            this.DeviceReadyState = Catalog.GetString("Initialising");
            this.Device.WaitForIdle(10000);

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

            this.Device.SetFinderLightSource(false, this.Task);
            this.Idle();
        }

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

            this.InsertFinder = new Finder(this.Device, ReferenceWheelVersion.Auto, ExternalWhiteReferenceType.Zenith, CombinedFinder.NO_CREATOR);
            this.InsertFinder.WhiteReferenceConversionMode = WhiteReferenceConversionMode.None;

            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;

            if (this.ProductId != SGS.ProductId.APOIDENT_2X_ES20)
                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.SetLightSourceDamageThreshold();
            this.InsertFinder.ResetReferences();
            this.InsertFinder.AutoReferencing = true;
            this.InsertFinder.AutoSaveReferences = false;

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

        public void Idle()
        {   
            try
            {
                this.Device.WaitForIdle(5000);
                this.Device.SetFinderPosition(FinderWheelPosition.White, null);
                this.Device.WaitForIdle(5000);
                if (this.Device.IsIdle)
                    this.DeviceReadyState = Catalog.GetString("Ready");
            }
            catch (Exception)
            {
                this.DeviceReadyState = Catalog.GetString("Error");
            }
        }

        public ushort GetAverage()
        {
            if (DeviceExists == false)
                return 500;

            if (this.analysis_type == AnalysisType.Quantification)
                return 2000;

            ushort average = 500;

            if (this.IsES20 == false)
            {
                if (this.IsInsertActive == 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 bool DeviceWarmup()
        {
            this.Warmup.Start();

            if (this.Warmup.WarmupSuccess)
            {
                return true;
            }
            else
            {
                this.ErrorTitle = Catalog.GetString("Warming up device failed");
                this.Exception = this.Warmup.Exception;
                this.ErrorMessage = String.Format(Catalog.GetString("The warm-up procedure failed: {0}"), this.Warmup.Exception.Message);
                return false;
            }
        }


        public void SwitchFinder()
        {
            if (this.IsInsertActive)
                this.Current = this.InsertFinder;
            else
                this.Current = this.StandardFinder;
        }

        public void Reset()
        {
            this.FinderButton.Reset();

            this.Device = null;
            this.Current = null;
            this.InsertFinder = null;
            this.StandardFinder = null;

            this.RemoteDevices?.RemoveRemoteDevices();
            this.RemoteDevices = null;

            this.DeviceReadyState = Catalog.GetString("Offline");
        }

        private void OnFinderStatusChanged(IDevice dev, FinderStatus status)
        {
            if (this.FinderExists == true)
                this.DeviceReadyState = this.Current.GetStatusString(status);
            else
                this.DeviceReadyState = Catalog.GetString("Offline");
        }

        private void InitFinderButton()
        {
            if (this.FinderExists)
                this.FinderButton.Init(this.Device);
        }

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

                Spectrometer.State state = Spectrometer.State.Unknown;

                lock (this.Device)
                {
                    state = this.Device.GetState();
                }



                return
                    (state != Spectrometer.State.Disconnected) &&
                    (state != Spectrometer.State.Unprogrammed) &&
                    (state != Spectrometer.State.Unknown);
            }
        }

        public string ProductName
        {
            get
            {
                if (this.DeviceExists == false)
                    return String.Empty;

                string product_name = String.Empty;

                switch (this.Device.Info.ProductId)
                {
                    case Hiperscan.SGS.ProductId.APOIDENT2_SGS:
                        product_name = "Apo-Ident 2";
                        break;

                    case Hiperscan.SGS.ProductId.APOIDENT_2X_ES20:
                        product_name = "Apo-Ident 2.1";
                        break;

                    default:
                        if (this.IsFinderSD)
                            product_name = "FinderSD";
                        else
                            product_name = "Apo-Ident";
                        break;
                }

                return product_name;
            }
        }

        public string ConnectionType
        {
            get
            {
                if (this.DeviceExists == false)
                    return String.Empty;

                if (this.Device.Info.IsRemote == true)
                    return Catalog.GetString("(W)LAN");

                return Catalog.GetString("USB");
            }
        }

        public string RemoteDeviceServerAddress
        {
            get
            {
                if (this.DeviceExists == false)
                    return String.Empty;

                if (this.Device.Info.IsRemote == false)
                    return String.Empty;

                return String.Format("{0}:{1}", this.Device.Info.RemoteSerialInterfaceClient.Server,
                                                        this.Device.Info.RemoteSerialInterfaceClient.Port);
            }
        }

        private string device_ready_state = String.Empty;
        public string DeviceReadyState
        {
            get
            {
                return this.device_ready_state;
            }

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

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


                this.device_ready_state = value;

                this.DeviceReadyStateChanged?.Invoke(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
            {
                if (this.IsInsertActive)
                    return this.InsertFinder;
                else
                    return this.StandardFinder;
            }
            private set
            {
                if (value == this.current)
                    return;

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

                this.current = value;

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


                if (this.current != null && this.IsDeviceReady == true)
                    this.DeviceReadyState = Catalog.GetString("Ready");
                else
                    this.DeviceReadyState = Catalog.GetString("Offline");

                if (this.current == null)
                    return;

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

                this.AverageSet?.Invoke(this, EventArgs.Empty);
            }
        }

        public bool IsES20
        {
            get
            {
                try
                {
                    return this.ProductId == SGS.ProductId.APOIDENT_2X_ES20;
                       
                }
                catch(Exception)
                {
                    return true;
                }
            }
        }

        public bool IsApoIdent_2_1
        {
            get
            {
                try
                {
                    return this.ProductId == SGS.ProductId.APOIDENT_2X_ES20;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }

        public uint ProductId
        {
            get
            {
                if (this.Device == null)
                    return 0;

                return this.Device.Info.ProductId;
            }
        }

        public string Creator
        {
            set
            {
                if (this.DeviceExists == false)
                    return;

                if (this.FinderExists)
                {
                    this.StandardFinder.Creator = value;
                    this.InsertFinder.Creator = value;
                }
            }
        }

        private AnalysisType analysis_type = AnalysisType.Identification;
        public AnalysisType AnalysisType
        {
            get => this.analysis_type;
            set => this.analysis_type = value;
        }

        private ApoIdentFileSystem FileSystem { get; set; }
        private DeviceSettings DeviceSettings { get; set; }

        public DeviceList DeviceList { get; private set; }
        public IDevice Device { get; private set; }
        private Finder StandardFinder { get; set; }
        private Finder InsertFinder { get; set; }
        public DeviceWarmup Warmup { get; private set; }
        public bool IsInsertActive { get; set; } = false;

        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 IsFinderSD { get; private set; } = false;

        public string OemSerialNumber { get; private set; }
        private RemoteDevices RemoteDevices { get; set; }

        public string ErrorTitle { get; private set; } = String.Empty;
        public string ErrorMessage { get; private set; } = String.Empty;
        public Exception Exception { get; private set; } = null;

        public FinderButton<FinderButtonState> FinderButton { get; private set; }
    }
}

