// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2018 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 System.Collections.Generic;
using Gtk;
using System.IO;


using Hiperscan.Unix;
using Hiperscan.I18n;
using Glade;
using Hiperscan.Spectroscopy;
using Hiperscan.SGS;
using Hiperscan.Spectroscopy.IdentModule;
using HollyLibrary;
using GLib;

namespace Hiperscan.Extensions.ApoIdent
{
    public partial class BlindClassificationDialog
    {
        private enum Columns
        {
            Classificator,
            ClassificationName,
            Info,
            SubstanceNames
        }

        [Widget] Gtk.Dialog blind_classfication_dialog      = null;
        [Widget] Gtk.Image transflectance_image             = null;
        [Widget] Gtk.Image measurement_image                = null;
        [Widget] Gtk.ComboBox sample_type_combobox          = null;

        public BlindClassificationDialog(FinderWindow finder_window)
        {
            this.FinderWindow = finder_window;
            this.StashDeviceStates();
            this.InitSolidsAndLiquids();
            this.InitGui();
            this.blind_classfication_dialog.Run();
            this.blind_classfication_dialog.Destroy();
            this.UnstashDeviceStates();
            this.FinderWindow.CurrentTopLeveLWindow = this.FinderWindow;
        }

        private void StashDeviceStates()
        {
            try
            {
                this.OldProbeType = this.FinderWindow.Finder.Current.ProbeType;

                this.FinderWindow.Finder.SwitchFinder(ProbeType.FinderStandard);
                this.OldAutoReferencing = this.FinderWindow.Finder.Current.AutoSaveReferences;
                this.FinderWindow.Finder.Current.AutoSaveReferences = false;

                this.NoStandardReferenced = this.FinderWindow.InitState == ServerDeviceState.Success &&
                    this.FinderWindow.Finder != null &&
                    this.FinderWindow.Finder.Current != null &&
                    this.FinderWindow.Finder.Current.ReferencingIsPending == true;

                this.FinderWindow.Finder.Current.ResetRecalibrationTimestamp();
            }
            catch (Exception)
            {
                this.OldProbeType = ProbeType.Unknown;
            }             
        }

        private void UnstashDeviceStates()
        {
            if (this.OldProbeType == ProbeType.Unknown)
                return;

            try
            {
                this.FinderWindow.Finder.Current.AutoSaveReferences = this.OldAutoReferencing;
                this.FinderWindow.Finder.Current.ResetRecalibrationTimestamp();
                if (this.NoStandardReferenced == true)
                    this.FinderWindow.Finder.Current.ResetReferences();

                this.FinderWindow.Finder.SwitchFinder(this.OldProbeType);
            }
            catch (Exception)
            {               
            }

            this.FinderWindow.Update(FinderUpdateState.AfterLeavingBlindClassification);
        }

        private void InitSolidsAndLiquids()
        {
            foreach (string model_class_name in this.FinderWindow.SubstanceManagement.ModelClassNameDetailResolver.Keys)
            {
                ModelClassInfo model_class_info = this.FinderWindow.SubstanceManagement.ModelClassNameDetailResolver[model_class_name];
                if (model_class_info.DatabaseInfo == AccessDatabaseInfo.UserDefined)
                    continue;

                if (model_class_info.IsFluid)
                    this.Liquids.Add(model_class_name, model_class_info);
                else
                    this.Solids.Add(model_class_name, model_class_info);
            }
        }


        private void InitGui()
        {
            Glade.XML gui = new Glade.XML(null, "blind_classfication_dialog", "blind_classfication_dialog", "Hiperscan");
            gui.Autoconnect(this);

            this.blind_classfication_dialog.SetWmclass(Plugin.name.ToLower(),
                                           Plugin.name.ToLower());

            this.blind_classfication_dialog.TransientFor = this.FinderWindow.GtkWindow;
            this.blind_classfication_dialog.IconList = ApoIdentStock.FinderIcons;

            this.measurement_image.Stock = ApoIdentStock.START;
            this.transflectance_image.Stock = ApoIdentStock.START;

            this.IsUpdating = true;
            this.sample_type_combobox.Active = 0;
            this.IsUpdating = false;

            this.measurement_advice_label.ModifyFg(Gtk.StateType.Normal, new Gdk.Color(209, 32, 35));
            this.transflectance_advice_label.ModifyFg(Gtk.StateType.Normal, new Gdk.Color(209, 32, 35));

            this.FinderWindow.CurrentTopLeveLWindow = this;
            this.InitTV();
            this.UpdateGui(BLindClassificationState.Init);
        }

        private void InitTV()
        {
            CellRendererInfo info = new CellRendererInfo();
            info.Activatable = true;
            info.Toggled += this.OnSubstanceInfoToggled;

            this.result_treeview.AppendColumn(Catalog.GetString("Classifier"), new CellRendererText(), "text", Columns.Classificator);
            this.result_treeview.AppendColumn(Catalog.GetString("Result NIRS"), new CellRendererText(), "text", Columns.ClassificationName);
            this.result_treeview.AppendColumn("   ", info, "active", Columns.Info);

            this.ResultStore = new ListStore(typeof(string),
                                             typeof(string),
                                             typeof(bool),
                                             typeof(List<string>));

            this.result_treeview.Model = this.ResultStore;
            this.result_treeview.ButtonPressEvent += this.OnButtonPressEvent;
        }



        private void Analyze()
        {
            Dictionary<string, ModelClassInfo> current = this.IsFluid ? this.Liquids : this.Solids;
            this.Results = this.FinderWindow.IdentServerClient.ClassifiyBlindClassification(current, this.CurrentProcessedSpectrum);
        }


        private void OnSubstanceInfoToggled(object sender, ToggledArgs args)
        {
            TreeIter iter;
            if (this.ResultStore.GetIter(out iter, new TreePath(args.Path)))
            {
                bool has_info = (bool)this.ResultStore.GetValue(iter, (int)Columns.Info);
                if (has_info == false)
                    return;

                List<string> substance_names = (List<string>)this.ResultStore.GetValue(iter, (int)Columns.SubstanceNames);
                substance_names.Sort();

                string separator = "\n\t- ";

                string message = String.Format(Catalog.GetString("Follwing substances are summarised in this classification:{0}{1}"),
                                               separator,
                                               String.Join(separator, substance_names.ToArray()));


                string classification_name = (string)this.ResultStore.GetValue(iter, (int)Columns.ClassificationName);

                MessageDialog mdlg = new MessageDialog(this.blind_classfication_dialog,
                                                       DialogFlags.Modal,
                                                       Gtk.MessageType.Info,
                                                       ButtonsType.Close,
                                                       message);
                mdlg.Title = classification_name;
                mdlg.IconList = ApoIdentStock.FinderIcons;

                mdlg.Run();
                mdlg.Destroy();               
            }
        }

        private void OnSampleTypeComboChanged(object sender, EventArgs e)
        {
            if (this.IsUpdating == true)
                return;

            this.CurrentProcessedSpectrum = null;

            try
            {
                this.FinderWindow.Finder.SwitchFinder(ProbeType.FinderStandard);
                this.FinderWindow.Finder.Current.ResetRecalibrationTimestamp();
            }
            catch { }

            this.IsFluid = this.sample_type_combobox.Active == 1;
            this.UpdateGui(BLindClassificationState.SampleTypeChanged);
        }

        private void OnTransflectanceButtonClicked(object sender, EventArgs e)
        {
            this.LostConnection = false;
            this.MeasurementSuccess = true;
            this.TransflectanceSuccess = true;
            this.ErrorMessage = String.Empty;
            this.CurrentProcessedSpectrum = null;
            this.UpdateGui(BLindClassificationState.StartTranslectance);

            bool abort_repeating = false;
            int repeats = 0;

            while (abort_repeating == false)
            {
                abort_repeating = true;
                try
                {
                    this.FinderWindow.Finder.Current.AcquireExternalTransflectanceReference();
                }
                catch (NullReferenceException) { /* may happen if device was disconnected */ }
                catch (Finder.InvalidTransflectReferenceException)
                {

                    this.ErrorMessage = String.Format(Catalog.GetString("<b> Error while Transflectance Reference measurement:</b>\n\n" +
                                                                     "Always start by applying a clean sample cup and the Transflectance Insert(feet down). " +
                                                                     "Please click on 'Information' in order to see how to perform a measurement.\n\n" +
                                                                     "Click Reset and repeat the Transflectance Reference measurement.\n\n" +
                                                                     "In case of permanent problems, please contact your distributor or Hiperscan customer " +
                                                                     "support under info@hiperscan.com or +49 351 212 496 0 for further information."));


                    this.TransflectanceSuccess = false;
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, Stock.Info, this.FinderWindow.ShowErrorDieReference, true, this.ErrorMessage);
                    this.FinderWindow.host.ProcessEvents();
                }
                catch (Finder.EmptyReferenceException ex)
                {
                    this.TransflectanceSuccess = false;
                    this.ErrorMessage = String.Format(Catalog.GetString("Error during empty referencing: {0}"), ex.Message);
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, Stock.Info, this.FinderWindow.ShowErrorEmptyReference, this.ErrorMessage);
                    this.FinderWindow.host.ProcessEvents();
                }
                catch (Finder.WhiteReferenceException ex)
                {
                    this.TransflectanceSuccess = false;
                    this.ErrorMessage = String.Format(Catalog.GetString("Error during white referencing: {0}"), ex.Message);
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, Stock.Info, this.FinderWindow.ShowErrorWhiteReference, this.ErrorMessage);
                    this.FinderWindow.host.ProcessEvents();
                }
                catch (LevenbergMarquardt.MaxIterationNumberException ex) //InternalCalibrationException
                {
                    if (repeats == 0)
                        abort_repeating = false;
                    else
                    {
                        this.TransflectanceSuccess = false;
                        this.ErrorMessage = String.Format(Catalog.GetString("Error during internal referencing: {0}"), ex.Message);
                        this.ErrorMessage += FinderWindow.ServiceForNoRepeatedError;
                        this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);
                    }
                    this.FinderWindow.host.ProcessEvents();
                    repeats++;
                }
                catch (InvalidDistanceException)
                {
                    this.TransflectanceSuccess = false;
                    this.ErrorMessage= String.Format(Catalog.GetString("Probe specimen is not valid."));
                    this.ErrorMessage += FinderWindow.ServiceForNoRepeatedError;
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);
                    this.FinderWindow.host.ProcessEvents();
                }
                catch (System.Runtime.Remoting.RemotingException ex)
                {
                    this.TransflectanceSuccess = false;
                    this.LostConnection = true;

                    this.ErrorMessage = String.Format(Catalog.GetString("Empty die executing error: {0}"), ex.Message);
                    this.ErrorMessage += FinderWindow.ServiceForRepeatedError;

                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);
                    this.FinderWindow.host.ProcessEvents();
                }
                catch (Exception ex)
                {
                    this.TransflectanceSuccess = false;
                    this.ErrorMessage = String.Format(Catalog.GetString("Empty die executing error: {0}"), ex.Message);
                    this.ErrorMessage += FinderWindow.ServiceForRepeatedError;
                    this.FinderWindow.Finder.Current.ResetRecalibrationTimestamp();
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);
                    this.FinderWindow.host.ProcessEvents();
                }
                finally
                {
                    if (abort_repeating == true)
                    {
                        this.FinderWindow.Finder.Idle();
                    }
                }

                if (this.TransflectanceSuccess == true)
                    this.UpdateGui(BLindClassificationState.EndTransflectance);
                else
                    this.UpdateGui( BLindClassificationState.FailedTransflectance);
            }
        }

        private void OnMeasurementButtonClicked(object sender, EventArgs e)
        {
            this.LostConnection = false;
            this.MeasurementSuccess = true;
            this.TransflectanceSuccess = true;
            this.ErrorMessage = String.Empty;
            this.CurrentProcessedSpectrum = null;
            this.UpdateGui(BLindClassificationState.StartMeasurement);

            bool abort_repeating = false;
            int repeats = 0;

            while (abort_repeating == false)
            {
                abort_repeating = true;
                this.MeasurementSuccess = true;
                try
                {
                    this.CurrentProcessedSpectrum = null;
                    this.FinderWindow.Finder.Current.Acquire(this.IsFluid);
                    this.CurrentProcessedSpectrum = this.FinderWindow.Finder.Current.Process(this.IsFluid);

                    this.CurrentProcessedSpectrum.LimitBandwidth = true;

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

                    this.Analyze();
                }
                catch (NullReferenceException) { /* may happen if device was disconnected */ }
                catch (Finder.EmptyReferenceException ex)
                {
                    this.MeasurementSuccess = false;
                    this.ErrorMessage = String.Format(Catalog.GetString("Error during empty referencing: {0}"), ex.Message);
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, Stock.Info, this.FinderWindow.ShowErrorEmptyReference, this.ErrorMessage);

                    this.FinderWindow.host.ProcessEvents();
                }
                catch (Finder.WhiteReferenceException ex)
                {
                    this.MeasurementSuccess = false;
                    this.ErrorMessage = String.Format(Catalog.GetString("Error during white referencing: {0}"), ex.Message);
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, Stock.Info, this.FinderWindow.ShowErrorWhiteReference, this.ErrorMessage);
                    this.FinderWindow.host.ProcessEvents();
                }
                catch (LevenbergMarquardt.MaxIterationNumberException ex) //InternalCalibrationException
                {
                    this.MeasurementSuccess = false;
                    if (repeats == 0)
                        abort_repeating = false;
                    else
                    {
                        this.ErrorMessage = String.Format(Catalog.GetString("Error during internal referencing: {0}"), ex.Message);
                        this.ErrorMessage += FinderWindow.ServiceForNoRepeatedError;
                        this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);
                    }
                    this.FinderWindow.host.ProcessEvents();
                    repeats++;
                }
                catch (NotFiniteNumberException)
                {
                    this.MeasurementSuccess = false;
                    this.ErrorMessage = String.Format(Catalog.GetString("Probe specimen is not valid."));
                    this.ErrorMessage += FinderWindow.ServiceForNoRepeatedError;
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);
                }
                catch (InvalidDistanceException)
                {
                    this.MeasurementSuccess = false;
                    this.ErrorMessage = String.Format(Catalog.GetString("Probe specimen is not valid."));
                    this.ErrorMessage += FinderWindow.ServiceForNoRepeatedError;
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);
                }
                catch (System.Runtime.Remoting.RemotingException ex)
                {
                    this.MeasurementSuccess = false;
                    this.LostConnection = true;

                    this.ErrorMessage= String.Format(Catalog.GetString("Measurement executing error: {0}"), ex.Message);
                    this.ErrorMessage += FinderWindow.ServiceForRepeatedError;

                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);
                    this.FinderWindow.host.ProcessEvents();
                }
                catch (Exception ex)
                {
                    this.MeasurementSuccess = false;
                    this.CurrentProcessedSpectrum = null;
                    this.ErrorMessage = String.Format(Catalog.GetString("Measurement executing error: {0}"), ex.Message); 
                    this.ErrorMessage += FinderWindow.ServiceForRepeatedError;
                    this.FinderWindow.host.ShowInfoMessage(this.blind_classfication_dialog, this.ErrorMessage);

                    this.FinderWindow.host.ProcessEvents();
                }
                finally
                {
                    if (abort_repeating == true)
                    {
                        try
                        {
                            this.FinderWindow.Finder.Idle();
                        }
                        catch (Exception)
                        {
                        }
                    }

                    try
                    {
                        this.FinderWindow.Finder.Current.ResetRecalibrationTimestamp();
                    }
                    catch{}
                }
            }

            if (this.MeasurementSuccess == true)
            {
                this.UpdateGui(BLindClassificationState.EndMeasurement);
            }
            else
            {
                this.CurrentProcessedSpectrum = null;
                this.UpdateGui(BLindClassificationState.FailedMeasurement);
            }                
        }

        [ConnectBefore]
        private void OnButtonPressEvent(object sender, ButtonPressEventArgs e)
        {
            if (e.Event.Button == 1 && e.Event.Type == Gdk.EventType.TwoButtonPress)
            {
                TreeIter iter;
                TreeModel tmp;
                if(this.result_treeview.Selection.GetSelected(out tmp , out iter) == true)
                {
                    string class_name = (string)this.ResultStore.GetValue(iter, (int)Columns.Classificator);
                    ResultSet result = this.Results[class_name];
                    new ResultListDialog(this.GtkWindow,
                                         this.FinderWindow.Config.Profiles.Current.ReportCountry,
                                         result,
                                         this.TestId);
                }
            }               
        }

        public string TestId
        {
            get
            {
                DateTime date = this.CurrentProcessedSpectrum.Timestamp;
                return (date.Year % 100).ToString() + date.ToString("MMddHHmmss");
            }
        }

        private Dictionary<string, ModelClassInfo> Liquids { get; set; } = new Dictionary<string, ModelClassInfo>();
        private Dictionary<string, ModelClassInfo> Solids { get; set; } = new Dictionary<string, ModelClassInfo>();


        private Spectrum CurrentProcessedSpectrum { get; set; }
        private FinderWindow FinderWindow   { get; set; }
        private ListStore ResultStore       { get; set; }
        private bool IsFluid { get; set; } = false;
        private bool LostConnection { get; set; } = false;
        private bool MeasurementSuccess { get; set; } = true;
        private bool TransflectanceSuccess { get; set; } = true;
        private string ErrorMessage { get; set; } = String.Empty;

        private bool NoStandardReferenced   { get; set; }
        private ProbeType OldProbeType      { get; set; }
        private bool OldAutoReferencing     { get; set; }
        private Dictionary<string, ResultSet> Results { get; set; } = new Dictionary<string, ResultSet>();
    }
}
