module OpenACCClass
   !! Thic module defines some matrix-vector operations using OpenACC.
   !! But, not tested fully, so unrecommended.
   
   use iso_fortran_env
   implicit none
   !! No derived types are defined in this module.
   
contains

   subroutine acc_crs_matvec(CRS_value, CRS_col, CRS_row_ptr, old_vector, new_vector)
      real(real64), intent(in)  :: CRS_value(:), Old_vector(:)
      integeR(int32), intent(in):: CRS_col(:), CRS_row_ptr(:)

      real(real64), allocatable, intent(inout) :: new_vector(:)
      integer(int32) :: i, j, n, gid, lid, row, CRS_id, col
      !> x_i = A_ij b_j

      n = size(CRS_row_ptr) - 1
      if (size(old_vector) /= n) then
         print *, "ERROR crs_matvec :: inconsistent size for old_vector"
         return
      end if

      if (.not. allocated(new_vector)) then
         allocate (new_vector(n))
         new_vector(:) = 0.0d0
      end if

   ! !$acc kernels
      do row = 1, n
         do CRS_id = CRS_row_ptr(row), CRS_row_ptr(row + 1) - 1
            new_vector(row) = new_vector(row) + CRS_value(CRS_id)*old_vector(CRS_col(CRS_id))
         end do
      end do
   ! !$acc end kernels

   end subroutine
! ###################################################################

! #####################################################
   subroutine acc_bicgstab_CRS(a, ptr_i, index_j, x, b, itrmax, er, relative_er, debug)
      integer(int32), intent(inout) :: ptr_i(:), index_j(:), itrmax
      real(real64), intent(inout) :: a(:), b(:), er
      real(real64), optional, intent(in) :: relative_er
      real(real64), intent(inout) :: x(:)
      logical, optional, intent(in) :: debug
      logical :: speak = .false.
      integer(int32) :: itr, i, j, n
      real(real64)  :: alp, bet, c1, c2, c3, ev, vv, rr, er0, init_rr, re_er0
      real(real64), allocatable:: r(:), r0(:), p(:), y(:), e(:), v(:), ax(:)

      er0 = er
      if (present(debug)) then
         speak = debug
      end if

      n = size(b)
      if (speak) print *, "BiCGSTAB STARTED >> DOF:", n
      allocate (r(n), r0(n), p(n), y(n), e(n), v(n))

      r(:) = b(:)
      if (speak) print *, "BiCGSTAB >> [1] initialize"

      call acc_crs_matvec(CRS_value=a, CRS_col=index_j, &
                          CRS_row_ptr=ptr_i, old_vector=x, new_vector=ax)
      r = b - ax

      if (speak) print *, "BiCGSTAB >> [2] dp1"

      c1 = dot_product(r, r)

      init_rr = c1
      !if(speak) print *, "BiCGSTAB >>      |r|^2 = ",init_rr

      if (c1 < er0) return
      !endif

      p(:) = r(:)
      r0(:) = r(:)

      do itr = 1, itrmax
         if (speak) print *, "BiCGSTAB >> [", itr, "] initialize"

         c1 = dot_product(r0, r)

         y(:) = 0.0d0
         call acc_crs_matvec(CRS_value=a, CRS_col=index_j, &
                             CRS_row_ptr=ptr_i, old_vector=p, new_vector=y)

         c2 = dot_product(r0, y)

         alp = c1/c2

         e(:) = r(:) - alp*y(:)
         v(:) = 0.0d0
         call acc_crs_matvec(CRS_value=a, CRS_col=index_j, &
                             CRS_row_ptr=ptr_i, old_vector=e, new_vector=v)

         if (speak) print *, "BiCGSTAB >> [", itr, "] half"

         ev = dot_product(e, v)
         vv = dot_product(v, v)

         if (vv == 0.0d0) stop "Bicgstab devide by zero"

         c3 = ev/vv
         x(:) = x(:) + alp*p(:) + c3*e(:)
         r(:) = e(:) - c3*v(:)

         rr = dot_product(r, r)

         if (itr == 1) then
            re_er0 = rr
         end if

         if (speak) then
            print *, sqrt(rr)
         end if
         if (present(relative_er)) then
            if (sqrt(rr/re_er0) < relative_er) then
               exit
            end if
         end if

         if (sqrt(rr) < er0) exit

         c1 = dot_product(r0, r)

         bet = c1/(c2*c3)

         if ((c2*c3) == 0.0d0) stop "Bicgstab devide by zero"
         p(:) = r(:) + bet*(p(:) - c3*y(:))

      end do

   end subroutine
!===============================================================

!===============================================================
   subroutine acc_ref_bicgstab_real64(a, b, x, n, itrmax, er)
      integer(int32), intent(in) :: n, itrmax
      real(real64), intent(in) :: a(n, n), b(n), er
      real(real64), intent(inout) :: x(n)
      integer(int32) itr
      real(real64) alp, bet, c1, c2, c3, ev, vv, rr, er0, init_rr
      real(real64) r(n), r0(n), p(n), y(n), e(n), v(n)
      er0 = er

      r(:) = b - matmul(a, x)
      c1 = dot_product(r, r)
      init_rr = c1
      if (c1 < er0) return
      p(:) = r(:)
      r0(:) = r(:)
      do itr = 1, itrmax

         c1 = dot_product(r0, r)

         y(:) = matmul(a, p)

         c2 = dot_product(r0, y)

         alp = c1/c2

         e(:) = r(:) - alp*y(:)
         v(:) = matmul(a, e)

         ev = dot_product(e, v)
         vv = dot_product(v, v)
         if (vv == 0.0d0) stop "Bicgstab devide by zero"

         c3 = ev/vv

         x(:) = x(:) + alp*p(:) + c3*e(:)
         r(:) = e(:) - c3*v(:)
         rr = dot_product(r, r)

         !    write(*,*) 'itr, er =', itr,rr
         if (rr/init_rr < er0) exit
         c1 = dot_product(r0, r)
         bet = c1/(c2*c3)

         if ((c2*c3) == 0.0d0) stop "Bicgstab devide by zero"

         p(:) = r(:) + bet*(p(:) - c3*y(:))

      end do
   end subroutine
   !===============================================================

end module OpenACCClass
