// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2022, 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.Threading.Tasks;
using Hiperscan.AnalysisModule;
using Hiperscan.ApoIdentCore.ActionControl;
using Hiperscan.ApoIdentCore.ComplexFinder;
using Hiperscan.ApoIdentCore.Enums;
using Hiperscan.ApoIdentCore.GuiControl;
using Hiperscan.ApoIdentCore.Misc;
using Hiperscan.ApoIdentCore.SectionManager.Measuring;
using Hiperscan.ApoIdentCore.SectionManager.Measuring.Simulator;
using Hiperscan.ApoIdentCommon;
using Hiperscan.GuiExtensions;
using Hiperscan.GuiExtensions.Dialog;
using Hiperscan.SGS;
using Hiperscan.Spectroscopy.Math;
using Hiperscan.Spectroscopy.Method;
using Hiperscan.Unix;

namespace Hiperscan.ApoIdentCore
{
    public partial class AppControl
    {
        private void InitMeasuringManager(DeviceList device_list)
        {
            if (this.MeasuringManager == null)
            {
                this.MeasuringManager = new MeasuringManager(device_list, this.DeviceSettings, this.FileSystem);
                this.MeasuringManager.DeviceAdded += this.OnDeviceAdded;
                this.MeasuringManager.DeviceRemoved += this.OnDeviceRemoved;
                this.MeasuringManager.Finder.DeviceReadyStateChanged += this.OnDeviceReadyStateChanged;
                this.MeasuringManager.Finder.AverageSet += this.OnAverageSet;
                this.MeasuringManager.Finder.DeviceTypeDetected += this.OnDeviceTypeDetected;
                this.MeasuringManager.Finder.WarmupStateChanged += this.OnWarmupStateChanged;
                this.MeasuringManager.Finder.WarmupQuitRequested += this.OnWarmupQuitRequested;
                this.MeasuringManager.MeasuringProgressed += this.OnMeasuringProgressed;
                this.MeasuringManager.FinderChanged += this.OnFinderChanged;
                this.MeasuringManager.RecalibrationTimerUpdated += this.OnRecalibrationTimeUpdated;
                this.MeasuringManager.RecalibrationCycleFinished += this.OnRecalibrationCycleFinished;
                this.MeasuringManager.Finder.FinderButton.FinderButtonState.MeasuringConditionStateChanged += this.OnMeasuringStateChanged;
                this.MeasuringManager.BeforeMeasuringStarted += this.OnBeforeMeasuringStarted;
            }
        }

        public void ChangeFinder(bool is_insert)
        {
            this.MeasuringManager.ChangeFinder(is_insert);
        }

        public void StartMeasuringCycleByGuiButton()
        {
            this.MeasuringManager.Finder.FinderButton.StartMeasuringFromExtern();
        }

        public void AbortReferencing()
        {
            Task.Run(() =>
            {
                this.GlobalStates.Busy = true;

                UpdateArgs arg = UpdateArgs.GetResetArg();
                arg.CursorState = CursorState.Busy;
                this.UpdateGuiNeeded?.Invoke(arg);

                this.MeasuringManager.ReferenceControl.AbortReferencing();
            });
        }

        private void OnFinderChanged(object sender, EventArgs e)
        {
            UpdateArgs arg = new UpdateArgs();
            arg.Measuring = UpdateCategory.Reset;

            if (this.ResultManager.AnalysisResultExists == false)
            {
                ProgramState action_type = ProgramState.MeasuringCannabis;
                if (this.SubstanceManager.SubstancesCatalog.AnalysisType == AnalysisModule.AnalysisType.Identification)
                {
                    action_type = ProgramState.MeasuringSolid;
                    if (this.MeasuringManager.Finder.IsInsertActive)
                        action_type = ProgramState.MeasuringInsert;
                }

                arg.AnimationType = action_type;
            }

            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void OnDeviceReadyStateChanged(object sender, EventArgs e)
        {
            UpdateArgs arg = new UpdateArgs();
            arg.StateBarValues = new StateBarValues();
            arg.StateBarValues.State = sender as string;
            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void OnAverageSet(object sender, EventArgs e)
        {
            ApoActionArg arg = new ApoActionArg();
            arg.Action = ApoActionType.UpdateProperties;
            this.ActionNeeded?.Invoke(arg);
        }

        private void OnDeviceTypeDetected(object sender, EventArgs e)
        {
            CombinedFinder finder = sender as CombinedFinder;

            string value = String.Format("{0} | {1} | {2}",
                                         finder.ProductName,
                                         finder.Serial,
                                         finder.ConnectionType);

            UpdateArgs arg = new UpdateArgs();
            arg.StateBarValues = new StateBarValues();
            arg.StateBarValues.DeviceInfos = value;
            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void OnWarmupStateChanged(object sender, EventArgs e)
        {
            DeviceWarmup warmup = sender as DeviceWarmup;
            UpdateArgs arg = new UpdateArgs();

            switch (warmup.State)
            {
                case WarmupState.Begin:

                    arg.SplashScreenState = SplashScreenState.StartWarmup;
                    break;

                case WarmupState.Warming:
                    arg.SplashScreenState = SplashScreenState.WarmupProgress;
                    arg.StateBarValues = new StateBarValues();
                    arg.StateBarValues.Progress = warmup.Fraction;
                    arg.StateBarValues.WarmupCurrentTemperature = warmup.CurrentTemperature;
                    arg.StateBarValues.WarmupMinTemperature = warmup.MinTemperature;
                    break;

                default:
                    break;
            }

            this.UpdateGuiNeeded?.Invoke(arg);
        }

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

        public void OnRecalibrationTimeUpdated(object sender, EventArgs e)
        {
            UpdateArgs arg = new UpdateArgs();
            arg.Measuring = UpdateCategory.SpecialCases;
            arg.RecalibrationTimeLeft = (sender as RecalibrationTimer).TimeLeft;
            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void OnRecalibrationCycleFinished(object sender, EventArgs e)
        {
            UpdateArgs arg = new UpdateArgs();
            arg.Measuring = UpdateCategory.Reset;
            if (this.ResultManager.AnalysisResultExists == false)
                arg.AnimationType = this.MeasuringManager.GetMeasuringState();
            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void OnMeasuringStateChanged(object sender, EventArgs e)
        {
            UpdateArgs arg = new UpdateArgs();
            arg.Measuring = UpdateCategory.Sensitives;
            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void OnMeasuringProgressed(MeasuringArg measuring_arg)
        {
            UpdateArgs arg = UpdateArgs.GetResetArg();
            arg.AnimationType = measuring_arg.MeasuringAction;
            arg.CursorState = measuring_arg.IsExecuting ? CursorState.Busy : CursorState.Normal;
            arg.IsMeasuring = measuring_arg.IsExecuting;


            bool measure = measuring_arg.MeasuringAction == ProgramState.MeasuringSolid ||
                                      measuring_arg.MeasuringAction == ProgramState.MeasuringCannabis ||
                                      measuring_arg.MeasuringAction == ProgramState.MeasuringFluid ||
                                      measuring_arg.MeasuringAction == ProgramState.MeasuringTransflectance ||
                                      measuring_arg.MeasuringAction == ProgramState.MeasuringTransflectanceAu ||
                                      measuring_arg.MeasuringAction == ProgramState.MeasuringInsert;

            if (measure)
            {
                arg.IsMeasuring = true;
                this.ExecuteMeasurement(arg);
            }
            else
            {
                this.GlobalStates.Busy = measuring_arg.IsExecuting;
                this.UpdateGuiNeeded?.Invoke(arg);
            }
        }


        private void ExecuteMeasurement(UpdateArgs arg)
        {
            try
            {
                this.ProgramSync.EnterCriticialZone();

                if (this.ResultManager.WasLastIdentificationPositive == true)
                {
                    if (this.ProtocolManager.CheckProtocolSaveState(this) == false)
                        return;
                }

                this.MeasuringManager.DiscardMeasurement();
                this.ResultManager.DiscardResult();
                this.ProtocolManager.DiscardReport();

                this.GlobalStates.Busy = true;

                bool abort_repeating = false;
                int repeats = 0;

                bool is_insert = arg.AnimationType == ProgramState.MeasuringInsert;
                bool is_fluid = arg.AnimationType == ProgramState.MeasuringFluid;
                bool is_transflectance = arg.AnimationType == ProgramState.MeasuringTransflectance ||
                                                    arg.AnimationType == ProgramState.MeasuringTransflectanceAu;
                    
                this.UpdateGuiNeeded?.Invoke(arg);

                arg = UpdateArgs.GetResetArg();
                arg.DialogArgs.Message = Catalog.GetString("Error while acquiring spectrum: {0}");
                arg.CursorState = CursorState.Normal;
                arg.DialogArgs.Title = Catalog.GetString("Error while acquiring spectrum");

                while (abort_repeating == false && this.MeasuringManager.ReferenceControl.IsAborted == false)
                {
                    abort_repeating = true;

                    try
                    {
                        this.MeasuringManager.Finder.SwitchFinder();

                        if (is_transflectance == false)
                        {
                            this.MeasuringManager.AcquireSpectrum(SpectrumSimulator.SimulationModeOn, is_fluid);
                            this.ResultManager.Analyse(this.MeasuringManager.ProcessedSpectrum);
                        }
                        else
                        {
                            this.MeasuringManager.AcquireTransflectance();
                        }
                    }
                    catch (SGS.Benchtop.Finder.InvalidTransflectReferenceException ex)
                    {
                        arg.AnimationType = ProgramState.TransflectanceFailed;
                        arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithHelp;
                        arg.DialogArgs.Exception = ex;
                        arg.DialogArgs.EventHandler = this.HandleTransflectanceError;
                        arg.DialogArgs.Title = Catalog.GetString("Error while acquiring transflectance");
                        arg.DialogArgs.Message = Catalog.GetString(@"Always start by applying a clean sample cup and transflectance insert (feet down). Information on how to perform the measurement can be found in the Operating Manual under Help.

Click Reset and repeat the transflectance reference measurement.");


                    }
                    catch (SGS.Benchtop.Finder.BlackReferenceException ex)
                    {
                        arg.AnimationType = ProgramState.BlackFailed;
                        arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithHelp;
                        arg.DialogArgs.Exception = ex;
                        arg.DialogArgs.EventHandler = this.HandleBlackReferenceError;
                        arg.DialogArgs.Title = Catalog.GetString("Error acquiring black reference");
                        arg.DialogArgs.Message = String.Format(arg.DialogArgs.Message, ex.Message);
                    }
                    catch (SGS.Benchtop.Finder.WhiteReferenceException ex)
                    {
                        arg.AnimationType = ProgramState.WhiteFailed;
                        arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithHelp;
                        arg.DialogArgs.Exception = ex;
                        arg.DialogArgs.EventHandler = this.HandleWhiteReferenceError;
                        arg.DialogArgs.Title = Catalog.GetString("Error acquiring white reference");
                        arg.DialogArgs.Message = String.Format(arg.DialogArgs.Message, ex.Message);
                    }
                    catch (LevenbergMarquardt.MaxIterationNumberException ex) //InternalCalibrationException
                    {
                        if (repeats == 0)
                        {
                            abort_repeating = false;
                        }
                        else
                        {
                            arg.AnimationType = ProgramState.MeasuringFailed;
                            arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithServiceMessage;
                            arg.DialogArgs.Exception = ex;
                            arg.DialogArgs.Message = String.Format(arg.DialogArgs.Message, ex.Message);
                        }

                        repeats++;

                    }
                    catch (InvalidDistanceException ex)
                    {
                        arg.AnimationType = ProgramState.MeasuringFailed;
                        arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithServiceMessage;
                        arg.DialogArgs.Exception = ex;
                        arg.DialogArgs.Message = String.Format(arg.DialogArgs.Message, 
                                                          Catalog.GetString("Sample spectrum is invalid."));
                    }
                    catch (System.Runtime.Remoting.RemotingException ex)
                    {
                        this.Disconnect();

                        arg = new UpdateArgs();
                        arg.AnimationType = ProgramState.MeasuringFailed;
                        arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithServiceMessage;
                        arg.DialogArgs.Exception = ex;
                        arg.DialogArgs.Message = String.Format(Catalog.GetString("No connection to analysis module: {0}"), ex.Message);
                    }
                    catch (Exception ex)
                    {
                        if (is_fluid)
                            this.MeasuringManager.Finder.ResetRecalibrationTimestamp();

                        if (is_transflectance == false)
                            this.MeasuringManager.SaveErrorSampleSpectrum();
                            
                        arg.AnimationType = ProgramState.MeasuringFailed;
                        arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithServiceMessage;
                        arg.DialogArgs.Exception = ex;

                        if (ex is NullReferenceException)
                            arg.DialogArgs.Message = String.Format(arg.DialogArgs.Message, "An invalid spectrum was returned.");
                        else
                            arg.DialogArgs.Message = String.Format(arg.DialogArgs.Message, ex.Message);
                    }
                    finally
                    {
                        this.MeasuringManager.Finder.Idle();
                    }
                }


                if (this.MeasuringManager.ReferenceControl.IsAborted || 
                    this.MeasuringManager.ContinueAction == false)
                {
                    this.HandleMeasuringAbort(arg);
                }
                else if(arg.DialogArgs.StandardDialogType == StandardDialogType.None)
                {
                    this.HandleMeasuringSuccess(is_transflectance);
                }
                else
                {
                    this.HandleMeasuringError(arg);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unexpected error while aborting measuring: {0}", ex);
            }
            finally
            {
                this.ProgramSync.LeaveCriticalZone();
            }
        }

        public void HandleMeasuringSuccess(bool is_transflectance)
        {
            this.GlobalStates.Busy = false;
            UpdateArgs arg = UpdateArgs.GetResetArg();
            arg.CursorState = CursorState.Normal;

            if (is_transflectance)
            {
                this.MeasuringManager.ActivateRecalibrationTimer();
                arg.AnimationType = ProgramState.MeasuringFluid;

            }
            else
            {
                if (this.ProtocolManager.IsComplete)
                    arg.AnimationType = ProgramState.ReadyForProtocolCreation;
                else
                    arg.AnimationType = ProgramState.ReadyForProtocolInput;

                if (this.ResultManager.AnalysisType != AnalysisType.Quantification)
                    this.ResultManager.CheckIdentificationResult();
                else
                    this.ResultManager.CheckQuantificationResult();
            }

            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void HandleMeasuringAbort(UpdateArgs arg)
        {
            this.MeasuringManager.DeactivateRecalibrationTimer();
            this.MeasuringManager.DiscardMeasurement();
            this.ResultManager.DiscardResult();
            this.ResultManager.WasLastIdentificationPositive = true;

            this.MeasuringManager.Finder.ResetRecalibrationTimestamp();

            string message = Catalog.GetString("Recalibration procedure was not finished. Please repeat the measurement.");


            this.GlobalStates.Busy = false;

            if (this.MeasuringManager.ContinueAction)
            {
                arg.DialogArgs.StandardDialogType = StandardDialogType.ErrorWithServiceMessage;
                arg.DialogArgs.Title = Catalog.GetString("Referencing aborted");
            }
            else
            {
                arg.DialogArgs.StandardDialogType = StandardDialogType.None;
            }

            this.ResultManager.ResetAdditionalTests();

            arg.AnimationType = this.MeasuringManager.GetMeasuringState();
            arg.CursorState = CursorState.Normal;
            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void HandleMeasuringError(UpdateArgs arg)
        {
            this.MeasuringManager.DeactivateRecalibrationTimer();
            this.MeasuringManager.DiscardMeasurement();
            this.ResultManager.DiscardResult();
            this.ResultManager.WasLastIdentificationPositive = true;

            this.GlobalStates.Busy = false;

            this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void HandleTransflectanceError(object sender, EventArgs e)
        {
            this.DocumentManager.ShowPDF("error_transflectance_reference.pdf");
        }

        private void HandleBlackReferenceError(object sender, EventArgs e)
        {
            this.DocumentManager.ShowPDF("error_black_reference.pdf");
        }

        private void HandleWhiteReferenceError(object sender, EventArgs e)
        {
            this.DocumentManager.ShowPDF("error_white_reference.pdf");
        }

        private void OnBeforeMeasuringStarted(object sender, EventArgs a)
        {
            if (this.ResultManager.AnalysisType != AnalysisType.Quantification)
                return;

            this.ResultManager.ResetAdditionalTests();

            UpdateArgs args = new UpdateArgs();
            args.DialogArgs.ApoDialogType = ApoDialogType.AdditionalTest;
            this.UpdateGuiNeeded?.Invoke(args);

            this.ResultManager.AdditopalTestField.Entry = this.ResultManager.CurrentAdditionalTest.ProtocolText;
            this.ResultManager.CustomerEvaluationField.Entry = this.ResultManager.CurrentAdditionalTest.ProtocolCustomerEvaluation;
            this.ProfileManager.CurrentProfile.SaveCacheData();

            this.MeasuringManager.ContinueAction = this.ResultManager.CurrentAdditionalTest.QuantificationConfirmed;
        }

        public MeasuringManager MeasuringManager { get; set; }
    }
}
