/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef itkVariableSizeMatrix_h
#define itkVariableSizeMatrix_h

#include "itkPoint.h"
#include "itkCovariantVector.h"
#include "vnl/vnl_matrix_fixed.h"
#include "vnl/algo/vnl_matrix_inverse.h"
#include "vnl/vnl_transpose.h"
#include "vnl/vnl_matrix.h"
#include "itkArray.h"
#include "itkMath.h"

namespace itk
{
/**
 * \class VariableSizeMatrix
 * \brief A templated class holding a M x N size Matrix.
 *
 * This class contains a vnl_matrix in order
 * to make all the vnl mathematical methods available. This class is meant to be
 * used when the matrix length cannot be determined at compile time.
 *
 * \ingroup DataRepresentation
 * \ingroup ITKCommon
 */

template <typename T>
class ITK_TEMPLATE_EXPORT VariableSizeMatrix
{
public:
  /** Standard class type aliases. */
  using Self = VariableSizeMatrix;

  /** Component value type */
  using ValueType = T;
  using ComponentType = T;

  /** Internal matrix type */
  using InternalMatrixType = vnl_matrix<T>;

  /** Matrix by Vector multiplication.  */
  Array<T>
  operator*(const Array<T> & vect) const;

  /** Matrix by Matrix multiplication.  */
  Self
  operator*(const Self & matrix) const;

  /** Matrix addition.  */
  Self
  operator+(const Self & matrix) const;

  const Self &
  operator+=(const Self & matrix);

  /** Matrix addition.  */
  Self
  operator-(const Self & matrix) const;

  const Self &
  operator-=(const Self & matrix);

  /** negation operator */
  Self &
  operator-();

  /** Matrix by vnl_matrix multiplication.  */
  vnl_matrix<T>
  operator*(const vnl_matrix<T> & matrix) const;

  /** Matrix by Matrix multiplication.  */
  void
  operator*=(const Self & matrix);

  /** Matrix by vnl_matrix multiplication.  */
  void
  operator*=(const vnl_matrix<T> & matrix);

  /** Matrix by vnl_vector multiplication.  */
  vnl_vector<T>
  operator*(const vnl_vector<T> & vc) const;

  /** Matrix by scalar multiplication.  */
  void
  operator*=(const T & value)
  {
    m_Matrix *= value;
  }

  /** Matrix by scalar multiplication.  */
  Self
  operator*(const T & value) const
  {
    Self result(*this);

    result *= value;
    return result;
  }

  /** Matrix by scalar division.  */
  void
  operator/=(const T & value)
  {
    m_Matrix /= value;
  }

  /** Matrix by scalar division.  */
  Self
  operator/(const T & value) const
  {
    Self result(*this);

    result /= value;
    return result;
  }

  /** Return an element of the matrix. */
  inline T &
  operator()(unsigned int row, unsigned int col)
  {
    return m_Matrix(row, col);
  }

  /** Return an element of the matrix. */
  inline const T &
  operator()(unsigned int row, unsigned int col) const
  {
    return m_Matrix(row, col);
  }

  /** Return a row of the matrix. */
  inline T *
  operator[](unsigned int i)
  {
    return m_Matrix[i];
  }

  /** Return a row of the matrix. */
  inline const T *
  operator[](unsigned int i) const
  {
    return m_Matrix[i];
  }

  /** Return the matrix. */
  inline InternalMatrixType &
  GetVnlMatrix()
  {
    return m_Matrix;
  }

  /** Return the matrix. */
  [[nodiscard]] inline const InternalMatrixType &
  GetVnlMatrix() const
  {
    return m_Matrix;
  }

  /** Set the matrix to identity. */
  inline void
  SetIdentity()
  {
    m_Matrix.set_identity();
  }

  /** Fill the matrix with a value. */
  inline void
  Fill(const T & value)
  {
    m_Matrix.fill(value);
  }

  /** Assignment operator. */
  inline Self &
  operator=(const vnl_matrix<T> & matrix)
  {
    m_Matrix = matrix;
    return *this;
  }

  /** Comparison operators. */
  inline bool
  operator==(const Self & matrix) const;

  ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(Self);

  /** Assignment operator. */
  inline Self &
  operator=(const Self & matrix) = default;

  /** Return the inverse matrix. */
  [[nodiscard]] inline vnl_matrix<T>
  GetInverse() const
  {
    return vnl_matrix_inverse<T>(m_Matrix).as_matrix();
  }

  /** Return the transposed matrix. */
  [[nodiscard]] inline vnl_matrix<T>
  GetTranspose() const
  {
    return m_Matrix.transpose();
  }

  /** Default constructor. */
  VariableSizeMatrix()
    : m_Matrix()
  {}

  VariableSizeMatrix(unsigned int rows, unsigned int cols);

  /** Copy constructor. */
  VariableSizeMatrix(const Self & matrix)
    : m_Matrix(matrix.m_Matrix)
  {}

  /** Return number of rows in the matrix */
  [[nodiscard]] inline unsigned int
  Rows() const
  {
    return m_Matrix.rows();
  }

  /** Return number of columns in the matrix */
  [[nodiscard]] inline unsigned int
  Cols() const
  {
    return m_Matrix.cols();
  }

  /** Set the matrix size. Old data lost. Returns true if size changed. */
  inline bool
  SetSize(unsigned int r, unsigned int c)
  {
    return m_Matrix.set_size(r, c);
  }

private:
  InternalMatrixType m_Matrix;
};

template <typename T>
std::ostream &
operator<<(std::ostream & os, const VariableSizeMatrix<T> & v)
{
  os << v.GetVnlMatrix();
  return os;
}

/**
 *  Comparison
 */
template <typename T>
inline bool
VariableSizeMatrix<T>::operator==(const Self & matrix) const
{
  if ((matrix.Rows() != this->Rows()) || (matrix.Cols() != this->Cols()))
  {
    return false;
  }
  bool equal = true;

  for (unsigned int r = 0; r < this->Rows(); ++r)
  {
    for (unsigned int c = 0; c < this->Cols(); ++c)
    {
      if (Math::NotExactlyEquals(m_Matrix(r, c), matrix.m_Matrix(r, c)))
      {
        equal = false;
        break;
      }
    }
  }
  return equal;
}
} // end namespace itk

#ifndef ITK_MANUAL_INSTANTIATION
#  include "itkVariableSizeMatrix.hxx"
#endif

#endif
