// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2021 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 System.IO;
using System.Reflection;
using Hiperscan.Extensions.ApoIdent.Widgets;
using Hiperscan.SGS.Benchtop;
using Hiperscan.Spectroscopy;
using Hiperscan.Spectroscopy.IO;
using Hiperscan.Spectroscopy.Math;
using Hiperscan.Spectroscopy.Method;
using Hiperscan.Unix;

namespace Hiperscan.Extensions.ApoIdent
{
    public partial class ApoIdentApplication
    {
        private void OnFinderButtonActivated(object sender, EventArgs e)
        {
            System.Threading.Tasks.Task.Run(() => this.Measure());
        }

        private void OnMeasuringButtonActivated(object sender, EventArgs e)
        {
            System.Threading.Tasks.Task.Run(() => this.Measure());
        }

        private void OnMeasuringButtonClicked(object sender, EventArgs e)
        {
            System.Threading.Tasks.Task.Run(() => this.Measure());
        }

        private void Measure()
        {
            if (this.Finder.DeviceExists == false)
                return;

            if (this.Control.CreateNotMeasurableReport)
                return;

            switch (this.Finder.MeasuringState)
            {
                case MeasuringState.Transflectance:
                case MeasuringState.TransflectanceAu:
                    this.MeasureTransflectance();
                    break;

                case MeasuringState.BlackReference:
                    this.Control.Busy = true;
                    this.Finder.Wait = false;
                    this.Update(ApoIdentApplicationState.MeasuringRunCycle);
                    break;

                case MeasuringState.WhiteReference:
                    this.Control.Busy = true;
                    this.Finder.Wait = false;
                    this.Update(ApoIdentApplicationState.MeasuringRunCycle);
                    break;

                case MeasuringState.WhiteInsetReference:
                    this.Control.Busy = true;
                    this.Finder.Wait = false;
                    this.Update(ApoIdentApplicationState.MeasuringRunCycle);
                    break;

                case MeasuringState.MeasuringFluid:
                    this.MeasureFluid();
                    break;

                case MeasuringState.MeasuringSolid:
                    this.MeasureSolid();
                    break;

                case MeasuringState.MeasuringInsert:
                    this.MeasureInset();
                    break;

                default:
                    break;
            }
        }

        private void MeasureTransflectance()
        {
            this.Control.MeasuringProgressState.ResetFlags();
            this.Control.MeasuringProgressState.SetFlag(MeasuringProgressStates.IsAcquiringTransflection);

            this.ExecuteMeasurement(true, false, false);
        }

        private void MeasureSolid()
        {
            this.Control.MeasuringProgressState.ResetFlags();
            this.Control.MeasuringProgressState.SetFlag(MeasuringProgressStates.IsMeasuring);

            this.ExecuteMeasurement(false, false, false);
        }

        private void MeasureInset()
        {
            this.Control.MeasuringProgressState.ResetFlags();
            this.Control.MeasuringProgressState.SetFlag(MeasuringProgressStates.IsMeasuring);

            this.ExecuteMeasurement(false, false, true);
        }

        private void MeasureFluid()
        {
            this.Control.MeasuringProgressState.ResetFlags();
            this.Control.MeasuringProgressState.SetFlag(MeasuringProgressStates.IsMeasuring);

            this.ExecuteMeasurement(false, true, false);
        }

        private void OnWaitForExternalBlackReference()
        {
            this.Control.MeasuringProgressState.ResetFlags();
            this.Control.MeasuringProgressState.SetFlag(MeasuringProgressStates.IsExternalReferencing);

            this.Finder.MeasuringState = MeasuringState.BlackReference;
            this.Finder.FinderButton.CycleIsRunning = false;

            this.Control.Busy = false;
            this.Update(ApoIdentApplicationState.MeasuringWaitForNextCycle);
            this.WaitForContinueMeasuring();
        }

        private void OnWaitForExternalWhiteReference()
        {
            this.Control.MeasuringProgressState.ResetFlags();
            this.Control.MeasuringProgressState.SetFlag(MeasuringProgressStates.IsExternalReferencing);

            if (this.Finder.GetMeasuringState(this.SubstanceManagement) == MeasuringState.MeasuringInsert)
                this.Finder.MeasuringState = MeasuringState.WhiteInsetReference;
            else
                this.Finder.MeasuringState = MeasuringState.WhiteReference;

            this.Finder.FinderButton.CycleIsRunning = false;

            this.Control.Busy = false;
            this.Update(ApoIdentApplicationState.MeasuringWaitForNextCycle);
            this.WaitForContinueMeasuring();
        }

        private void OnInsetToggleButtonToggled(object sender, EventArgs eventArgs)
        {
            this.Finder.IsInsertActive = (sender as Gtk.ToggleButton).Active;
            this.Finder.MeasuringState = this.Finder.GetMeasuringState(this.SubstanceManagement);
            this.Update(ApoIdentApplicationState.MeasuringStateChanged);
        }

        private void OnFinderInternalCalibrationStarted()
        {
            this.Update(ApoIdentApplicationState.MeasuringStartInternalCaibration);
        }

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

            if (this.SubstanceManagement.CurrentModelClass == null)
                return;

            if (this.SubstanceManagement.CurrentModelClass.SubstanceType != SubstanceType.Fluid)
                return;

            System.Threading.Tasks.Task.Run(() => 
            {
                this.WaitForEndOfInternalCalibration(this.Finder.AbortCalibrationToken);
            });
        }

        private void WaitForEndOfInternalCalibration(CombinedFinder.AbortToken abort_token)
        {
            try
            {
                DateTime time_out = DateTime.Now.AddMilliseconds(this.Finder.Current.RecalibrationTimeoutMilliseconds + 1000);

                for (; ; )
                {
                    if (time_out < DateTime.Now)
                        break;

                    if (abort_token.IsAborted)
                        return;

                    TimeSpan diff = time_out - DateTime.Now;
                    if (diff.TotalMilliseconds > 0)
                    {
                        string time_left = String.Format(Catalog.GetString("{0:D2}:{1:D2} min"),
                                                     diff.Minutes,
                                                     diff.Seconds);

                        this.Update(ApoIdentApplicationState.MeasuringTimeLeft, time_left);
                    }

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

                if (abort_token.IsAborted)
                    return;

                if (this.Control.MeasuringProgressState.IsReferencing)
                    return;

                this.Finder.MeasuringState = this.Finder.GetMeasuringState(this.SubstanceManagement);
                if (this.Control.MeasuringProgressState.IsAcquiringFirstFluid)
                {
                    this.Control.MeasuringProgressState.ResetFlags();
                    this.Update(ApoIdentApplicationState.SubstanceManagementSubstanceAfterSelectionChanged);
                }
                else
                {
                    this.Update(ApoIdentApplicationState.MeasuringEndInternalCaibration);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("WaitForEndOfInternalCalibration failed: {0}", ex);
            }
        }

        private void ExecuteMeasurement(bool is_transflectance, bool is_fluid, bool is_inset)
        {
            try
            {
                lock (this.Control.Transaction)
                {
                    if (this.Control.NoMisIdentificationYet == true)
                    {
                        if (this.CheckForNotSavedProtocolDetails() == false)
                            return;
                    }

                    this.Control.PrepareMeasurement();
                    this.Control.Busy = true;
                    this.Update(ApoIdentApplicationState.MeasuringStart);

                    bool abort_repeating = false;
                    int repeats = 0;

                    string message = Catalog.GetString("Error while acquiring spectrum: {0}");
                    string title = Catalog.GetString("<b>Error while Transflectance Reference measurement:</b>\n\n");
                    if (is_transflectance == false)
                        title = Catalog.GetString("<b>Error while measuring specimen:</b>\n\n");

                    Exception exception = null;
                    EventHandler handler = null;
                    ApoIdentApplicationState error_state = ApoIdentApplicationState.MeasuringFailed;


                    while (abort_repeating == false && this.Control.MeasurementProcessInterrupted == false)
                    {
                        abort_repeating = true;
                        try
                        {
                            this.Finder.SwitchFinder();

                            if (is_transflectance == false)
                            {
                                if (ApoIdentEnvs.SimulateMeasuringOff)
                                {
                                    this.Finder.Current.Acquire(is_fluid);
                                    this.Finder.Current.SaveReferenceSpectrum(this.Finder.Current.CurrentSampleSpectrum,
                                                                              is_fluid ? SpectrumType.FluidSpecimen : SpectrumType.SolidSpecimen,
                                                                              false, string.Empty);


                                    this.Control.CurrentProcessedSpectrum = this.Finder.Current.Process(is_fluid);
                                    this.Control.CurrentProcessedSpectrum.LimitBandwidth = true;
                                }
                                else
                                {
                                    string resource = "Hiperscan.Extensions.ApoIdent.Resources.Simulator.SimulationSpectrum.csv";

                                    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource))
                                    {
                                        this.Control.CurrentProcessedSpectrum = CSV.Read(stream);
                                    }
                                }

                                if (Math.Abs(this.Control.CurrentProcessedSpectrum.Absorbance.YMax()) < 0.02 &&
                                    Math.Abs(this.Control.CurrentProcessedSpectrum.Absorbance.YMean()) < 0.01)
                                    throw new Exception(Catalog.GetString("Invalid specimen spectrum."));

                                this.IdentServerClient.Classify(this.Control.CurrentProcessedSpectrum);
                                this.Control.ProtocolState.SetFlag(ProtocolStates.ProtocolNotSaved);
                                this.Control.ProtocolState.SetFlag(ProtocolStates.IdentificationResultExists);
                            }
                            else
                            {
                                bool was_referencing_pending = this.Finder.Current.ReferencingIsPending;
                                this.Finder.Current.AcquireExternalTransflectanceReference();
                                if (was_referencing_pending && this.Finder.Current.RecalibrationIsPending)
                                {
                                    throw new Exception(Catalog.GetString("The reference procedure for fluids must be finished in a short time period (max. 5 Minutes). " +
                                                                                        "Please repeat referencing."));
                                }
                            }
                        }
                        catch (Finder.InvalidTransflectReferenceException ex)
                        {
                            error_state = ApoIdentApplicationState.MeasuringTransflectanceFailed;
                            handler = this.ShowErrorTransflectanceReference;
                            message = Catalog.GetString("Always start by applying a clean sample cup and the Transflectance Insert(feet down). " +
                                                        "Information on how to perform the measurement can be found " +
                                                        "in the Operating Manual under Help.\n\n" +
                                                        "Click Reset and repeat the Transflectance Reference measurement.");

                            message += ApoIdentApplication.ServiceForRepeatedError;

                            exception = ex;
                        }
                        catch (Finder.BlackReferenceException ex)
                        {
                            error_state = ApoIdentApplicationState.MeasuringBlackFailed;
                            handler = this.ShowErrorBlackReference;
                            title = Catalog.GetString("<b>Error while Black Reference measurement:</b>\n\n");
                            message = String.Format(message, ex.Message);
                            exception = ex;
                        }
                        catch (Finder.WhiteReferenceException ex)
                        {
                            error_state = ApoIdentApplicationState.MeasuringWhiteFailed;
                            handler = this.ShowErrorWhiteReference;
                            title = Catalog.GetString("<b> Error while White Reference measurement:</b>\n\n");
                            message = String.Format(message, ex.Message);
                            exception = ex;
                        }
                        catch (LevenbergMarquardt.MaxIterationNumberException ex) //InternalCalibrationException
                        {
                            if (repeats == 0)
                                abort_repeating = false;
                            else
                            {
                                message = String.Format(message, ex.Message);
                                exception = ex;
                            }

                            repeats++;

                        }
                        catch (InvalidDistanceException ex)
                        {
                            message = String.Format(Catalog.GetString("Probe specimen is not valid."));
                            exception = ex;
                        }
                        catch (System.Runtime.Remoting.RemotingException ex)
                        {
                            this.Control.CurrentProcessedSpectrum = null;
                            this.IdentServerClient.Reset();
                            this.Finder.Reset();
                            this.Control.ConnectionSuccess = false;
                            message = String.Format(Catalog.GetString("Measurement executing error: {0}"), ex.Message);
                            exception = ex;
                        }
                        catch (Exception ex)
                        {
                            if (is_transflectance == false)
                            {
                                try
                                {
                                    this.Finder.Current.SaveReferenceSpectrum(this.Finder.Current.CurrentSampleSpectrum,
                                                                              this.SubstanceManagement.CurrentModelClass.SubstanceType == SubstanceType.Fluid ? SpectrumType.FluidSpecimen : SpectrumType.SolidSpecimen,
                                                                              true, string.Empty);
                                }
                                catch { }

                            }
                            message = String.Format(Catalog.GetString("Measurement executing error: {0}"), ex.Message);
                            if (ex is NullReferenceException)
                                message = Catalog.GetString("Measurement executing error: An invalid spectrum was returned.");

                            exception = ex;
                        }
                        finally
                        {
                            this.Finder.Idle();
                        }
                    }

                    this.Finder.MeasuringState = this.Finder.GetMeasuringState(this.SubstanceManagement);
                    this.Finder.FinderButton.CycleIsRunning = false;
                    this.Control.MeasuringProgressState.ResetFlags();

                    if (this.Control.MeasurementProcessInterrupted)
                    {
                        this.HandleMeasuringInterrupt();
                        return;
                    }

                    if (exception == null)
                    {
                        this.HandleMeasuringSuccess(is_transflectance);
                        return;
                    }

                    if (is_fluid && is_transflectance == false)
                        this.Control.MeasuringProgressState.SetFlag(MeasuringProgressStates.IsAcquiringFirstFluid);

                    this.HandleMeasuringError(error_state, message, exception, title, handler);
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine("Unexpected error while aborting measuring: {0}", ex);
            }
        }

        public void HandleMeasuringSuccess(bool is_transflectance)
        {
            if (is_transflectance)
            {
                this.Control.MeasuringProgressState.SetFlag(MeasuringProgressStates.IsAcquiringFirstFluid);
                this.Control.Busy = false;
                this.Update(ApoIdentApplicationState.MeasuringForFirstFluid);
            }
            else
            {
                this.CheckAdditionalTestAsMandatory();
                this.Control.Busy = false;
                this.Update(ApoIdentApplicationState.MeasuringSuccess);
                this.CheckIdentificationResult();
            }
        }

        private void HandleMeasuringInterrupt()
        {
            this.Config.Profiles.Current.AdditionalCompletionEntries.Reset();
            this.IdentServerClient.DiscardResult();
            this.Finder.ResetRecalibrationTimestamp();

            this.Control.MeasurementProcessInterrupted = false;

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

            this.Control.NoMisIdentificationYet = true;
            this.Control.Busy = false;
            this.Update(ApoIdentApplicationState.MeasuringInterruptedEnd, message);
        }

        private void HandleMeasuringError(ApoIdentApplicationState state, string message, Exception ex, string title, EventHandler handler)
        {
            this.Config.Profiles.Current.AdditionalCompletionEntries.Reset();
            this.IdentServerClient.DiscardResult();
            //this.Finder.ResetRecalibrationTimestamp();

            this.Control.NoMisIdentificationYet = true;
            this.Control.Busy = false;
            this.Update(state, message, ex, title, handler);
            this.Update(ApoIdentApplicationState.MeasuringFailedPostProcessing);
        }

        private void WaitForContinueMeasuring()
        {
            this.Finder.Wait = true;
            for (; ; )
            {
                if (this.Control.MeasurementProcessInterrupted)
                    throw new Exception(Catalog.GetString("Recalibration procedure was not finished. " +
                                                         "Please repeat measurement."));

                if (this.Finder.Wait == false)
                    break;

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

        private void OnMeasuringAbortButtonActivated(object sender, EventArgs e)
        {
            this.AbortReferencing();
        }

        private void OnMeasuringAbortButtonClicked(object sender, EventArgs e)
        {
            this.AbortReferencing();
        }

        private void AbortReferencing()
        {
            this.Finder.ResetReferences();

            this.Control.Busy = true;
            this.Update(ApoIdentApplicationState.MeasuringInterruptedStart);
            this.Control.MeasurementProcessInterrupted = true;
        }

        public void ShowErrorBlackReference(object o, EventArgs e)
        {
            this.ShowPDF("error_black_reference.pdf");
        }

        public void ShowErrorWhiteReference(object o, EventArgs e)
        {
            this.ShowPDF("error_white_reference.pdf");
        }

        public void ShowErrorTransflectanceReference(object o, EventArgs e)
        {
            this.ShowPDF("error_transflectance_reference.pdf");
        }
    }
}
            