/*=========================================================================
 *
 *  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 itkFEMItpackSparseMatrix_h
#define itkFEMItpackSparseMatrix_h

#include "itkFEMException.h"
#include "ITKFEMExport.h"

namespace itk::fem
{
/**
 * \class ItpackSparseMatrix
 * \brief a compressed row sparse matrix representation that makes
 *        use of itpack to dynamically assemble the matrix
 * \sa ItpackLinearSystemWrapper
 * \ingroup ITKFEM
 */

// Forward declaration of friend class
class ItpackLinearSystemWrapper;

class ITKFEM_EXPORT ItpackSparseMatrix
{
public:
  /** type alias from f2c.h  */
  using integer = long;
  using doublereal = double;

  /** Constructor */
  ItpackSparseMatrix();

  /**
   * Constructor with single parameter
   * \param order the order of the matrix to be created
   */
  ItpackSparseMatrix(integer order);

  /**
   * Constructor with two parameters
   * \param order the order of the matrix to be created
   * \param maxNonZeroValues the maximum number of non-zero values that may
   *        appear in the matrix
   */
  ItpackSparseMatrix(integer order, integer maxNonZeroValues);

  /**
   * Destructor
   */
  ~ItpackSparseMatrix();

  /**
   * Set the order of the matrix
   * \param order the order of the matrix
   * \note the order must be set before any values are entered
   */
  void
  SetOrder(integer order)
  {
    m_N = order;
  }

  /**
   * Set the maximum number of non-zero values that may appear in the matrix
   * \param maxNonZeroValues maximum number of non-zero values that may appear in matrix
   * \note the maxNonZeroValues must be set before any values are entered
   */
  void
  SetMaxNonZeroValues(integer maxNonZeroValues)
  {
    m_NZ = maxNonZeroValues;
  }

  /**
   * Insert a value into the matrix
   * \param i row index
   * \param j column index
   * \param value value to be added at (i,j)
   */
  void
  Set(integer i, integer j, doublereal value);

  /**
   * Add to existing entry of matrix
   * \param i row index
   * \param j column index
   * \param value value to add to current value at (i,j)
   */
  void
  Add(integer i, integer j, doublereal value);

  /** Get a value from the matrix
   * \param i row index
   * \param j column index
   */
  doublereal
  Get(integer i, integer j);

  /**
   * Get the order of the matrix (via "itpack-like" naming scheme)
   */
  integer *
  GetN()
  {
    return &m_N;
  }

  /**
   * Get the row indices of the matrix (via "itpack-like" naming scheme)
   */
  integer *
  GetIA();

  /**
   * Pass pointers to compressed row format arrays
   * \param ia row indices
   * \param ja column indices
   * \param a matrix values
   */
  void
  SetCompressedRow(integer * ia, integer * ja, doublereal * a);

  /**
   * Get the column indices of the matrix (via "itpack-like" naming scheme)
   */
  integer *
  GetJA();

  /**
   * Get the values of the matrix (via "itpack-like" naming scheme)
   */
  doublereal *
  GetA();

  /**
   * Get the values of the matrix
   */
  doublereal *
  GetValueArray()
  {
    return GetA();
  }

  /**
   * Get the column indices
   */
  integer *
  GetColumnArray()
  {
    return GetJA();
  }

  /**
   * Get the row indices
   */
  integer *
  GetRowArray()
  {
    return GetIA();
  }

  /**
   * Get the order of the matrix
   */
  [[nodiscard]] integer
  GetOrder() const
  {
    return m_N;
  }

  /**
   * Get the maximum number of non-zero values allowed in the matrix
   */
  [[nodiscard]] integer
  GetMaxNonZeroValues() const
  {
    return m_NZ;
  }

  /**
   * Clear the memory
   */
  void
  Clear();

  /**
   * Multiply the matrix by a vector
   */
  void
  mult(doublereal * vector, doublereal * result);

  /**
   * Multiply the matrix by another ItpackSparseMatrix
   */
  void
  mult(ItpackSparseMatrix * rightMatrix, ItpackSparseMatrix * resultMatrix);

  /** output compressed row vectors: IA, JA, A */
  void
  PrintCompressedRow();

private:
  /** friend class */
  friend class LinearSystemWrapperItpack;

  /** initialize matrix */
  void
  Initialize();

  /** unfinalize matrix */
  void
  UnFinalize();

  /** finalize matrix form */
  void
  Finalize();

  /** flag indicating whether the matrix representation has been finalized */
  integer m_MatrixFinalized;

  /** flag indicating variables have been initialized */
  integer m_MatrixInitialized;

  /** Order of system */
  integer m_N;

  /** Maximum number of non-zero elements in master stiffness matrix */
  integer m_NZ;

  /** row pointers used in compressed row storage format */
  integer * m_IA;

  /** column indices used in compressed row storage format */
  integer * m_JA;

  /** nonzero entries in compressed row storage format */
  doublereal * m_A;

  /** integer workspace used in matrix building */
  integer * m_IWORK;

  /**
   * flag indicating mode of matrix building for repeat entries
   * m_MODE < 0 - current entry left as is
   *        = 0 - current entry reset
   *        > 0 - value is added to current entry
   */
  integer m_MODE;

  /** unit number that error messages are written to during matrix building */
  integer m_NOUT;

  /**
   * flag indicating desired level of error reporting during matrix building
   * m_LEVEL < 0 - no printing
   *         = 0 - fatal error messages written to m_NOUT
   *         > 0 - messages printed when repeat entries are encountered
   * \note must be set to less than 0
   */
  integer m_LEVEL;

  /**
   * error flag for matrix building
   * m_IER = 0   - no error
   *       = 700 - entry already set - reset according to m_MODE
   *       = 701 - Improper value for i or j
   *       = 701 - m_NZ is too small - no room for new entry
   */
  // integer m_IER;
};

/**
 * \class FEMExceptionItpackSparseMatrixSbagn
 * \brief handles errors that occur when unfinalizing the matrix
 * \sa ItpackSparseMatrix
 * \sa FEMException
 * \ingroup ITKFEM
 */
class ITK_ABI_EXPORT FEMExceptionItpackSparseMatrixSbagn : public FEMException
{
public:
  /** type alias from f2c.h  */
  using integer = long;
  using doublereal = double;

  /**
   * Constructor. In order to construct this exception object, five parameters
   * must be provided: file, lineNumber, location and a detailed description
   * of the exception, and the invalid index
   */
  FEMExceptionItpackSparseMatrixSbagn(std::string  file,
                                      unsigned int lineNumber,
                                      std::string  location,
                                      integer      errorCode);

  /** Virtual destructor needed for subclasses. Has to have empty throw(). */
  ~FEMExceptionItpackSparseMatrixSbagn() noexcept override;

  /** \see LightObject::GetNameOfClass() */
  itkOverrideGetNameOfClassMacro(FEMExceptionItpackSparseMatrixSbagn);
};

/**
 * \class FEMExceptionItpackSparseMatrixSbsij
 * \brief handles errors that occur when building the matrix
 * \sa ItpackSparseMatrix
 * \sa FEMException
 * \ingroup ITKFEM
 */
class ITK_ABI_EXPORT FEMExceptionItpackSparseMatrixSbsij : public FEMException
{
public:
  /** type alias from f2c.h  */
  using integer = long;
  using doublereal = double;

  /**
   * Constructor. In order to construct this exception object, five parameters
   * must be provided: file, lineNumber, location and a detailed description
   * of the exception, and the invalid index
   */
  FEMExceptionItpackSparseMatrixSbsij(std::string  file,
                                      unsigned int lineNumber,
                                      std::string  location,
                                      integer      errorCode);

  /** Virtual destructor needed for subclasses. Has to have empty throw(). */
  ~FEMExceptionItpackSparseMatrixSbsij() noexcept override;

  /** \see LightObject::GetNameOfClass() */
  itkOverrideGetNameOfClassMacro(FEMExceptionItpackSparseMatrixSbsij);
};
} // namespace itk::fem

#endif // itkFEMItpackSparseMatrix_h
