// 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.Common.SearchIpAddress;
using Hiperscan.Extensions.ApoIdentCommon;
using Hiperscan.SGS;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Benchtop.Transformations;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;

namespace Hiperscan.Extensions.ApoIdent
{

    public partial class CombinedFinder
    {
        public event EventHandler DeviceModeChanged = null;
        public event EventHandler AverageSet = null;
        public event EventHandler VersionDetected = 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 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;
        }

        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(Configurations config)
        {
            this.Config = config;
            this.Serial = String.Empty;
            this.DeviceName = String.Empty;
            this.ScanEngineVersion = String.Empty;
            this.IsFinderSD = false;;
        }

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

            if (this.Config.DeviceSettings.DeviceSearchMode == Configurations.DeviceSearchMode.Custom)
                parameter.UserdefinedAddress = new SearchIPAdr.FullAddress(this.Config.DeviceSettings.Url, (uint)this.Config.DeviceSettings.TcpPort);


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

            return parameter;
        }


        public ApoIdentApplicationState InitDevice(DeviceList dev_list, IdleTaskHandler task)
        {
            this.DeviceMode = Catalog.GetString("Not ready");
            this.QuitRequested = false;
            this.DimmerValue = 0;
            this.Task = task;
            this.Exception = null;

            try
            {
                this.RemoteDevices = new RemoteDevices(dev_list, false, true);
                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.Config.DeviceSettings.LastUrl = this.RemoteDevices.LastUsedRemoteAdress.Address;
                    this.Config.DeviceSettings.LastTcpPort = (int) this.RemoteDevices.LastUsedRemoteAdress.Port;
                    try 
                    {
                        this.Config.WriteToXmlFile(); 
                    } 
                    catch(Exception ex) 
                    {
                        Console.WriteLine("Cannot save last used ip address: {0}", ex);
                    }
                }

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

            if (this.Device == null)
            {
                this.ErrorMessage = 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_count = dev_list.Count;

                dev_list.Update();
                if (dev_list.Count > dev_count)
                    return ApoIdentApplicationState.ConnectionInitDeviceTryAgain;

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

                this.Serial = this.Device.Info.xApoIdentSerialNumber();
                this.DeviceName = this.Device.CurrentConfig.Name;
                this.ScanEngineVersion = this.Device.ScanEngineVersion.ToString();
                this.ProductId = this.Device.Info.ProductId;

                try { this.DimmerValue = this.Device.ReadDimmerParameter(); }
                catch (NotImplementedException) { }
                catch (NotSupportedException) { }
                catch (Exception ex)
                {
                    this.Exception = ex;
                    this.ErrorMessage = String.Format(Catalog.GetString("Device cannot be initialized. Cannot read dimmer parameter: {0}"), ex.Message);
                    return ApoIdentApplicationState.ConnectionInitDeviceFailed;
                }

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

                System.Threading.Thread.Sleep(500);
                return ApoIdentApplicationState.None;
            }
        }

        public ApoIdentApplicationState InitFinder(string creator)
        {
            try
            {
                this.InitStandardFinder(creator);
                this.InitInsertFinder(creator);
                this.VersionDetected?.Invoke(this, EventArgs.Empty);

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

                return ApoIdentApplicationState.None;
            }
            catch (Exception ex)
            {
                this.Reset();

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

                return ApoIdentApplicationState.ConnectionInitFinderFailed;
            }
        }

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

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


            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.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.DeviceMode = Catalog.GetString("Initializing");
            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();

            this.Device.StateChanged += this.OnDeviceStateChanged;
        }

        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.None;

            this.InsertFinder.SupportInfoMessage = ApoIdentApplication.ServiceForRepeatedError;

            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.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.SetFinderWheel(FinderWheelPosition.White, null);
                this.Device.WaitForIdle(5000);
                if (this.Device.IsIdle)
                    this.DeviceMode = Catalog.GetString("Ready");
            }
            catch (Exception)
            {
                this.DeviceMode = Catalog.GetString("Error");
            }
        }

        private ushort GetAveraging()
        {
            if (DeviceExists == false)
                return 500;

            ushort average = 500;

            if (this.ProductId != Hiperscan.SGS.DeviceList.PRODUCT_ID_APOIDENT_ES20)
            {
                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 ApoIdentApplicationState DeviceWarmup()
        {
            this.Warmup.Start();

            if (this.Warmup.WarmupSuccess)
                return ApoIdentApplicationState.None;
            else
                return ApoIdentApplicationState.CoonectDeviceWarmupFailed;
        }


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

        public void ResetRecalibrationTimestamp()
        {
            this.AbortInvokeCalibrationEvent();

            if (this.FinderExists == false)
                return;
 
            this.Current.ResetRecalibrationTimestamp();
        }

        public void Reset()
        {
            this.FinderButton?.StopPolling();
            this.FinderButton = null;

            this.Device = null;
            this.Current = null;
            this.InsertFinder = null;
            this.StandardFinder = null;
            this.DeviceMode = Catalog.GetString("Not ready");

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

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

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

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

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

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

            }
        }

        public bool ResetReferences()
        {
            try
            {
                if (this.FinderExists == false)
                    return false;

                this.ResetRecalibrationTimestamp();

                if (this.IsInsertActive)
                    this.InsertFinder.ResetReferences();
                else
                    this.StandardFinder.ResetReferences();

                return true;
            }
            catch(Exception)
            {
                return false;
            }
        }

        public void InitFinderButton()
        {
            if (this.FinderExists)
                this.FinderButton = new FinderButtonPolling(this.Device,                                  this.StandardFinder.HardwareFeatures.HasFlag(HardwareFeatures.StartButton));
        }

        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.DeviceList.PRODUCT_ID_APOIDENT:
                        product_name = "Apo-Ident 2";
                        break;

                    case Hiperscan.SGS.DeviceList.PRODUCT_ID_APOIDENT_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_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;

                this.DeviceModeChanged?.Invoke(value, EventArgs.Empty);
            }
        }

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

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

        private Finder x_current = null;
        public Finder Current
        {
            get
            {
                if (this.IsInsertActive)
                    return this.InsertFinder;
                else
                    return this.StandardFinder;
            }
            private set
            {
                if (value == this.x_current)
                    return;

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

                this.x_current = value;

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


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

                if (this.x_current == null)
                    return;

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

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

        public bool ExternalReferencingIsPending
        {
            get
            {
                try
                {
                    if (this.IsInsertActive)
                        return this.InsertFinder.ReferencingIsPending;
                    else
                        return this.StandardFinder.ReferencingIsPending;
                }
                catch (Exception)
                {
                    return true;
                }
            }
        }

        public bool IsApoIdent1
        {
            get
            {
                try
                {
                    if (this.Device.Info.ProductId == DeviceList.PRODUCT_ID_SGS1900)
                        return this.IsFinderSD == false;

                    return false;
                }
                catch(Exception)
                {
                    return true;
                }
            }
        }

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

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

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

        public FinderButtonPolling FinderButton { get; private set; } = null;
        public uint ProductId { get; private  set; }

    }
}

