// 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
{
    /// <summary>
    /// Transformation is defined by x* = b + F*x
    /// </summary>
    public partial class LinearTransformation : ICloneable
    {
        private bool? is_neutral = null;

        public LinearTransformation(Vector b, Matrix f, WavelengthLimits wavelength_limits = null)
        {
            if (f.ColumnCount != f.RowCount)
                throw new Exception(Catalog.GetString("Matrix F is not quadratic."));

            if (f.ColumnCount != b.Count)
                throw new Exception(Catalog.GetString("Vector b does not have the same order as matrix F."));

            this.B = b;
            this.F = f;

            this.WavelengthLimits = wavelength_limits;
        }

        public LinearTransformation(Matrix f, WavelengthLimits wavelength_limits = null)
            : this(new SparseVector(f.ColumnCount), f, wavelength_limits)
        {
        }

        public LinearTransformation(double[,] quadratic_f, WavelengthLimits wavelength_limits = null)
            : this(LinearTransformation.ConvertFromArrayToMatrix(quadratic_f), wavelength_limits)
        {
        }

        public LinearTransformation(double[][] diagonal_f, WavelengthLimits wavelength_limits = null)
            : this(LinearTransformation.ConvertFromDiagonalArrayToMatrix(diagonal_f), wavelength_limits)
        {
        }

        public LinearTransformation(double[] b, double[,] quadratic_f, WavelengthLimits wavelength_limits = null)
            : this(LinearTransformation.ConvertFromArraytoVector(b), LinearTransformation.ConvertFromArrayToMatrix(quadratic_f), wavelength_limits)
        {
        }

        public LinearTransformation(double[] b, double[][] diagonal_f, WavelengthLimits wavelength_limits = null)
            : this(LinearTransformation.ConvertFromArraytoVector(b), LinearTransformation.ConvertFromDiagonalArrayToMatrix(diagonal_f), wavelength_limits)
        {
        }

        public override bool Equals(object obj)
        {
            if ((obj is LinearTransformation) == false)
                return false;

            LinearTransformation linear_transformation = (LinearTransformation)obj;

            return this.Order == linear_transformation.Order &&
                       (this.B - linear_transformation.B).ForAll(x => System.Math.Abs(x) < double.Epsilon) &&
                       (this.F - linear_transformation.F).ForAll(x => System.Math.Abs(x) < double.Epsilon);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public object Clone()
        {
            return new LinearTransformation((Vector)this.B.Clone(), (Matrix)this.F.Clone());
        }

        public LinearTransformation CreateCopy()
        {
            return (LinearTransformation)this.Clone();
        }

        /// <summary>
        /// Gets the Matrix F of x* = b + F*x
        /// </summary>
        /// <value>The f.</value>
        public Matrix F { get; private set; }

        /// <summary>
        /// Gets the Vector B of x* = b + F*x
        /// </summary>
        /// <value>The b.</value>
        public Vector B { get; private set; }

        public int Order
        {
            get { return this.B.Count; }
        }

        public bool IsNeutral
        {
            get 
            {
                if (this.is_neutral == null)
                    this.is_neutral = this.Equals(LinearTransformation.GetNeutralLinearTransformation(this.Order));

                return (bool)this.is_neutral;
            }
        }

        /// <summary>
        /// Gets or sets the wavelength limits for transforming spectra and datasets.
        /// </summary>
        /// <value>The wavelength limits.</value>
        public WavelengthLimits WavelengthLimits { get; set; } = null;

        public bool HasWavelengthLimits
        {
            get { return this.WavelengthLimits != null; }
        }
    }
}
