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

using Hiperscan.Unix;

using MathNet.Numerics.LinearAlgebra.Double;
using MathNet.Numerics.LinearAlgebra.Factorization;

namespace Hiperscan.Spectroscopy.Math
{
    public partial class LinearTransformation
    {
        public static Matrix ConvertFromArrayToMatrix(double[,] array)
        {
            int rows = array.GetLength(0);
            int columns = array.GetLength(1);

            if (rows != columns)
                throw new Exception(Catalog.GetString("Cannot create symmetrical transformation matrix with different column and row count."));

            return SparseMatrix.OfArray(array);
        }

        public static Matrix ConvertFromDiagonalArrayToMatrix(double[][] array)
        {
            int rows = array.Length;

            if (rows % 2 == 0)
                throw new Exception("Connot convert from diagonal array to Matrix with even row count.");

            int center_index = rows / 2;
            int center_columns = array[center_index].Length;

            Matrix matrix = SparseMatrix.OfDiagonalArray(array[center_index]);

            for (int i=0; i< rows; i++)
            {
                if (i == center_index)
                    continue;

                int column_count = array[i].Length;
                int deviation_count = System.Math.Abs(i - center_index);

                if (center_columns - deviation_count != column_count)
                    throw new Exception(string.Format(
                        Catalog.GetString("Column count in row at position {0} ({1}) has to be {2} less than center row at position {3} ({4})."),
                            i, column_count, deviation_count, center_index, center_columns));

                if (i < center_index)
                {
                    for (int j=0; j < column_count; j++)
                        matrix[j, j + deviation_count] = array[i][j];
                }
                else if (i > center_index)
                {
                    for (int j=0; j < column_count; j++)
                        matrix[j + deviation_count, j] = array[i][j];
                }
            }

            return matrix;
        }

        public static Vector ConvertFromArraytoVector(double[] array)
        {
            return SparseVector.OfEnumerable(array);
        }

        public static Vector DataSetToVector(DataSet data_set)
        {
            return LinearTransformation.DataSetToVector(data_set, data_set.XMin(), data_set.XMax());
        }

        public static Vector DataSetToVector(DataSet data_set, double lower_bound, double upper_bound)
        {
            DataSet ds = data_set.GetRange(lower_bound, upper_bound);

            return SparseVector.OfEnumerable(ds.Y);
        }

        public static DataSet VectorToDataSet(Vector vector, IEnumerable<double> x_values)
        {
            return new DataSet(x_values, vector.AsEnumerable());
        }

        public Vector Transform(Vector x)
        {
            if (x.Count != this.Order)
                throw new Exception(Catalog.GetString("Input vector does not have the same order as the transformation function."));

            return (Vector)(this.B + x * this.F);
        }

        public IEnumerable<double> Transform(IEnumerable<double> values)
        {
            return this.Transform(SparseVector.OfEnumerable(values)).AsEnumerable();
        }

        public DataSet Transform(DataSet data_set, bool new_instance = true)
        {
            WavelengthLimits limits = null;

            if (this.HasWavelengthLimits)
                limits = this.WavelengthLimits;
            else
                limits = new WavelengthLimits(data_set.XMin(), data_set.XMax());
                
            return this.Transform(data_set, limits, new_instance);
        }

        public DataSet Transform(DataSet data_set, WavelengthLimits limits, bool new_instance = true)
        {
            return this.Transform(data_set, limits.Minimum, limits.Maximum, new_instance);
        }

        public DataSet Transform(DataSet data_set, double lower_bound, double upper_bound, bool new_instance = true)
        {
            DataSet ds = new_instance ? new DataSet(data_set) : data_set;

            Vector vector = this.Transform(LinearTransformation.DataSetToVector(ds, lower_bound, upper_bound));

            return LinearTransformation.VectorToDataSet(vector, ds.X.Where(x => x >= lower_bound && x <= upper_bound));
        }

        public Spectrum Transform(Spectrum spectrum, bool absorbance_transformation, bool new_instance = true)
        {
            WavelengthLimits limits = null;

            if (this.HasWavelengthLimits)
                limits = this.WavelengthLimits;
            else
                limits = new WavelengthLimits(spectrum.RawIntensity.XMin(), spectrum.RawIntensity.XMax());

            return this.Transform(spectrum, absorbance_transformation, limits, new_instance);
        }

        public Spectrum Transform(Spectrum spectrum, bool absorbance_transformation, WavelengthLimits limits, bool new_instance = true)
        {
            return this.Transform(spectrum, absorbance_transformation, limits.Minimum, limits.Maximum, new_instance);
        }

        public Spectrum Transform(Spectrum spectrum, bool absorbance_transformation, double lower_bound, double upper_bound, bool new_instance = true)
        {
            Spectrum __spectrum = new_instance ? spectrum.Clone() : spectrum;

            if (absorbance_transformation)
            {
                if (__spectrum.HasAbsorbance == false)
                    throw new Exception("Cannot transform absorbance without reference spectrum.");

                if (__spectrum.HasDarkIntensity && __spectrum.DarkIntensity.Intensity > double.Epsilon)
                    throw new Exception("Cannot transform absorbance spectrum with dark intensity greater epsilon.");

                if (__spectrum.HasWavelengthCorrection && __spectrum.WavelengthCorrection.Corrections.Any(x => x > double.Epsilon))
                    throw new Exception("Cannot transform absorbance spectrum with wavelength corrections.");

                if (__spectrum.HasLinearDistortion 
                    && System.Math.Abs(__spectrum.LinearDistortion.Factor - 1.0) > double.Epsilon 
                    && __spectrum.LinearDistortion.Offset > double.Epsilon)
                    throw new Exception("Cannot transform absorbance spectrum with linear distortion.");

                DataSet __absorbance = this.Transform(__spectrum.Absorbance, lower_bound, upper_bound, true);
                DataSet __factor = __absorbance / __spectrum.Absorbance.GetRange(lower_bound, upper_bound);

                DataSet __intensity  = DataSet.Log(__spectrum.Intensity.GetRange(lower_bound, upper_bound)) * __factor;
                DataSet __background = DataSet.Log(__spectrum.Reference.Intensity.GetRange(lower_bound, upper_bound)) * __factor;

                __spectrum.RawIntensity = DataSet.Pow(10.0, __intensity);
                __spectrum.Reference.RawIntensity = DataSet.Pow(10.0, __background);
            }
            else
            { 
                __spectrum.RawIntensity = this.Transform(__spectrum.RawIntensity, lower_bound, upper_bound, false);
                if (spectrum.HasReference)
                {
                    Spectrum reference = __spectrum.Reference.Clone();;
                    reference.RawIntensity = reference.RawIntensity.GetRange(lower_bound, upper_bound);
                    reference.RawIntensity = this.Transform(reference.RawIntensity, lower_bound, upper_bound, false);
                    __spectrum.Reference = reference;
                }
            }

            return __spectrum;
        }

        public static Vector GetVector(int order, double value)
        {
            return SparseVector.OfEnumerable((new double[order]).Select(x => value));
        }

        public static Matrix GetMatrix(int order, double diagonal_value)
        {
            return SparseMatrix.OfDiagonalVector(LinearTransformation.GetVector(order, diagonal_value));
        }

        public static Vector GetNeutralVector(int order)
        {
            return new SparseVector(order);
        }

        public static Matrix GetNeutralMatrix(int order)
        {
            return LinearTransformation.GetMatrix(order, 1.0);
        }

        public static LinearTransformation GetNeutralLinearTransformation(int order)
        {
            return new LinearTransformation(LinearTransformation.GetNeutralMatrix(order));
        }

        public LinearTransformation Trim(int start_index, int count)
        {
            return new LinearTransformation((Vector)this.B.SubVector(start_index, count), 
                                            (Matrix)this.F.SubMatrix(start_index, count, start_index, count),
                                            this.WavelengthLimits);
        }

        public LinearTransformation TrimSides(int left_count, int right_count)
        {
            return this.Trim(left_count, this.Order - left_count - right_count);
        }

        public LinearTransformation TrimSides(int both_count)
        {
            return this.TrimSides(both_count, both_count);
        }
    }
}
