// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2024. 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.Unix;
using Hiperscan.Spectroscopy.Math;
using Hiperscan.GuiExtensions.Dialog;
using Hiperscan.Extensions.ReferenceCheck.Control.Enums;
using Hiperscan.Extensions.ReferenceCheck.Control.GuiUpdate;
using Hiperscan.Extensions.ReferenceCheck.Control.Acquisition;

namespace Hiperscan.Extensions.ReferenceCheck.Control
{
    public partial class CoreApp
    {
        private void GoToAcqquisitionSection()
        {
            if (this.CurrentAcquisitionSet == null)
            {
                this.InitAcquisitionSection();
                this.SetNewSessions();
                this.CurrentAcquisitionSet.SetReadyForAcquisition();
                this.CurrentVisibleView = ViewTypes.Acquisition;
                return;
            }

            switch (this.CurrentAcquisitionSet.AcquisitionType)
            {
                case AcquisitionType.Black:
                    this.CurrentAcquisitionSet = this.WhiteReferenceSet;
                    this.CurrentAcquisitionSet.SetReadyForAcquisition();
                    break;

                case AcquisitionType.White:
                    if (this.TransflectanceReferenceSet.AllSessionsComplete)
                    {
                        goto case AcquisitionType.Transflectance;
                    }
                    else
                    {
                        this.CurrentAcquisitionSet = this.TransflectanceReferenceSet;
                        this.CurrentAcquisitionSet.SetReadyForAcquisition();
                    }
                    break;

                case AcquisitionType.Transflectance:
                    if (this.InsertWhiteReferenceSet.AllSessionsComplete)
                    {
                        this.CurrentAcquisitionSet = this.EmptyReferenceSet;
                        this.CurrentAcquisitionSet.SetReadyForAcquisition();
                    }
                    else
                    {
                        this.CurrentAcquisitionSet = this.InsertWhiteReferenceSet;
                        this.CurrentAcquisitionSet.SetReadyForAcquisition();
                    }
                    break;

                case AcquisitionType.WhiteInsert:
                    this.CurrentAcquisitionSet = this.EmptyReferenceSet;
                    this.CurrentAcquisitionSet.SetReadyForAcquisition();
                    break;

                case AcquisitionType.Empty:
                    if (this.CurrentAcquisitionSet.AllSessionsComplete)
                    {
                        this.CalibrationSet.CurrentSession.StopTimer();
                        this.FinderButton.Reset();
                        this.StartEvaluation();

                        this.GotoProtocolSection();
                    }
                    else 
                    {
                        this.SetNewSessions();
                        this.CurrentAcquisitionSet = this.BlackReferenceSet;
                        this.CurrentAcquisitionSet.SetReadyForAcquisition();
                    }
                    break;

                    default:
                    break;
            }

            UpdateArgs args = new UpdateArgs();
            args.ViewType = ViewTypes.Acquisition;
            args.UpdateCatecory = UpdateCategory.All;
            this.UpdateGuiNeeded?.Invoke(args);
        }

        private void GoToNextAcquisition()
        {
            this.GoToAcqquisitionSection();
        }

        private void InitAcquisitionSection()
        {
            AcquisitionSessions sessions = this.SessionManager.AllSesstions[AcquisitionType.Black];
            this.BlackReferenceSet = new BlackReferenceSet(this.FinderButton.FinderButtonState, sessions);
            this.BlackReferenceSet.UpdateGuiNeeded += (arg) => this.UpdateGuiNeeded?.Invoke(arg);
            this.CurrentAcquisitionSet = this.BlackReferenceSet;

            sessions = this.SessionManager.AllSesstions[AcquisitionType.White];
            this.WhiteReferenceSet = new WhiteReferenceSet(this.FinderButton.FinderButtonState, sessions);
            this.WhiteReferenceSet.UpdateGuiNeeded += (arg) => this.UpdateGuiNeeded?.Invoke(arg);

            sessions = this.SessionManager.AllSesstions[AcquisitionType.Transflectance];
            this.TransflectanceReferenceSet = new TransflectanceReferenceSet(this.FinderButton.FinderButtonState, sessions);
            this.TransflectanceReferenceSet.UpdateGuiNeeded += (arg) => this.UpdateGuiNeeded?.Invoke(arg);

            sessions = this.SessionManager.AllSesstions[AcquisitionType.WhiteInsert];
            this.InsertWhiteReferenceSet = new InsertWhiteReferenceSet(this.FinderButton.FinderButtonState, sessions);
            this.InsertWhiteReferenceSet.UpdateGuiNeeded += (arg) => this.UpdateGuiNeeded?.Invoke(arg);

            sessions = this.SessionManager.AllSesstions[AcquisitionType.Empty];
            this.EmptyReferenceSet = new EmptyReferenceSet(this.FinderButton.FinderButtonState, sessions);
            this.EmptyReferenceSet.UpdateGuiNeeded += (arg) => this.UpdateGuiNeeded?.Invoke(arg);

            this.CalibrationSet = this.SessionManager.CalibrationSessions;
            foreach (CalibrationSession session in this.CalibrationSet.SessionQuue)
                session.UpdateGuiNeeded += (arg) => this.UpdateGuiNeeded?.Invoke(arg);
        }

        private void SetNewSessions()
        {
            this.BlackReferenceSet.InitNewSession();
            this.WhiteReferenceSet.InitNewSession();
            this.TransflectanceReferenceSet.InitNewSession();
            this.InsertWhiteReferenceSet.InitNewSession();
            this.EmptyReferenceSet.InitNewSession();

            this.CalibrationSet.CurrentSession?.StopTimer();
            this.CalibrationSet.GetNextSession();
        }

        public void AcquireSpectrum()
        {
            Task.Run(() => this.FinderButton.StartMeasuringFromExtern());
        }

        private void OnMeasuringStarted(object sender, EventArgs e)
        { 
            if (this.CurrentAcquisitionSet.IsSerialValid == false)
                return;

            try
            {
                this.Host.ExceptionQueue.Clear();
                this.CurrentAcquisitionSet.SetAcquisitionStart();
                if (this.AcquireInternalReferences())
                    this.CurrentAcquisitionSet.SetAcquisitionStart();

                this.AcquireReference();
                if (this.Host.ExceptionQueue.Count > 0)
                {
                    Exception ex = (Exception)this.Host.ExceptionQueue.Dequeue();
                    throw new Exception(ex.Message, ex);
                }
            }
            catch(Exception ex)
            {
                this.CalibrationSet.CurrentSession.IsInternalRecalibrating = false;
                this.CurrentAcquisitionSet.SetAcquisitionFailed();

                UpdateArgs arg = new UpdateArgs();
                arg.DialogArgs.StandardDialogType = StandardDialogType.Error;
                arg.DialogArgs.Exception = ex;
                arg.DialogArgs.Message = String.Format(Catalog.GetString("Cannot acquire spectrum: {0}"), ex.Message);
                this.UpdateGuiNeeded?.Invoke(arg);
            }
        }

        private void CheckForUniqueSerial()
        {
            if (this.CurrentAcquisitionSet.SerialExists)    
                throw new Exception(this.CurrentAcquisitionSet.SerialExistsMessage);
        }

        private void AcquireReference()
        {
            this.CheckForUniqueSerial();

            switch (this.CurrentAcquisitionSet.AcquisitionType)
            {
                case AcquisitionType.Black:
                    this.Finder.AcquireExternalBlack();
                    this.Finder.ReferencingSet.Black.ProbeType = this.Finder.ProbeType;
                    this.CurrentAcquisitionSet.SetAcquisitionFinished(this.Finder.ReferencingSet.Black);
                    break;

                case AcquisitionType.White:
                    this.Finder.AcquireExternalWhite();
                    this.Finder.ReferencingSet.White.ProbeType = this.Finder.ProbeType;
                    this.CurrentAcquisitionSet.SetAcquisitionFinished(this.Finder.ReferencingSet.White);
                    break;

                case AcquisitionType.Transflectance:
                    this.Finder.AcquireExternalTransflectance();
                    this.Finder.ReferencingSet.Transflectance.ProbeType = this.Finder.ProbeType;
                    this.CurrentAcquisitionSet.SetAcquisitionFinished(this.Finder.ReferencingSet.Transflectance);
                    break;

                case AcquisitionType.WhiteInsert:
                    this.Finder.ChangeProbeType(true);
                    this.Finder.AcquireExternalWhite();
                    this.Finder.ReferencingSet.White.ProbeType = this.Finder.ProbeType;
                    this.CurrentAcquisitionSet.SetAcquisitionFinished(this.Finder.ReferencingSet.White);
                    this.Finder.ChangeProbeType(false);
                    break;

                case AcquisitionType.Empty:
                    this.Finder.AcquireSample(false);
                    this.CurrentAcquisitionSet.SetAcquisitionFinished(this.Finder.SampleSet.Sample);
                    break;
            }
        }

        private bool AcquireInternalReferences()
        {
            if (this.CalibrationSet.CurrentSession.HasInternalCalibration)
            {
                if (this.CalibrationSet.CurrentSession.IsTimeOut)
                {
                    string message = String.Format(Catalog.GetString("The time of {0} min for proceeding is over. Please close and restart the application."),
                                                   this.CalibrationSet.CurrentSession.TimeOutTimeMinutes);

                    throw new Exception(message);
                }

                return false;
            }

            this.CalibrationSet.CurrentSession.IsInternalRecalibrating = true;

            UpdateArgs arg = new UpdateArgs();
            arg.UpdateCatecory = UpdateCategory.All;
            arg.ViewType = Enums.ViewTypes.Acquisition;
            this.UpdateGuiNeeded?.Invoke(arg);

            try
            {
                this.AcquireCalibrationSet();
                this.CheckWaveLengthCorrectionOfCalibrationSet();
            }
            catch (LevenbergMarquardt.MaxIterationNumberException)
            {
                this.Finder.AcquireInternalOxid();
                this.CalibrationSet.CurrentSession.OxidSpectrum = this.Finder.RecalibrationSet.Oxid;
                this.CheckWaveLengthCorrectionOfCalibrationSet();
            }

            return true;
        }

        private void AcquireCalibrationSet()
        {
            this.Finder.ChangeProbeType(false);
            this.Finder.AcquireInternalDark();
            this.Finder.AcquireInternalWhite();
            this.Finder.AcquireInternalOxid();

            if (this.Host.ExceptionQueue.Count > 0)
            {
                Exception ex = (Exception)this.Host.ExceptionQueue.Dequeue();
                throw new Exception(ex.Message, ex);
            }

            this.CalibrationSet.CurrentSession.DarkSpectrum = this.Finder.RecalibrationSet.Dark;
            this.CalibrationSet.CurrentSession.WhiteSpectrum = this.Finder.RecalibrationSet.White;
            this.CalibrationSet.CurrentSession.OxidSpectrum = this.Finder.RecalibrationSet.Oxid;

            this.CalibrationSet.CurrentSession.HasInternalCalibration = true;
            this.CalibrationSet.CurrentSession.StartTimer();
            this.CalibrationSet.CurrentSession.IsInternalRecalibrating = false;
        }

        private void CheckWaveLengthCorrectionOfCalibrationSet()
        {
            Evaluation.ResultData data = new Evaluation.ResultData();
            data.SetWheelVersion(this.Finder.ReferenceWheelVersion);
            Spectroscopy.Processing.SpectrumProcessor processor = data.Processor;
            if (processor.GetType() == typeof(Spectroscopy.Processing.ReferenceBlockProcessor))
            {
                //FIXME
                throw new NotSupportedException();
            }
            else if (processor.GetType() == typeof(Spectroscopy.Processing.ReferenceWheelProcessor))
            {
                Spectroscopy.Processing.ReferenceWheelProcessor wheel = 
                    (Spectroscopy.Processing.ReferenceWheelProcessor)processor;
                wheel.InternalDarkReference = this.CalibrationSet.CurrentSession.DarkSpectrum;
                wheel.InternalWhiteReference = this.CalibrationSet.CurrentSession.WhiteSpectrum;
                wheel.InternalOxidReference = this.CalibrationSet.CurrentSession.OxidSpectrum;
                wheel.ForceUpdateWavelengthCorrection(false);
            }
            else
            {
                throw new Exception("Cannot check wavelengthcorrection.\nUnknown referencewheel given.");
            }
        }

        private void StartEvaluation()
        {
            this.SessionManager.Result.Evaluate(this.SessionManager.AllSesstions,
                                                this.SessionManager.CalibrationSessions);
        }

        public string AcqquisitionTime
        {
            get
            {
                return this.CalibrationSet?.CurrentSession?.GetAcqquisitionTime() ?? String.Empty;
            }
        }

        public bool IsInternalRecalibrating
        {
            get
            {
                return this.CalibrationSet?.CurrentSession?.IsInternalRecalibrating ?? false;
            }
        }

        public AcquisitionSet CurrentAcquisitionSet { get; private set; }
        private  BlackReferenceSet BlackReferenceSet { get; set; }
        private WhiteReferenceSet WhiteReferenceSet { get; set; }
        private TransflectanceReferenceSet TransflectanceReferenceSet { get; set; }
        private InsertWhiteReferenceSet InsertWhiteReferenceSet { get; set; }
        private EmptyReferenceSet EmptyReferenceSet { get; set; }

        public CalibrationSessions CalibrationSet { get; set; }
    }
}
