// Created with MonoDevelop
//
//    Copyright (C) 2017 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 MathNet.Numerics.LinearAlgebra.Double;


namespace Hiperscan.Spectroscopy.Math
{

    public partial class LevenbergMarquardt
    {

        public class JacobianMatrix : MathNet.Numerics.LinearAlgebra.Double.DenseMatrix
        {
            private readonly DataSet ds;
            private readonly double[] x;

            public delegate Vector FunctionVectorDelegateHandler(double[] coeff);

            readonly FunctionVectorDelegateHandler function_vector_delegate = null;


            public JacobianMatrix(DataSet ds,
                                  double[] x,
                                  FunctionVectorDelegateHandler function_vector_delegate)
                : base(ds.Count, x.Length)
            {
                this.ds = ds;
                this.x = x;
                this.function_vector_delegate = function_vector_delegate;
                this.ComputeMatrix();
            }

            private void ComputeMatrix()
            {
                double[] derivatives;
                for (int ix = 0; ix < this.x.Length; ++ix)
                {
                    derivatives = this.PartialDerivatives(ix);
                    for (int jx = 0; jx < this.ds.Count; ++jx)
                    {
                        this[jx, ix] = derivatives[jx];
                    }
                }
            }

            private double[] PartialDerivatives(int ix)
            {
                double[] x1 = new double[this.x.Length];
                double[] x2 = new double[this.x.Length];

                for (int jx = 0; jx < this.x.Length; ++jx)
                {
                    x1[jx] = x2[jx] = this.x[jx];
                }

                x1[ix] -= Step / 2;
                x2[ix] += Step / 2;

                double[] dy1 = this.function_vector_delegate(x1).ToArray();
                double[] dy2 = this.function_vector_delegate(x2).ToArray();

                double[] derivatives = new double[this.ds.Count];
                for (int jx = 0; jx < this.ds.Count; ++jx)
                {
                    derivatives[jx] = (dy2[jx] - dy1[jx]) / Step;
                }

                return derivatives;
            }

            public double Step { get; set; } = 1e-8;
        }
    }
}