// Created with MonoDevelop
//
//    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 Hiperscan.Unix;
using Hiperscan.Spectroscopy;

using Hiperscan.Spectroscopy.Processing;

namespace Hiperscan.Spectroscopy.ReferenceChecks
{
    public partial class ReferenceChecks
    {
        #region TransflectanceInsertChecker

        private class TransflectanceInsertChecker
        {
            internal const double REFLECTANCE_MIN   = 0.4;
            internal const double REFLECTANCE_MAX   = 1.0;
            internal const double RMS_1_MAX         = 0.005;
            internal const double RMS_4_MAX         = 0.00653;
            internal const double SLOPE_MAX         = 3.33E-5;

            public double ReflectanceMin { get; private set; }
            public double ReflectanceMax { get; private set; }
            public double Rms1Max { get; private set; }
            public double Rms4Max { get; private set; }
            public double SlopeMax { get; private set; }

            public DataSet LastReflectanceDataSet { get; private set; }
            public double LastReflectanceMean { get; private set; }
            public double LastRms1 { get; private set; }
            public double LastRms4 { get; private set; }
            public double LastSlope { get; private set; }

            public string LastExceptionMessage { get; private set; }

            public TransflectanceInsertChecker(double reflectance_max, double reflectance_min, double rms_1_max, double rms_4_max, double slope_max)
            {
                this.ReflectanceMax = reflectance_max;
                this.ReflectanceMin = reflectance_min;
                this.Rms1Max = rms_1_max;
                this.Rms4Max = rms_4_max;
                this.SlopeMax = slope_max;
            }

            public bool Check(Spectrum transflectance_reference_spectrum, Spectrum ext_white_spectrum, DarkIntensity dark_intensity)
            {
                this.LastExceptionMessage = string.Empty;

                ext_white_spectrum = ext_white_spectrum.Clone();
                transflectance_reference_spectrum = transflectance_reference_spectrum.Clone();

                ext_white_spectrum.DarkIntensity = dark_intensity;
                transflectance_reference_spectrum.DarkIntensity = dark_intensity;

                DataSet transflectance = new DataSet(transflectance_reference_spectrum.RawIntensity);
                transflectance -= transflectance_reference_spectrum.DarkIntensity.Intensity;

                DataSet ext_white = new DataSet(ext_white_spectrum.RawIntensity);
                ext_white -= ext_white_spectrum.DarkIntensity.Intensity;

                DataSet reflectance = transflectance / ext_white;
                this.LastReflectanceDataSet = reflectance;

                if (this.CheckReflectance(reflectance) == false)
                    return false;
                if (this.CheckSqrtAndSlope(reflectance) == false)
                    return false;
                if (this.CheckRMS_4(reflectance) == false)
                    return false;

                return true;
            }

            // Criterion 1 - Feature #26056
            private bool CheckReflectance(DataSet transflectance_reflectance)
            {
                double mean = TransflectanceInsertChecker.GetReflectanceRange(transflectance_reflectance, 1050, 1850).YMean();
                this.LastReflectanceMean = mean;

                Console.WriteLine("Check transflectance criterion 1 -> mean reflectance: {0}", mean);


                if (mean <= this.ReflectanceMin)
                {
                    this.LastExceptionMessage = Catalog.GetString("The black reference and transflectance reference may have been mixed up.");
                    return false;
                }

                if (mean >= this.ReflectanceMax)
                {
                    this.LastExceptionMessage = Catalog.GetString("The white reference and transflectance reference may have been mixed up.");
                    return false;
                }

                return true;
            }

            // Criterion 2 - Feature #26056
            private bool CheckSqrtAndSlope(DataSet transflection_reflection)
            {
                DataSet range_1 = TransflectanceInsertChecker.GetReflectanceRange(transflection_reflection, 1100, 1150);
                DataSet range_2 = TransflectanceInsertChecker.GetReflectanceRange(transflection_reflection, 1251, 1800);

                range_1.AddRange(range_2);
                DataSet corr_reflections = range_1;

                DataSet.LinearRegressionResult result = corr_reflections.LinearRegression();
                DataSet regression = corr_reflections.LinearRegressionCurve(result);
                double abs_slope = System.Math.Abs(result.Slope);
                double rms = TransflectanceInsertChecker.GetRMS(corr_reflections, regression);

                Console.WriteLine("Check transflectance criterion 2a -> rms: {0}", rms);
                Console.WriteLine("Check transflectance criterion 2b -> slope: {0}", abs_slope);

                this.LastRms1 = rms;
                this.LastSlope = abs_slope;

                if (rms < this.Rms1Max && abs_slope <= this.SlopeMax)
                {
                    this.LastExceptionMessage = Catalog.GetString("The white reference may have been incorrectly applied during measurement.");
                    return false;
                }

                return true;
            }

            // Criterion 3 - Feature #26056
            private bool CheckRMS_4(DataSet transflection_reflection)
            {
                DataSet range_1 = TransflectanceInsertChecker.GetReflectanceRange(transflection_reflection, 1100, 1150);
                DataSet range_2 = TransflectanceInsertChecker.GetReflectanceRange(transflection_reflection, 1251, 1800);

                range_1.AddRange(range_2);
                DataSet corr_reflections = range_1;

                double[] p = MathNet.Numerics.Fit.Polynomial(corr_reflections.X.ToArray(), corr_reflections.Y.ToArray(), 4);
                DataSet regression = new DataSet(corr_reflections.X, 0);

                for (int i = 0; i < p.Length; i++)
                {
                    for (int j = 0; j < regression.Count; j++)
                    {
                        regression.Y[j] += System.Math.Pow(regression.X[j], i) * p[i];
                    }
                }

                double rms = TransflectanceInsertChecker.GetRMS(corr_reflections, regression);

                Console.WriteLine("Check transflectance criterion 3 -> rms: {0}", rms);

                if (rms >= this.Rms4Max)
                {
                    this.LastExceptionMessage = Catalog.GetString("A Sample may have been incorrectly applied during measurement.");
                    return false;
                }

                return true;
            }

            private static DataSet GetReflectanceRange(DataSet tranflection, double start_nm, double end_nm)
            {
                int start_index = 0;
                int end_index = 0;

                double tolerance = 0.00001;

                foreach (double x in tranflection.X)
                {
                    if (x > start_nm - tolerance)
                        break;

                    start_index++;
                }

                foreach (double x in tranflection.X)
                {
                    if (x > end_nm - tolerance)
                        break;

                    end_index++;
                }

                int range = end_index - start_index + 1;

                return tranflection.GetRange(start_index, range);
            }

            private static double GetRMS(DataSet data1, DataSet data2)
            {
                DataSet diff = data1 - data2;

                double sum = 0;
                foreach (double y in diff.Y)
                    sum += y * y;

                return System.Math.Sqrt(sum / diff.Count);
            }
        }

        #endregion

        public TransflectanceInsertCheckResult CheckTransflectanceInsert(Spectrum transflectance_reference_spectrum,
                                                                         Spectrum ext_white_spectrum,
                                                                         DarkIntensity dark_intensity,
                                                                         bool throw_exception = true)
        {
            return this.CheckTransflectanceInsert(transflectance_reference_spectrum,
                                                  ext_white_spectrum,
                                                  dark_intensity,
                                                  ReferenceChecks.TRANSFLECTANCE_INSERT_REFLECTANCE_MAX,
                                                  ReferenceChecks.TRANSFLECTANCE_INSERT_REFLECTANCE_MIN,
                                                  ReferenceChecks.TRANSFLECTANCE_INSERT_RMS_1_MAX,
                                                  ReferenceChecks.TRANSFLECTANCE_INSERT_RMS_4_MAX,
                                                  ReferenceChecks.TRANSFLECTANCE_INSERT_SLOPE_MAX, 
                                                  throw_exception);
        }

        public TransflectanceInsertCheckResult CheckTransflectanceInsert(Spectrum transflectance_reference_spectrum, 
                                                                         Spectrum ext_white_spectrum, 
                                                                         DarkIntensity dark_intensity, 
                                                                         double reflectance_max,
                                                                         double reflectance_min,
                                                                         double rms_1_max,
                                                                         double rms_4_max,
                                                                         double slope_max,
                                                                         bool throw_exception = true)
        {
            TransflectanceInsertCheckResult result = new TransflectanceInsertCheckResult(true, 
                                                                                         transflectance_reference_spectrum, 
                                                                                         null,
                                                                                         this.SpectrumProcessor.InstrumentReferenceSystem, 
                                                                                         null,
                                                                                         double.NaN, 
                                                                                         double.NaN,
                                                                                         double.NaN, 
                                                                                         double.NaN,
                                                                                         double.NaN, 
                                                                                         double.NaN,
                                                                                         double.NaN,
                                                                                         double.NaN, 
                                                                                         double.NaN);

            if (this.SpectrumProcessor.ExternalWhiteReferenceType != ExternalWhiteReferenceType.Zenith)
                return result;

            if (this.PlausibilityChecks == false)
                return result;

            TransflectanceInsertChecker transflectance_check = new TransflectanceInsertChecker(reflectance_max, 
                                                                                               reflectance_min, 
                                                                                               rms_1_max, 
                                                                                               rms_4_max, 
                                                                                               slope_max);

            bool success = transflectance_check.Check(transflectance_reference_spectrum, ext_white_spectrum, dark_intensity);
            Exception ex = null;

            if (success == false)
            {
                this.SaveReferenceMethod?.Invoke(transflectance_reference_spectrum, SpectrumType.ExternalFluidWhite, true, string.Empty);
                ex = new InvalidTransflectReferenceException(transflectance_check.LastExceptionMessage);

                if (throw_exception == true)
                    throw ex;
            }

            return new TransflectanceInsertCheckResult(success, 
                                                       transflectance_reference_spectrum,
                                                       ex,
                                                       this.SpectrumProcessor.InstrumentReferenceSystem,
                                                       transflectance_check.LastReflectanceDataSet,
                                                       transflectance_check.LastReflectanceMean,
                                                       reflectance_max,
                                                       reflectance_min,
                                                       transflectance_check.LastRms1,
                                                       rms_1_max,
                                                       transflectance_check.LastRms4,
                                                       rms_4_max,
                                                       transflectance_check.LastSlope,
                                                       slope_max);
        }
    }
}
