// Created with MonoDevelop
//
//    Copyright (C) 2009 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 System.Globalization;
using System.Linq;

using Hiperscan.Spectroscopy.Math;
using Hiperscan.Unix;


namespace Hiperscan.Spectroscopy
{

    public partial class Spectrum
    {

        public static Spectrum GetMeanSpectrum(IEnumerable<Spectrum> spectra)
        {
            if (spectra.Select(s => s.Serial).Distinct().Count() > 1)
                throw new Exception(Catalog.GetString("Cannot calculate mean spectrum of spectra originated from different instruments."));

            if (spectra.Select(s => s.HasAbsorbance).Distinct().Count() > 1)
                throw new Exception(Catalog.GetString("Cannot calculate mean spectrum of mixed intensity and absorbance spectra."));

            if (spectra.Select(s => s.Reference.Timestamp).Distinct().Count() > 1)
                throw new Exception(Catalog.GetString("Cannot calculate mean spectrum of spectra with different White Reference spectra."));

            if (spectra.Select(s => s.DarkIntensity.Timestamp).Distinct().Count() > 1)
                throw new Exception(Catalog.GetString("Cannot calculate mean spectrum of spectra with different Dark Intensities."));

            if (spectra.Select(s => s.WavelengthCorrection?.Timestamp).Distinct().Count() > 1)
                throw new Exception(Catalog.GetString("Cannot calculate mean spectrum of spectra with different Wavelength Corrections."));

            DataSet mean_rawintensity = new DataSet(spectra.First().RawIntensity);
            foreach (Spectrum spectrum in spectra.Skip(1))
            {
                mean_rawintensity += spectrum.RawIntensity.Interpolate(mean_rawintensity.X);
            }
            mean_rawintensity /= (double)spectra.Count();

            Spectrum mean_spectrum = spectra.First().Clone();
            mean_spectrum.RawIntensity = mean_rawintensity;

            return mean_spectrum;
        }

        public Spectrum FirLowpassFilter(double[] coeffs)
        {
            Spectrum spectrum = new Spectrum(this);

            if (!this.HasReference || !this.HasDarkIntensity)
            {
                spectrum.RawIntensity = this.RawIntensity.FirLowpassFilter(coeffs);
                return spectrum;
            }
            else
            {
                Spectrum bg = this.Reference.Clone();
                bg.RawIntensity = bg.RawIntensity.FirLowpassFilter(coeffs);
                spectrum.Reference = bg;
                spectrum.RawIntensity = this.RawIntensity.FirLowpassFilter(coeffs);
                return spectrum;
            }
        }

        private DataSet InterpolateWavelengths(DataSet intensity, List<double> wavelengths)
        {
            List<double> x = new List<double>(intensity.X);
            List<double> y = new List<double>(intensity.Y);

            x.Insert(0, wavelengths[0] - 200.0);
            x.Insert(0, wavelengths[0] - 400.0);
            x.Add(wavelengths[wavelengths.Count - 1] + 200.0);
            x.Add(wavelengths[wavelengths.Count - 1] + 400.0);

            y.Insert(0, y[0]);
            y.Insert(0, y[0]);
            y.Add(y[y.Count - 1]);
            y.Add(y[y.Count - 1]);

            DataSet ds = new DataSet(x, y);

            return ds.Interpolate(wavelengths, InterpolationType.NaturalCubic);
        }

        public static LinearDistortion MatchIntensities(Spectrum ref_spectrum, Spectrum new_spectrum, double min, double max)
        {
            List<Spectrum> ref_spectra = new List<Spectrum>();
            List<Spectrum> new_spectra = new List<Spectrum>();

            ref_spectra.Add(ref_spectrum);
            new_spectra.Add(new_spectrum);

            return Spectrum.MatchIntensities(ref_spectra, new_spectra, min, max);
        }

        public static LinearDistortion MatchIntensities(List<Spectrum> ref_spectra, List<Spectrum> fit_spectra, double min, double max)
        {
            if (ref_spectra.Count != fit_spectra.Count)
                throw new ArgumentException(Catalog.GetString("Dimension of reference spectra and new spectra must not be different."));

            DataSet ref_intensity = null;
            DataSet fit_intensity = null;

            for (int ix = 0; ix < ref_spectra.Count; ++ix)
            {
                Spectrum ref_spectrum = new Spectrum(ref_spectra[ix]);
                ref_spectrum.ApplyCorrectionsToRawData(CorrectionMask.All & ~CorrectionMask.DarkIntensity);

                Spectrum fit_spectrum = new Spectrum(fit_spectra[ix]);
                fit_spectrum.ApplyCorrectionsToRawData(CorrectionMask.All & ~CorrectionMask.DarkIntensity);

                DataSet ref_ds = ref_spectrum.RawIntensity;
                DataSet fit_ds = fit_spectrum.RawIntensity.Interpolate(ref_spectrum.RawIntensity.X);

                List<int> indices = ref_ds.Find('X', '<', min);
                ref_ds.RemoveAt(indices);
                fit_ds.RemoveAt(indices);
                indices = ref_ds.Find('X', '>', max);
                ref_ds.RemoveAt(indices);
                fit_ds.RemoveAt(indices);

                if (ref_intensity == null)
                    ref_intensity = ref_ds;
                else
                    ref_intensity.AddRange(ref_ds);

                if (fit_intensity == null)
                    fit_intensity = fit_ds;
                else
                    fit_intensity.AddRange(fit_ds);
            }

            double[] p = LevenbergMarquardt.Fit(new LevenbergMarquardt.MatchLinearFit(ref_intensity, fit_intensity, 1.0)).Coefficients;

            return new LinearDistortion(p[0], p[1]);
        }

        private DataSet GetCorrectedRawIntensity(CorrectionMask mask, bool apply_filter)
        {
            if (this.HasAdcCorrection            == false &&
                this.HasSystemFunctionCorrection == false &&
                this.LimitBandwidth              == false &&
                this.HasWavelengthCorrection     == false &&
                this.HasWavelengthLimits         == false &&
                this.HasLinearDistortion         == false)
            {
                // no correction available
                return this.RawIntensity;
            }

            DataSet ds = new DataSet(this.RawIntensity);

            if (this.HasAdcCorrection && (mask & CorrectionMask.Adc) != CorrectionMask.None)
                ds -= this.adc_correction.Interpolate(ds.Y, InterpolationType.NaturalCubic);

            if (this.HasSystemFunctionCorrection && (mask & CorrectionMask.SystemFunction) != CorrectionMask.None)
                ds = ds.FirLowpassFilter(this.system_function_correction);

            if (apply_filter)
                ds = ds.FirLowpassFilter(DataSet.Get15nmFirLowpassCoefficients());

            if (this.HasWavelengthCorrection && 
                this.WavelengthCorrection.IsEmpty == false &&
                (mask & CorrectionMask.WavelengthCorrection) != CorrectionMask.None)
            {
                ds = this.WavelengthCorrection.CorrectedSpectrum(ds);
            }

            if (this.HasWavelengthLimits && (mask & CorrectionMask.WavelengthLimits) != CorrectionMask.None)
                ds = this.WavelengthLimits.CorrectedSpectrum(ds);

            if (this.HasLinearDistortion && (mask & CorrectionMask.LinearDistortion) != CorrectionMask.None)
            {
                double a = this.LinearDistortion.Offset;
                double b = this.LinearDistortion.Factor;
                if (a.Equals(0.0) == false || b.Equals(1.0) == false)
                {
                    DataSet.Operand offset = new DataSet.Operand(0.0, a);
                    DataSet.Operand factor = new DataSet.Operand(1.0, b);
                    ds = (ds * factor) + offset;
                }
            }

            return ds;
        }

        public DarkIntensity GetCorrectedDarkIntensity(CorrectionMask mask)
        {
            if ((this.HasAdcCorrection == false    || (mask & CorrectionMask.Adc) == CorrectionMask.None) &&
                (this.HasLinearDistortion == false || (mask & CorrectionMask.LinearDistortion) == CorrectionMask.None))
            {
                return this.DarkIntensity;
            }

            if (this.HasDarkIntensity == false)
                return this.DarkIntensity;

            double di = this.DarkIntensity.Intensity;

            if (this.HasAdcCorrection && (mask & CorrectionMask.Adc) != CorrectionMask.None)
            {
                DataSet correction = this.adc_correction.Interpolate(new double[] { di }, InterpolationType.NaturalCubic);
                di -= correction.Y[0];
            }

            double a = 0.0;
            double b = 1.0;
            if (this.HasLinearDistortion && (mask & CorrectionMask.LinearDistortion) != CorrectionMask.None)
            {
                a = this.LinearDistortion.Offset;
                b = this.LinearDistortion.Factor;
            }

            return new DarkIntensity(di * b + a, this.DarkIntensity.Sigma);
        }

        public static Spectrum GetMeanAbsorbance(IEnumerable<Spectrum> spectra)
        {
            if (spectra == null || spectra.Any() == false)
                throw new Exception("At least one spectrum is needed to calculate a mean spectrum.");

            return Spectrum.GetMeanAbsorbance(spectra.Select(s => s.Absorbance),
                                              spectra.First().WavelengthLimits,
                                              spectra.First().CultureInfo);
        }

        public static Spectrum GetMeanAbsorbance(IEnumerable<DataSet> data_sets, WavelengthLimits wavelength_limits, CultureInfo culture)
        {
            DataSet mean_ds = DataSet.Mean(data_sets);
            DataSet reference = new DataSet(mean_ds.X, 1.0);
            DataSet intensity = reference / DataSet.Pow(10.0, mean_ds);

            return new Spectrum(intensity, wavelength_limits)
            {
                Reference = new Spectrum(reference, wavelength_limits),
                DarkIntensity = new DarkIntensity(double.Epsilon, 0.0),
                CultureInfo = culture
            };
        }
    }
}
