
// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2023. 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.Unix;
using Hiperscan.Common;
using Hiperscan.Common.SearchIpAddress;
using Hiperscan.SGS;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Benchtop.FinderButtonPolling;
using Hiperscan.Spectroscopy;
using Hiperscan.GuiExtensions;
using Hiperscan.GuiExtensions.Dialog;
using Hiperscan.ApoIdentCommon;
using Hiperscan.ApoIdentCommon.ApoConfig;
using Hiperscan.ApoIdentCommon.ApoConfig.IO;
using Hiperscan.Extensions.ReferenceCheck.Control.Enums;
using Hiperscan.Extensions.ReferenceCheck.Control.GuiUpdate;

namespace Hiperscan.Extensions.ReferenceCheck.Control
{
    public partial class CoreApp
    {
        private void GoToInitDeviceSection()
        {
            this.CurrentProgrammState = ProgramState.InitDevice;
            this.CurrentVisibleView = ViewTypes.InitDevice;
            this.InitializeConnection();
        }

        private void InitializeConnection()
        {
            System.Threading.Tasks.Task.Run(() => this.DoInitializeConnection());
        }

        private void DoInitializeConnection()
        {
            this.CurrentProgrammState = ProgramState.InitDevice;
            this.ResetDevice();
            this.DeviceState.Reset(this.DeviceSettings == null);
            this.CallDeviceUpdateGui(1000, CursorState.Busy);

            try
            {
                this.InitConfig();
                this.InitConnectionSection();
                this.InitSerialSection();
                this.InitProductSection();
                this.InitTemperatureSection();

                this.FinderButton.Init(this.Device);
                this.FinderButton.MeasurementStarted += this.OnMeasuringStarted;

                this.CallDeviceUpdateGui(0);
            }
            catch(Exception ex)
            {
                this.ResetDevice();
                UpdateArgs arg = new UpdateArgs();
                arg.CursorState = CursorState.Normal;
                arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithQuesteion;
                arg.DialogArgs.Exception = ex;
                arg.DialogArgs.Message = String.Format(Catalog.GetString(@"Cannot initialise device.
{0}

Do you want to retry?"), ex.Message);
                this.UpdateGuiNeeded?.Invoke(arg);

                if (arg.DialogArgs.Response == DialogRespnseType.Yes)
                    this.InitializeConnection();
                else
                    this.Exit();
            }
        }

        private void InitConfig()
        {
            if (this.DeviceSettings != null)
                return;

            this.DeviceState.ConfigIsActive = true;
            this.DeviceState.ConfigOutput = this.DeviceState.ReadingOutput;

            this.CallDeviceUpdateGui(1000);

            try
            {
                this.FileSystem = new ApoIdentFileSystem(this.Host.WindowsProfileChanged,
                                                         this.Host.ConfigurationDirPath);

                Configurations apo_config = new Configurations(this.FileSystem.ConfigFile);
                this.DeviceSettings = apo_config.DeviceSettings;

                if (this.DeviceSettings.HasRemoteInfos)
                    this.DeviceState.ConfigOutput = Catalog.GetString("Available");
                else
                    this.DeviceState.ConfigOutput = Catalog.GetString("Not available");
            }
            catch(Exception)
            {
                this.DeviceState.ConfigOutput = Catalog.GetString("Not available");
            }

            this.CallDeviceUpdateGui(0);
        }

        private void InitConnectionSection()
        {
            this.DeviceState.ConnectionIsActive = true;
            this.DeviceState.ConnectionOutput = this.DeviceState.ReadingOutput;

            this.CallDeviceUpdateGui(1000);

            this.InitDevice();
            this.InitFinder();
            this.SetResultHeader();

            this.DeviceState.ConnectionOutput = this.ConnectionType;
            this.CallDeviceUpdateGui(0);
        }

        private void InitSerialSection()
        {
            this.DeviceState.SerialIsActive = true;
            this.DeviceState.SerialOutput = this.DeviceState.ReadingOutput;

            this.CallDeviceUpdateGui(3000);

            this.DeviceState.SerialOutput = this.Device.Info.SerialNumber.Substring(0, 8);
            this.CallDeviceUpdateGui(0);
        }

        private void InitProductSection()
        {
            this.DeviceState.ProductIsActive = true;
            this.DeviceState.ProductOutput = this.DeviceState.ReadingOutput;

            this.CallDeviceUpdateGui(3000);

            this.DeviceState.ProductOutput = this.ProductName;
            this.CallDeviceUpdateGui(0);
        }

        private void InitTemperatureSection()
        {
            this.DeviceState.TemperatureIsActive = true;
            this.DeviceState.TemperatureOutput = this.DeviceState.ReadingOutput;
            this.CallDeviceUpdateGui(1000);

            this.DeviceWarmup();

            this.CallDeviceUpdateGui(0, CursorState.Normal);
        }

        private void CallDeviceUpdateGui(int time_delay_ms, CursorState cursor_state = CursorState.None)
        {
            UpdateArgs arg = new UpdateArgs();
            arg.ViewType = ViewTypes.InitDevice;
            arg.CursorState = cursor_state;
            arg.UpdateCatecory = UpdateCategory.Sensitives | UpdateCategory.QuickDataChange;
            this.UpdateGuiNeeded?.Invoke(arg);

            System.Threading.Thread.Sleep(time_delay_ms);
        }

        private void ResetDevice()
        {
            this.FinderButton?.Reset();
            this.Device = null;
            this.Finder = null;
            this.Warmup = null;

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

        private void InitDevice()
        {
            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.Device == null)
            {               
                throw new Exception(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 this software is already running."));
            }

            if (this.Device.IsConnected == false)
                this.Device.Open();
                
            ProbeType probe_type = this.Device.GetFinderProbeType();
            if (probe_type != ProbeType.FinderStandard && probe_type != ProbeType.FinderSDStandard)
            {
                throw new Exception(Catalog.GetString("Device cannot be initialized. This device type is not supported by this application."));
            }
        }

        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;
        }

        private void InitFinder()
        {
            string crator = string.Empty;

            this.Finder = new Finder(this.Device, ReferenceWheelVersion.Auto,
                                     ExternalWhiteReferenceType.Zenith,
                                     CoreApp.Creator)
            {
                WhiteReferenceConversionMode = WhiteReferenceConversionMode.None,
            };

            if (this.Finder.ProbeType == ProbeType.FinderSDStandard)
            {
                this.Finder.TransformationType = SGS.Benchtop.Transformations.TransformationTypes.FinderSD_ApoIdent;
            }


            this.Finder.PlausibilityChecks = false;
            this.Finder.ReferenceWheelVersion = Finder.GetApoIdentReferenceWheelVersion(this.Finder.ReferenceWheelVersion);
            this.Finder.CorrectionMask = CorrectionMask.All ^ CorrectionMask.Adc;
            this.Finder.SetLightSourceDamageThreshold();
            this.Finder.ResetReferences();
            this.Finder.AutoReferencing = false;            
            this.Finder.AutoSaveReferences = false;

            this.Finder.CorrectionMask = CorrectionMask.All ^ CorrectionMask.Adc;

            this.Finder.RecalbrationAveraging = Finder.DEFAULT_RECALBRATION_AVERAGING;
            this.Finder.ReferencingAveraging = Finder.DEFAULT_REFERENCING_AVERAGING;
            this.Finder.StandardSampleAveraging = Finder.GetApoIdentSampleAverage(this.Device, false);
            this.Finder.InsertSampleAveraging = Finder.GetApoIdentSampleAverage(this.Device, true);

            this.Finder.Device.WaitForIdle(10000);

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

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

        private void DeviceWarmup()
        {
            this.Warmup = new DeviceWarmup(this.Finder);
            this.Warmup.WarmupStateChanged += this.OnWarmupStateChanged;
            this.Warmup.QuitRequested += this.OnWarmpupCancelled;
            this.Warmup.Start();

            if (this.IsDeviceWarmupCancelled == false && this.Warmup.WarmupSuccess == false)
            {
                throw new Exception(String.Format(Catalog.GetString("The warm-up procedure failed: {0}"), this.Warmup.Exception.Message),
                                    this.Warmup.Exception);
            }
        }

        private void InterruptWarming()
        {
            if (this.QuitRequested)
                return;

            UpdateArgs arg = new UpdateArgs();
            arg.DialogArgs.StandardDialogType = StandardDialogType.Question;
            arg.DialogArgs.TimeOutMilliSeconds = 5000;
            arg.DialogArgs.Message = Catalog.GetString("Would you like to abort Warming Up?");
            arg.CursorState = CursorState.Normal;
            this.UpdateGuiNeeded?.Invoke(arg);


            if (arg.DialogArgs.Response != DialogRespnseType.Yes)
            {
                arg = new UpdateArgs();
                arg.CursorState = CursorState.Busy;
                this.UpdateGuiNeeded?.Invoke(arg);
                return;
            }


            this.IsDeviceWarmupCancelled = true;
            this.Warmup.State = WarmupState.Finsihed;
            this.DeviceState.TemperatureOutput = Catalog.GetString("Cancelling ...");

            arg = new UpdateArgs();
            arg.CursorState = CursorState.Busy;
            arg.ViewType = ViewTypes.InitDevice;
            arg.UpdateCatecory = UpdateCategory.QuickDataChange;
            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void OnWarmpupCancelled(object sender, EventArgs e)
        {
            if (this.IsDeviceWarmupCancelled)
                this.Exit();
        }

        private void OnWarmupStateChanged(object sender, EventArgs e)
        {
            if (this.IsDeviceWarmupCancelled)
                return;

            UpdateArgs arg = new UpdateArgs();
            arg.ViewType = ViewTypes.InitDevice;
            arg.UpdateCatecory = UpdateCategory.All;

            switch (this.Warmup.State)
            {
                case WarmupState.NoNeed:
                    this.CurrentProgrammState = ProgramState.Waiting;
                    this.DeviceState.WarmingupIsActive = false;
                    break;

                case WarmupState.Begin:
                    this.CurrentProgrammState = ProgramState.Warmingup;
                    this.DeviceState.TemperatureOutput = this.GetTemperatureOutput();
                    this.DeviceState.WarmingupIsActive = true;
                    this.DeviceState.WarmingOutput = this.Warmup.Fraction;
                    break;

                case WarmupState.Warming:
                    this.DeviceState.TemperatureOutput = this.GetTemperatureOutput();
                    this.DeviceState.WarmingupIsActive = true;
                    this.DeviceState.WarmingOutput = this.Warmup.Fraction;

                    break;

                case WarmupState.Failed:
                    this.DeviceState.WarmingupIsActive = false;
                    if (this.QuitRequested == false && this.IsDeviceWarmupCancelled == false)
                        throw new Exception(this.Warmup.Exception.Message, this.Warmup.Exception);
                        break;

                case WarmupState.Finsihed:
                    this.DeviceState.WarmingupIsActive = false;
                    if (this.QuitRequested == false && this.IsDeviceWarmupCancelled == false)
                    {
                        this.CurrentProgrammState = ProgramState.Waiting;
                    }
                    break;

                default:
                    break;
            }  

            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private string GetTemperatureOutput()
        {
            return String.Format(Catalog.GetString("{0:f1}°C (> {1:f1}°C)"),
                        this.Warmup.CurrentTemperature,
                        this.Warmup.MinTemperature);
        }

        private void Idle()
        {
            try
            {
                this.Device.WaitForIdle(5000);
                this.Device.SetFinderPosition(FinderWheelPosition.White, null);
                this.Device.WaitForIdle(5000);
            }
            catch (Exception) {  }
        }

        private void SetResultHeader()
        {
            this.SessionManager.Result.CheckDate = DateTime.Now;
            this.SessionManager.Result.DisplayName = System.Reflection.Assembly.GetCallingAssembly().FullName;
            this.SessionManager.Result.Creator = ReferenceCheck.Control.CoreApp.Creator;
            this.SessionManager.Result.DeviceSerialNumber = this.Finder.Device.CurrentConfig.Name;
            this.SessionManager.Result.SGSVersion = StringEnum.GetStringValue(this.Finder.Device.SpectrometerVersion);
            this.SessionManager.Result.SGSSerialNumber = this.Finder.Serial;
            this.SessionManager.Result.SetWheelVersion(this.Finder.ReferenceWheelVersion);
        }

        public string ConnectionType
        {
            get
            {
                if (this.Device.Info.IsRemote == true)
                    return Catalog.GetString("(W)LAN");

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

        public string ProductName
        {
            get
            {
                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;
                }

                this.SessionManager.Result.DeviceName = product_name;
                return product_name;
            }
        }

        public bool IsFinderSD
        {
            get
            {
                if (this.Finder == null)
                    return false;

                switch (this.Finder.ProbeType)
                {
                    case ProbeType.FinderSDInsert:
                    case ProbeType.FinderSDStandard:
                    case ProbeType.FinderSDSampleSpinner:
                        return true;

                    default:
                        return false;
                }
            }
        }

        private DeviceSettings DeviceSettings { get; set; }
        public DeviceState DeviceState { get; private set; } = new DeviceState();
        public ProfileManager Profiles { get; set; }

        private RemoteDevices RemoteDevices { get; set; }
        private ApoIdentFileSystem FileSystem { get; set; }
        private IDevice Device { get; set; } 
        private Finder Finder { get; set; }
        public DeviceWarmup Warmup { get; private set; }
        private bool IsDeviceWarmupCancelled { get; set; }

        private FinderButton<FinderButtonState> FinderButton { get; set; } = new FinderButton<FinderButtonState>();
    }
}
