!-------------------------------------------------------------------------------
! Copyright (c) 2019 FrontISTR Commons
! This software is released under the MIT License, see LICENSE.txt
!-------------------------------------------------------------------------------

module hecmw_matrix_ass
  use hecmw_util
  use m_hecmw_comm_f
  use hecmw_matrix_misc
  use hecmw_matrix_contact
  implicit none

  private

  public :: hecmw_mat_ass_elem
  public :: hecmw_mat_add_node
  public :: hecmw_array_search_i
  public :: hecmw_mat_ass_equation
  public :: hecmw_mat_ass_equation_rhs
  public :: hecmw_mat_add_dof
  public :: hecmw_mat_ass_bc
  public :: hecmw_mat_ass_contact
  public :: stf_get_block

contains

  !C
  !C***
  !C*** MAT_ASS_ELEM
  !C***
  !C
  subroutine hecmw_mat_ass_elem(hecMAT, nn, nodLOCAL, stiffness)
    type (hecmwST_matrix)     :: hecMAT
    integer(kind=kint) :: nn
    integer(kind=kint) :: nodLOCAL(:)
    real(kind=kreal) :: stiffness(:, :)
    !** Local variables
    integer(kind=kint) :: ndof, inod_e, jnod_e, inod, jnod
    real(kind=kreal) :: a(6,6)

    ndof = hecMAT%NDOF

    do inod_e = 1, nn
      inod = nodLOCAL(inod_e)
      do jnod_e = 1, nn
        jnod = nodLOCAL(jnod_e)
        !***** Add components
        call stf_get_block(stiffness, ndof, inod_e, jnod_e, a)
        call hecmw_mat_add_node(hecMAT, inod, jnod, a)
      enddo
    enddo

  end subroutine hecmw_mat_ass_elem

  subroutine stf_get_block(stiffness, ndof, inod, jnod, a)
    real(kind=kreal) :: stiffness(:, :), a(:, :)
    integer(kind=kint) :: ndof, inod, jnod
    !** Local variables
    integer(kind=kint) :: row_offset, col_offset, i, j

    row_offset = ndof*(inod-1)
    do i = 1, ndof

      col_offset = ndof*(jnod-1)
      do j = 1, ndof

        a(i, j) = stiffness(i + row_offset, j + col_offset)
      enddo
    enddo
  end subroutine stf_get_block


  subroutine hecmw_mat_add_node(hecMAT, inod, jnod, a)
    type (hecmwST_matrix) :: hecMAT
    integer(kind=kint) :: inod, jnod
    real(kind=kreal) :: a(:, :)
    !** Local variables
    integer(kind=kint) :: NDOF, is, iE, k, idx_base, idx, idof, jdof

    NDOF = hecMAT%NDOF

    if (inod < jnod) then
      is = hecMAT%indexU(inod-1)+1
      iE = hecMAT%indexU(inod)
      k = hecmw_array_search_i(hecMAT%itemU, is, iE, jnod)

      if (k < is .or. iE < k) then
        write(*,*) '###ERROR### : cannot find connectivity (1)'
        write(*,*) ' myrank = ', hecmw_comm_get_rank(), ', inod = ', inod, ', jnod = ', jnod
        call hecmw_abort(hecmw_comm_get_comm())
      endif

      idx_base = NDOF**2 * (k-1)
      do idof = 1, NDOF
        do jdof = 1, NDOF
          idx = idx_base + jdof
          !$omp atomic
          hecMAT%AU(idx) = hecMAT%AU(idx) + a(idof, jdof)
        enddo
        idx_base = idx_base + NDOF
      enddo

    else if (inod > jnod) then
      is = hecMAT%indexL(inod-1)+1
      iE = hecMAT%indexL(inod)
      k = hecmw_array_search_i(hecMAT%itemL, is, iE, jnod)

      if (k < is .or. iE < k) then
        write(*,*) '###ERROR### : cannot find connectivity (2)'
        write(*,*) ' myrank = ', hecmw_comm_get_rank(), ', inod = ', inod, ', jnod = ', jnod
        call hecmw_abort(hecmw_comm_get_comm())
      endif

      idx_base = NDOF**2 * (k-1)
      do idof = 1, NDOF
        do jdof = 1, NDOF
          idx = idx_base + jdof
          !$omp atomic
          hecMAT%AL(idx) = hecMAT%AL(idx) + a(idof, jdof)
        enddo
        idx_base = idx_base + NDOF
      enddo

    else
      idx_base = NDOF**2 * (inod - 1)
      do idof = 1, NDOF
        do jdof = 1, NDOF
          idx = idx_base + jdof
          !$omp atomic
          hecMAT%D(idx) = hecMAT%D(idx) + a(idof, jdof)
        enddo
        idx_base = idx_base + NDOF
      enddo
    endif
  end subroutine hecmw_mat_add_node


  function hecmw_array_search_i(array, is, iE, ival)
    integer(kind=kint) :: hecmw_array_search_i
    integer(kind=kint) :: array(:)
    integer(kind=kint) :: is, iE, ival
    !** Local variables
    integer(kind=kint) :: left, right, center, cval

    left = is
    right = iE
    do
      if (left > right) then
        center = -1
        exit
      endif

      center = (left + right) / 2
      cval = array(center)

      if (ival < cval) then
        right = center - 1
      else if (cval < ival) then
        left = center + 1
      else
        exit
      endif
    enddo

    hecmw_array_search_i = center

  end function hecmw_array_search_i


  !C
  !C***
  !C*** MAT_ASS_EQUATION
  !C***
  !C
  subroutine hecmw_mat_ass_equation ( hecMESH, hecMAT )
    type (hecmwST_matrix), target :: hecMAT
    type (hecmwST_local_mesh)     :: hecMESH
    !** Local variables
    real(kind=kreal), pointer :: penalty
    real(kind=kreal) :: ALPHA, a1_2inv, ai, aj, factor
    integer(kind=kint) :: NDIAG, impc, is, iE, i, j, inod, idof, jnod, jdof
    logical :: is_internal_i, is_internal_j

    if( hecmw_mat_get_penalized(hecMAT) == 1 ) return

    ! write(*,*) "INFO: imposing MPC by penalty"

    penalty => hecMAT%Rarray(11)

    if (penalty < 0.0) stop "ERROR: negative penalty"
    if (penalty < 1.0) write(*,*) "WARNING: penalty ", penalty, " smaller than 1"

    ALPHA= hecmw_mat_diag_max(hecMAT, hecMESH) * penalty
    call hecmw_mat_set_penalty_alpha(hecMAT, ALPHA)

    OUTER: do impc = 1, hecMESH%mpc%n_mpc
      is = hecMESH%mpc%mpc_index(impc-1) + 1
      iE = hecMESH%mpc%mpc_index(impc)

      do i = is, iE
        if (hecMESH%mpc%mpc_dof(i) > hecMAT%NDOF) cycle OUTER
      enddo

      a1_2inv = 1.0 / hecMESH%mpc%mpc_val(is)**2


      do i = is, iE
        inod = hecMESH%mpc%mpc_item(i)

        is_internal_i = (hecMESH%node_ID(2*inod) == hecmw_comm_get_rank())

        idof = hecMESH%mpc%mpc_dof(i)
        ai = hecMESH%mpc%mpc_val(i)
        factor = ai * a1_2inv

        do j = is, iE
          jnod = hecMESH%mpc%mpc_item(j)

          is_internal_j = (hecMESH%node_ID(2*jnod) == hecmw_comm_get_rank())
          if (.not. (is_internal_i .or. is_internal_j)) cycle

          jdof = hecMESH%mpc%mpc_dof(j)
          aj = hecMESH%mpc%mpc_val(j)

          call hecmw_mat_add_dof(hecMAT, inod, idof, jnod, jdof, aj*factor*ALPHA)
        enddo

      enddo
    enddo OUTER

    call hecmw_mat_set_penalized(hecMAT, 1)

  end subroutine hecmw_mat_ass_equation


  subroutine hecmw_mat_ass_equation_rhs ( hecMESH, hecMAT )
    type (hecmwST_matrix), target :: hecMAT
    type (hecmwST_local_mesh)     :: hecMESH
    !** Local variables
    real(kind=kreal) :: ALPHA, a1_2inv, ai, factor, ci
    integer(kind=kint) :: ndof, impc, iS, iE, i, inod, idof

    if( hecmw_mat_get_penalized_b(hecMAT) == 1) return

    ALPHA = hecmw_mat_get_penalty_alpha(hecMAT)
    if (ALPHA <= 0.0) stop "ERROR: penalty applied on vector before matrix"

    ndof = hecMAT%NDOF

    OUTER: do impc = 1, hecMESH%mpc%n_mpc
      iS = hecMESH%mpc%mpc_index(impc-1) + 1
      iE = hecMESH%mpc%mpc_index(impc)

      do i = is, iE
        if (hecMESH%mpc%mpc_dof(i) > ndof) cycle OUTER
      enddo

      a1_2inv = 1.0 / hecMESH%mpc%mpc_val(iS)**2

      do i = iS, iE
        inod = hecMESH%mpc%mpc_item(i)

        idof = hecMESH%mpc%mpc_dof(i)
        ai = hecMESH%mpc%mpc_val(i)
        factor = ai * a1_2inv

        ci = hecMESH%mpc%mpc_const(impc)
        !$omp atomic
        hecMAT%B(ndof*(inod-1)+idof) = hecMAT%B(ndof*(inod-1)+idof) + ci*factor*ALPHA
      enddo
    enddo OUTER

    call hecmw_mat_set_penalized_b(hecMAT, 1)

  end subroutine hecmw_mat_ass_equation_rhs


  subroutine hecmw_mat_add_dof(hecMAT, inod, idof, jnod, jdof, val)
    type (hecmwST_matrix) :: hecMAT
    integer(kind=kint) :: inod, idof, jnod, jdof
    real(kind=kreal) :: val
    !** Local variables
    integer(kind=kint) :: NDOF, is, iE, k, idx

    NDOF = hecMAT%NDOF
    if (inod < jnod) then
      is = hecMAT%indexU(inod-1)+1
      iE = hecMAT%indexU(inod)
      k = hecmw_array_search_i(hecMAT%itemU, is, iE, jnod)

      if (k < is .or. iE < k) then
        write(*,*) '###ERROR### : cannot find connectivity (3)'
        write(*,*) '  myrank = ', hecmw_comm_get_rank(), ', inod = ', inod, ', jnod = ', jnod
        call hecmw_abort(hecmw_comm_get_comm())
        return
      endif

      idx = NDOF**2 * (k-1) + NDOF * (idof-1) + jdof
      !$omp atomic
      hecMAT%AU(idx) = hecMAT%AU(idx) + val

    else if (inod > jnod) then
      is = hecMAT%indexL(inod-1)+1
      iE = hecMAT%indexL(inod)
      k = hecmw_array_search_i(hecMAT%itemL, is, iE, jnod)

      if (k < is .or. iE < k) then
        write(*,*) '###ERROR### : cannot find connectivity (4)'
        write(*,*) ' myrank = ', hecmw_comm_get_rank(), ', inod = ', inod, ', jnod = ', jnod
        call hecmw_abort(hecmw_comm_get_comm())
        return
      endif

      idx = NDOF**2 * (k-1) + NDOF * (idof-1) + jdof
      !$omp atomic
      hecMAT%AL(idx) = hecMAT%AL(idx) + val

    else
      idx = NDOF**2 * (inod - 1) + NDOF * (idof - 1) + jdof
      !$omp atomic
      hecMAT%D(idx) = hecMAT%D(idx) + val
    endif

  end subroutine hecmw_mat_add_dof

  !C
  !C***
  !C*** MAT_ASS_BC
  !C***
  !C
  subroutine hecmw_mat_ass_bc(hecMAT, inode, idof, RHS, conMAT)
    type (hecmwST_matrix)     :: hecMAT
    integer(kind=kint) :: inode, idof
    real(kind=kreal) :: RHS, val
    type (hecmwST_matrix),optional     :: conMAT
    integer(kind=kint) :: NDOF, in, i, ii, iii, ndof2, k, is, iE, iiS, iiE, ik, idx

    NDOF = hecMAT%NDOF
    if( NDOF < idof ) return

    !C-- DIAGONAL block

    hecMAT%B(NDOF*inode-(NDOF-idof)) = RHS
    if(present(conMAT)) conMAT%B(NDOF*inode-(NDOF-idof)) = 0.0D0
    ndof2 = NDOF*NDOF
    ii  = ndof2 - idof

    do i = NDOF-1,0,-1
      if( i .NE. NDOF-idof ) then
        idx = NDOF*inode-i
        val = hecMAT%D(ndof2*inode-ii)*RHS
        !$omp atomic
        hecMAT%B(idx) = hecMAT%B(idx) - val
        if(present(conMAT)) then
          val = conMAT%D(ndof2*inode-ii)*RHS
          !$omp atomic
          conMAT%B(idx) = conMAT%B(idx) - val
        endif
      endif
      ii = ii - NDOF
    end do

    !*Set diagonal row to zero
    ii  = ndof2-1 - (idof-1)*NDOF

    do i = 0, NDOF - 1
      hecMAT%D(ndof2*inode-ii+i)=0.d0
      if(present(conMAT)) conMAT%D(ndof2*inode-ii+i)=0.d0
    end do

    !*Set diagonal column to zero
    ii = ndof2 - idof
    do i = 1, NDOF
      if( i.NE.idof ) then
        hecMAT%D(ndof2*inode-ii) = 0.d0
        if(present(conMAT)) conMAT%D(ndof2*inode-ii) = 0.d0
      else
        hecMAT%D(ndof2*inode-ii) = 1.d0
        if(present(conMAT)) conMAT%D(ndof2*inode-ii) = 0.d0
      endif
      ii = ii - NDOF
    end do

    !C-- OFF-DIAGONAL blocks

    ii  = ndof2-1 - (idof-1)*NDOF
    is = hecMAT%indexL(inode-1) + 1
    iE = hecMAT%indexL(inode  )

    do k= is, iE

      !*row (left)
      do i = 0, NDOF - 1
        hecMAT%AL(ndof2*k-ii+i) = 0.d0
        if(present(conMAT)) conMAT%AL(ndof2*k-ii+i) = 0.d0
      end do

      !*column (upper)
      in = hecMAT%itemL(k)
      iiS = hecMAT%indexU(in-1) + 1
      iiE = hecMAT%indexU(in  )
      do ik = iiS, iiE
        if (hecMAT%itemU(ik) .eq. inode) then
          iii = ndof2 - idof
          do i = NDOF-1,0,-1
            idx = NDOF*in-i
            val = hecMAT%AU(ndof2*ik-iii)*RHS
            !$omp atomic
            hecMAT%B(idx) = hecMAT%B(idx) - val
            hecMAT%AU(ndof2*ik-iii)= 0.d0
            if(present(conMAT)) then
              val = conMAT%AU(ndof2*ik-iii)*RHS
              !$omp atomic
              conMAT%B(idx) = conMAT%B(idx) - val
              conMAT%AU(ndof2*ik-iii)= 0.d0
            endif
            iii = iii - NDOF
          end do
          exit
        endif
      enddo

    enddo

    ii = ndof2-1 - (idof-1)*NDOF
    is = hecMAT%indexU(inode-1) + 1
    iE = hecMAT%indexU(inode  )

    do k= is, iE

      !*row (right)
      do i = 0,NDOF-1
        hecMAT%AU(ndof2*k-ii+i) = 0.d0
        if(present(conMAT)) conMAT%AU(ndof2*k-ii+i) = 0.d0
      end do

      !*column (lower)
      in = hecMAT%itemU(k)
      iiS = hecMAT%indexL(in-1) + 1
      iiE = hecMAT%indexL(in  )
      do ik= iiS, iiE
        if (hecMAT%itemL(ik) .eq. inode) then
          iii  = ndof2 - idof

          do i = NDOF-1, 0, -1
            idx = NDOF*in-i
            val = hecMAT%AL(ndof2*ik-iii)*RHS
            !$omp atomic
            hecMAT%B(idx) = hecMAT%B(idx) - val
            hecMAT%AL(ndof2*ik-iii) = 0.d0
            if(present(conMAT)) then
              val = conMAT%AL(ndof2*ik-iii)*RHS
              !$omp atomic
              conMAT%B(idx) = conMAT%B(idx) - val
              conMAT%AL(ndof2*ik-iii) = 0.d0
            endif
            iii = iii - NDOF
          end do
          exit
        endif
      enddo

    enddo
    !*End off - diagonal blocks

    call hecmw_cmat_ass_bc(hecMAT, inode, idof, RHS)

  end subroutine hecmw_mat_ass_bc

  !C
  !C***
  !C*** MAT_ASS_CONTACT
  !C***
  !C
  subroutine hecmw_mat_ass_contact(hecMAT, nn, nodLOCAL, stiffness)
    type (hecmwST_matrix)     :: hecMAT
    integer(kind=kint) :: nn
    integer(kind=kint) :: nodLOCAL(:)
    real(kind=kreal) :: stiffness(:, :)
    !** Local variables
    integer(kind=kint) :: ndof, inod_e, jnod_e, inod, jnod
    real(kind=kreal) :: a(3,3)

    ndof = hecMAT%NDOF
    if( ndof .ne. 3 ) then
      write(*,*) '###ERROR### : ndof=',ndof,'; contact matrix supports only ndof==3'
      call hecmw_abort(hecmw_comm_get_comm())
      return
    endif

    do inod_e = 1, nn
      inod = nodLOCAL(inod_e)
      do jnod_e = 1, nn
        jnod = nodLOCAL(jnod_e)
        !***** Add components
        call stf_get_block(stiffness, ndof, inod_e, jnod_e, a)
        call hecmw_cmat_add(hecMAT%cmat, inod, jnod, a)
      enddo
    enddo
    call hecmw_cmat_pack(hecMAT%cmat)

  end subroutine hecmw_mat_ass_contact

end module hecmw_matrix_ass
