! =============================================================================
!> Parent module for everything solver-related. Interfaces to the different
!! submodules are defined here, and the <tt>solve_evp</tt> routine calls the
!! correct solver based on parfile settings.
module mod_solvers
  use mod_global_variables, only: dp, NaN
  use mod_logging, only: logger, str
  use mod_check_values, only: set_small_values_to_zero
  use mod_matrix_structure, only: matrix_t
  use mod_transform_matrix, only: matrix_to_array
  use mod_settings, only: settings_t
  implicit none

  private

  !> interface to the different solution methods implemented in submodules
  interface
    module subroutine qr_invert(matrix_A, matrix_B, settings, omega, vr)
      !> matrix A
      type(matrix_t), intent(in) :: matrix_A
      !> matrix B
      type(matrix_t), intent(in) :: matrix_B
      !> settings object
      type(settings_t), intent(in) :: settings
      !> array with eigenvalues
      complex(dp), intent(out)  :: omega(:)
      !> array with right eigenvectors
      complex(dp), intent(out)  :: vr(:, :)
    end subroutine

    module subroutine qr_cholesky(matrix_A, matrix_B, settings, omega, vr)
      !> matrix A
      type(matrix_t), intent(in) :: matrix_A
      !> matrix B
      type(matrix_t), intent(in) :: matrix_B
      !> settings object
      type(settings_t), intent(in) :: settings
      !> array with eigenvalues
      complex(dp), intent(out)  :: omega(:)
      !> array with right eigenvectors
      complex(dp), intent(out)  :: vr(:, :)
    end subroutine qr_cholesky

    module subroutine qz_direct(matrix_A, matrix_B, settings, omega, vr)
      !> matrix A
      type(matrix_t), intent(in) :: matrix_A
      !> matrix B
      type(matrix_t), intent(in) :: matrix_B
      !> settings object
      type(settings_t), intent(in) :: settings
      !> array with eigenvalues
      complex(dp), intent(out)  :: omega(:)
      !> array with right eigenvectors
      complex(dp), intent(out)  :: vr(:, :)
    end subroutine qz_direct

    module subroutine arnoldi(matrix_A, matrix_B, settings, omega, vr)
      !> matrix A
      type(matrix_t), intent(in) :: matrix_A
      !> matrix B
      type(matrix_t), intent(in) :: matrix_B
      !> settings object
      type(settings_t), intent(inout) :: settings
      !> array with eigenvalues
      complex(dp), intent(out)  :: omega(:)
      !> array with right eigenvectors
      complex(dp), intent(out)  :: vr(:, :)
    end subroutine arnoldi

    module subroutine inverse_iteration(matrix_A, matrix_B, settings, omega, vr)
      !> matrix A
      type(matrix_t), intent(in) :: matrix_A
      !> matrix B
      type(matrix_t), intent(in) :: matrix_B
      !> settings object
      type(settings_t), intent(in) :: settings
      !> array with eigenvalues
      complex(dp), intent(out)  :: omega(:)
      !> array with right eigenvectors
      complex(dp), intent(out)  :: vr(:, :)
    end subroutine inverse_iteration
  end interface

  public  :: solve_evp

contains


  !> Main subroutine to solve the eigenvalue problem. Depending on the solvelist
  !! passed in the parfile, different solvers are called.
  !! @warning Throws an error if an unknown solver is passed. @endwarning
  subroutine solve_evp(matrix_A, matrix_B, settings, omega, vr)
    !> A-matrix
    type(matrix_t), intent(in) :: matrix_A
    !> B-matrix
    type(matrix_t), intent(in) :: matrix_B
    !> settings object
    type(settings_t), intent(inout) :: settings
    !> eigenvalues
    complex(dp), intent(out)  :: omega(:)
    !> right eigenvectors
    complex(dp), intent(out)  :: vr(:, :)

    select case(settings%solvers%get_solver())
    case("QR-invert")
      call qr_invert(matrix_A, matrix_B, settings, omega, vr)
    case("QR-cholesky")
      call qr_cholesky(matrix_A, matrix_B, settings, omega, vr)
    case("QZ-direct")
      call qz_direct(matrix_A, matrix_B, settings, omega, vr)
    case("arnoldi")
      call arnoldi(matrix_A, matrix_B, settings, omega, vr)
    case("inverse-iteration")
      call inverse_iteration(matrix_A, matrix_B, settings, omega, vr)
    case("none")
      ! Set eigenvalues and vectors to NaN.
      omega = NaN * (1, 1)
      if (settings%io%should_compute_eigenvectors()) vr = NaN * (1, 1)
    case default
      call logger%error("unknown solver passed: " // settings%solvers%get_solver())
      return
    end select
  end subroutine solve_evp

end module mod_solvers
