! =============================================================================
!> Submodule containing the implementation of the QR-cholesky algorithm.
!! Using LAPACKS's <tt>zpbtrf</tt> and BLAS's <tt>zgbtrs</tt>, the original
!! problem is written as a standard eigenvalue problem as
!! $$ U^{-H}\mathcal{A}U^{-1}\textbf{Y} = \omega\textbf{Y}\ $$
!! where \(\mathcal{B} = U^HU\) is positive definite and
!! \(\textbf{Y}=U\textbf{X}\).
!! Eventually a call to LAPACK's <tt>zgeev</tt> routine is done to obtain
!! all eigenvalues and eigenvectors.
submodule (mod_solvers) smod_qr_cholesky
  use mod_banded_matrix_hermitian, only: hermitian_banded_matrix_t
  use mod_transform_matrix, only: matrix_to_hermitian_banded
  implicit none

contains

  !> Solves the eigenvalue problem by rewriting it to a standard form
  !! through splitting of the B-matrix.
  !! @warning Throws an error if <tt>matrix_A</tt> or <tt>matrix_B</tt>
  !!          is not a square matrix. @endwarning
  module procedure qr_cholesky
    !> banded \(U^HU\)
    type(hermitian_banded_matrix_t) :: UU
    !> matrix \(U^{-H}A^U{-1}\)
    complex(dp) :: UAU(matrix_A%matrix_dim, matrix_A%matrix_dim)
    !> order of matrix \(U^{-H}A^U{-1}\)
    integer     :: N
    !> leading dimension of matrix \(U^{-H}A^U{-1}\)
    integer     :: ldUAU
    !> calculate left eigenvectors if "V", omit if "N"
    character   :: jobvl
    !> leading dimension of vl
    integer     :: ldvl
    !> calculate right eigenvectors if "V", omit if "N"
    character   :: jobvr
    !> leading dimension of vr
    integer     :: ldvr
    !> dimension of work array
    integer     :: lwork
    !> work array
    complex(dp), allocatable  :: work(:)
    !> info parameter, 0 on successful exit
    integer     :: info
    !> second work array
    real(dp), allocatable     :: rwork(:)
    !> dummy for left eigenvectors, jobvl = "N" so this is never referenced
    complex(dp) :: vl(2, 2)
    !> number of subdiagonals
    integer :: kl
    !> number of superdiagonals
    integer :: ku

    integer :: i

    ! check input sanity
    if (.not. (matrix_A%matrix_dim == matrix_B%matrix_dim)) then
      call logger%error("A or B not square, or not compatible")
      return
    end if
    ! set array dimensions
    N = size(UAU, dim=1)
    ldUAU = N
    ldvl = N
    ldvr = N
    ! compute B = U^HU
    call logger%debug("computing B = U^HU")
    ! first convert B to banded
    call matrix_B%get_nb_diagonals(ku=ku, kl=kl)
    if (ku /= kl) then
      call logger%error( &
        "B has unequal sub/super diagonals: " // str(kl) // "/" // str(ku) &
      )
      return
    end if
    ! NOTE: We need a banded hermitian matrix with uplo = "U" to get U^HU!
    call matrix_to_hermitian_banded(matrix=matrix_B, diags=ku, uplo="U", banded=UU)
    call zpbtrf("U", UU%n, UU%kd, UU%AB, UU%kd+1, info)
    if (info /= 0) then ! LCOV_EXCL_START
      call logger%error("zpbtrf failed: B is not positive definite")
      return
    end if ! LCOV_EXCL_STOP
    ! compute U^{-H}AU^{-1}
    call logger%debug("computing B = U^{-H}AU^{-1}")
    call matrix_to_array(matrix=matrix_A, array=UAU)
    ! first U^{-H}A by solving U^HX = A
    do i = 1, N
      call ztbsv("U", "C", "N", N, UU%kd, UU%AB, UU%kd+1, UAU(1, i), 1)
    end do
    ! second U^{-H}AU^{-1} by solving XU = U^{-H}A
    do i = 1, N
      call ztbsv("U", "T", "N", N, UU%kd, UU%AB, UU%kd+1, UAU(i, 1), N)
    end do
    ! calculate eigenvectors, we don't use the left ones
    jobvl = "N"
    jobvr = "N"
    if (settings%io%should_compute_eigenvectors()) jobvr = "V"
    ! allocate rwork array
    allocate(rwork(2 * N))
    ! get lwork
    allocate(work(1))
    call zgeev( &
      jobvl, jobvr, N, UAU, ldUAU, omega, vl, ldvl, vr, ldvr, work, -1, rwork, info &
    )
    lwork = int(work(1))
    deallocate(work)
    ! allocate work array
    allocate(work(lwork))


    ! solve eigenvalue problem
    call logger%debug("solving evp using QR algorithm zgeev (LAPACK)")
    call zgeev( &
      jobvl, jobvr, N, UAU, ldUAU, omega, vl, ldvl, vr, ldvr, work, lwork, rwork, info &
    )
    if (info /= 0) then ! LCOV_EXCL_START
      call logger%warning("LAPACK routine zgeev failed!")
      call logger%warning("value for the info parameter: " // str(info))
    end if ! LCOV_EXCL_STOP

    ! tear down work arrays
    deallocate(work)
    deallocate(rwork)

    ! convert from Y to X by solving UX = Y
    if (settings%io%should_compute_eigenvectors()) then
      call logger%debug("computing evs as X = U^{-1}Y")
      do i = 1, N
        call ztbsv("U", "N", "N", N, UU%kd, UU%AB, UU%kd+1, vr(1, i), 1)
      end do
    end if

    ! tear down U^HU
    call UU%destroy()
  end procedure qr_cholesky

end submodule smod_qr_cholesky
