!--------------------------------------------------------------------------------------------------!
!   CP2K: A general program to perform molecular dynamics simulations                              !
!   Copyright 2000-2020 CP2K developers group <https://cp2k.org>                                   !
!                                                                                                  !
!   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
!--------------------------------------------------------------------------------------------------!

! **************************************************************************************************
!> \brief Calculation of contracted, spherical Gaussian integrals using the (OS) integral
!>        scheme. Routines for the following two-center integrals:
!>        i)  (a|O(r12)|b) where O(r12) is the overlap, coulomb operator etc.
!>        ii) (aba) and (abb) s-overlaps
!> \par History
!>      created [06.2015]
!>      05.2019: Added truncated coulomb operator (A. Bussy)
!> \author Dorothea Golze
! **************************************************************************************************
MODULE generic_os_integrals
   USE ai_contraction_sphi,             ONLY: ab_contract,&
                                              abc_contract,&
                                              abcd_contract
   USE ai_derivatives,                  ONLY: dabdr_noscreen
   USE ai_operator_ra2m,                ONLY: operator_ra2m
   USE ai_operators_r12,                ONLY: ab_sint_os,&
                                              cps_coulomb2,&
                                              cps_gauss2,&
                                              cps_truncated2,&
                                              cps_verf2,&
                                              cps_verfc2,&
                                              cps_vgauss2,&
                                              operator2
   USE ai_overlap,                      ONLY: overlap_aab,&
                                              overlap_ab,&
                                              overlap_abb
   USE ai_overlap_aabb,                 ONLY: overlap_aabb
   USE basis_set_types,                 ONLY: get_gto_basis_set,&
                                              gto_basis_set_type
   USE constants_operator,              ONLY: operator_coulomb,&
                                              operator_gauss,&
                                              operator_truncated,&
                                              operator_verf,&
                                              operator_verfc,&
                                              operator_vgauss
   USE debug_os_integrals,              ONLY: overlap_aabb_test,&
                                              overlap_ab_test,&
                                              overlap_abc_test
   USE kinds,                           ONLY: dp
   USE orbital_pointers,                ONLY: ncoset
#include "./base/base_uses.f90"

   IMPLICIT NONE

   PRIVATE

! **************************************************************************************************

   CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'generic_os_integrals'

   PUBLIC :: int_operators_r12_ab_os, int_overlap_ab_os, int_ra2m_ab_os, int_overlap_aba_os, &
             int_overlap_abb_os, int_overlap_aabb_os

CONTAINS

! **************************************************************************************************
!> \brief Calcululates the two-center integrals of the type (a|O(r12)|b) using the OS scheme
!> \param r12_operator the integral operator, which depends on r12=|r1-r2|
!> \param vab integral matrix of spherical contracted Gaussian functions
!> \param dvab derivative of the integrals
!> \param rab distance vector between center A and B
!> \param fba basis at center A
!> \param fbb basis at center B
!> \param omega parameter in the operator
!> \param r_cutoff the cutoff in case of truncated coulomb operator
!> \param calculate_forces ...
! **************************************************************************************************
   SUBROUTINE int_operators_r12_ab_os(r12_operator, vab, dvab, rab, fba, fbb, omega, r_cutoff, &
                                      calculate_forces)

      INTEGER, INTENT(IN)                                :: r12_operator
      REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: vab
      REAL(KIND=dp), DIMENSION(:, :, :), INTENT(INOUT), &
         OPTIONAL                                        :: dvab
      REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: rab
      TYPE(gto_basis_set_type), POINTER                  :: fba, fbb
      REAL(KIND=dp), INTENT(IN), OPTIONAL                :: omega, r_cutoff
      LOGICAL, INTENT(IN)                                :: calculate_forces

      CHARACTER(LEN=*), PARAMETER :: routineN = 'int_operators_r12_ab_os'

      INTEGER                                            :: handle
      REAL(KIND=dp)                                      :: my_omega, my_r_cutoff

      PROCEDURE(ab_sint_os), POINTER                     :: cps_operator2

      NULLIFY (cps_operator2)
      CALL timeset(routineN, handle)

      my_omega = 1.0_dp
      my_r_cutoff = 1.0_dp

      SELECT CASE (r12_operator)
      CASE (operator_coulomb)
         cps_operator2 => cps_coulomb2
      CASE (operator_verf)
         cps_operator2 => cps_verf2
         IF (PRESENT(omega)) my_omega = omega
      CASE (operator_verfc)
         cps_operator2 => cps_verfc2
         IF (PRESENT(omega)) my_omega = omega
      CASE (operator_vgauss)
         cps_operator2 => cps_vgauss2
         IF (PRESENT(omega)) my_omega = omega
      CASE (operator_gauss)
         cps_operator2 => cps_gauss2
         IF (PRESENT(omega)) my_omega = omega
      CASE (operator_truncated)
         cps_operator2 => cps_truncated2
         IF (PRESENT(r_cutoff)) my_r_cutoff = r_cutoff
      CASE DEFAULT
         CPABORT("Operator not available")
      END SELECT

      CALL int_operator_ab_os_low(cps_operator2, vab, dvab, rab, fba, fbb, my_omega, my_r_cutoff, &
                                  calculate_forces)

      CALL timestop(handle)

   END SUBROUTINE int_operators_r12_ab_os

! **************************************************************************************************
!> \brief calculate integrals (a|O(r12)|b)
!> \param cps_operator2 procedure pointer for the respective operator.
!> \param vab integral matrix of spherical contracted Gaussian functions
!> \param dvab derivative of the integrals
!> \param rab distance vector between center A and B
!> \param fba basis at center A
!> \param fbb basis at center B
!> \param omega parameter in the operator
!> \param r_cutoff ...
!> \param calculate_forces ...
! **************************************************************************************************
   SUBROUTINE int_operator_ab_os_low(cps_operator2, vab, dvab, rab, fba, fbb, omega, r_cutoff, &
                                     calculate_forces)

      PROCEDURE(ab_sint_os), POINTER                     :: cps_operator2
      REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: vab
      REAL(KIND=dp), DIMENSION(:, :, :), OPTIONAL, &
         INTENT(INOUT)                                   :: dvab
      REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: rab
      TYPE(gto_basis_set_type), POINTER                  :: fba, fbb
      REAL(KIND=dp), INTENT(IN)                          :: omega, r_cutoff
      LOGICAL, INTENT(IN)                                :: calculate_forces

      CHARACTER(LEN=*), PARAMETER :: routineN = 'int_operator_ab_os_low'

      INTEGER                                            :: handle, i, iset, jset, lds, m1, m2, &
                                                            maxco, maxcoa, maxcob, maxl, maxla, &
                                                            maxlb, ncoa, ncoap, ncob, ncobp, &
                                                            nseta, nsetb, sgfa, sgfb
      INTEGER, DIMENSION(:), POINTER                     :: la_max, la_min, lb_max, lb_min, npgfa, &
                                                            npgfb, nsgfa, nsgfb
      INTEGER, DIMENSION(:, :), POINTER                  :: first_sgfa, first_sgfb
      REAL(KIND=dp)                                      :: dab, rab2
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: vac, vac_plus
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: devab, vwork
      REAL(KIND=dp), DIMENSION(:, :), POINTER            :: sphi_a, sphi_b, zeta, zetb

      CALL timeset(routineN, handle)
      NULLIFY (la_max, la_min, lb_max, lb_min, npgfa, npgfb, nsgfa, nsgfb, &
               first_sgfa, first_sgfb, sphi_a, sphi_b, zeta, zetb)

      ! basis ikind
      first_sgfa => fba%first_sgf
      la_max => fba%lmax
      la_min => fba%lmin
      npgfa => fba%npgf
      nseta = fba%nset
      nsgfa => fba%nsgf_set
      sphi_a => fba%sphi
      zeta => fba%zet
      ! basis jkind
      first_sgfb => fbb%first_sgf
      lb_max => fbb%lmax
      lb_min => fbb%lmin
      npgfb => fbb%npgf
      nsetb = fbb%nset
      nsgfb => fbb%nsgf_set
      sphi_b => fbb%sphi
      zetb => fbb%zet

      CALL get_gto_basis_set(fba, maxco=maxcoa, maxl=maxla)
      CALL get_gto_basis_set(fbb, maxco=maxcob, maxl=maxlb)
      maxco = MAX(maxcoa, maxcob)
      IF (calculate_forces) THEN
         maxl = MAX(maxla + 1, maxlb)
      ELSE
         maxl = MAX(maxla, maxlb)
      ENDIF
      lds = ncoset(maxl)

      rab2 = SUM(rab*rab)
      dab = SQRT(rab2)

      vab = 0.0_dp
      IF (calculate_forces) dvab = 0.0_dp

      DO iset = 1, nseta

         ncoa = npgfa(iset)*ncoset(la_max(iset))
         ncoap = npgfa(iset)*ncoset(la_max(iset) + 1)
         sgfa = first_sgfa(1, iset)

         DO jset = 1, nsetb

            ncob = npgfb(jset)*ncoset(lb_max(jset))
            ncobp = npgfb(jset)*ncoset(lb_max(jset) + 1)
            sgfb = first_sgfb(1, jset)
            m1 = sgfa + nsgfa(iset) - 1
            m2 = sgfb + nsgfb(jset) - 1

            ! calculate integrals
            IF (calculate_forces) THEN
               ALLOCATE (vwork(ncoap, ncobp, la_max(iset) + lb_max(jset) + 3), &
                         vac(ncoa, ncob), vac_plus(ncoap, ncobp), devab(ncoa, ncob, 3))
               devab = 0._dp
               vwork = 0.0_dp
               vac = 0.0_dp
               CALL operator2(cps_operator2, la_max(iset) + 1, npgfa(iset), zeta(:, iset), la_min(iset), &
                              lb_max(jset) + 1, npgfb(jset), zetb(:, jset), lb_min(jset), &
                              omega, r_cutoff, rab, rab2, vac, vwork, maxder=1, vac_plus=vac_plus)
               CALL dabdr_noscreen(la_max(iset), npgfa(iset), zeta(:, iset), lb_max(jset), npgfb(jset), &
                                   vac_plus, devab(:, :, 1), devab(:, :, 2), devab(:, :, 3))
               DO i = 1, 3
                  CALL ab_contract(dvab(sgfa:m1, sgfb:m2, i), devab(:, :, i), sphi_a(:, sgfa:), &
                                   sphi_b(:, sgfb:), ncoa, ncob, nsgfa(iset), nsgfb(jset))
               ENDDO

            ELSE
               ALLOCATE (vwork(ncoa, ncob, la_max(iset) + lb_max(jset) + 1), &
                         vac(ncoa, ncob), vac_plus(ncoap, ncobp), devab(ncoa, ncob, 3))
               vwork = 0.0_dp
               vac = 0.0_dp
               CALL operator2(cps_operator2, la_max(iset), npgfa(iset), zeta(:, iset), la_min(iset), &
                              lb_max(jset), npgfb(jset), zetb(:, jset), lb_min(jset), &
                              omega, r_cutoff, rab, rab2, vac, vwork)
            ENDIF

            CALL ab_contract(vab(sgfa:m1, sgfb:m2), vac(1:ncoa, 1:ncob), sphi_a(:, sgfa:), sphi_b(:, sgfb:), &
                             ncoa, ncob, nsgfa(iset), nsgfb(jset))
            DEALLOCATE (vwork, vac, vac_plus, devab)
         END DO
      END DO

      CALL timestop(handle)

   END SUBROUTINE int_operator_ab_os_low

! **************************************************************************************************
!> \brief calculate overlap integrals (a,b)
!> \param sab integral (a,b)
!> \param dsab derivative of sab with respect to A
!> \param rab distance vector between center A and B
!> \param fba basis at center A
!> \param fbb basis at center B
!> \param calculate_forces ...
!> \param debug integrals are debugged by recursive routines if requested
!> \param dmax maximal deviation between integrals when debugging
! **************************************************************************************************
   SUBROUTINE int_overlap_ab_os(sab, dsab, rab, fba, fbb, calculate_forces, debug, dmax)

      REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: sab
      REAL(KIND=dp), DIMENSION(:, :, :), INTENT(INOUT), &
         OPTIONAL                                        :: dsab
      REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: rab
      TYPE(gto_basis_set_type), POINTER                  :: fba, fbb
      LOGICAL, INTENT(IN)                                :: calculate_forces, debug
      REAL(KIND=dp), INTENT(INOUT)                       :: dmax

      CHARACTER(LEN=*), PARAMETER                        :: routineN = 'int_overlap_ab_os'

      INTEGER                                            :: handle, i, iset, jset, m1, m2, maxco, &
                                                            maxcoa, maxcob, maxl, maxla, maxlb, &
                                                            ncoa, ncob, nseta, nsetb, sgfa, sgfb
      INTEGER, DIMENSION(:), POINTER                     :: la_max, la_min, lb_max, lb_min, npgfa, &
                                                            npgfb, nsgfa, nsgfb
      INTEGER, DIMENSION(:, :), POINTER                  :: first_sgfa, first_sgfb
      LOGICAL                                            :: failure
      REAL(KIND=dp)                                      :: dab, ra(3), rb(3)
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: sint
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: dsint
      REAL(KIND=dp), DIMENSION(:), POINTER               :: set_radius_a, set_radius_b
      REAL(KIND=dp), DIMENSION(:, :), POINTER            :: rpgfa, rpgfb, scon_a, scon_b, zeta, zetb

      failure = .FALSE.
      CALL timeset(routineN, handle)
      NULLIFY (la_max, la_min, lb_max, lb_min, npgfa, npgfb, nsgfa, nsgfb, &
               first_sgfa, first_sgfb, scon_a, scon_b, zeta, zetb)

      ! basis ikind
      first_sgfa => fba%first_sgf
      la_max => fba%lmax
      la_min => fba%lmin
      npgfa => fba%npgf
      nseta = fba%nset
      nsgfa => fba%nsgf_set
      rpgfa => fba%pgf_radius
      set_radius_a => fba%set_radius
      scon_a => fba%scon
      zeta => fba%zet
      ! basis jkind
      first_sgfb => fbb%first_sgf
      lb_max => fbb%lmax
      lb_min => fbb%lmin
      npgfb => fbb%npgf
      nsetb = fbb%nset
      nsgfb => fbb%nsgf_set
      rpgfb => fbb%pgf_radius
      set_radius_b => fbb%set_radius
      scon_b => fbb%scon
      zetb => fbb%zet

      CALL get_gto_basis_set(fba, maxco=maxcoa, maxl=maxla)
      CALL get_gto_basis_set(fbb, maxco=maxcob, maxl=maxlb)
      maxco = MAX(maxcoa, maxcob)
      maxl = MAX(maxla, maxlb)
      ALLOCATE (sint(maxco, maxco))
      ALLOCATE (dsint(maxco, maxco, 3))

      dab = SQRT(SUM(rab**2))

      sab = 0.0_dp
      IF (calculate_forces) THEN
         IF (PRESENT(dsab)) dsab = 0.0_dp
      END IF

      DO iset = 1, nseta

         ncoa = npgfa(iset)*(ncoset(la_max(iset)) - ncoset(la_min(iset) - 1))
         sgfa = first_sgfa(1, iset)

         DO jset = 1, nsetb

            IF (set_radius_a(iset) + set_radius_b(jset) < dab) CYCLE

            ncob = npgfb(jset)*(ncoset(lb_max(jset)) - ncoset(lb_min(jset) - 1))
            sgfb = first_sgfb(1, jset)
            m1 = sgfa + nsgfa(iset) - 1
            m2 = sgfb + nsgfb(jset) - 1

            IF (calculate_forces) THEN
               CALL overlap_ab(la_max(iset), la_min(iset), npgfa(iset), rpgfa(:, iset), zeta(:, iset), &
                               lb_max(jset), lb_min(jset), npgfb(jset), rpgfb(:, jset), zetb(:, jset), &
                               rab, sint, dsint)
            ELSE
               CALL overlap_ab(la_max(iset), la_min(iset), npgfa(iset), rpgfa(:, iset), zeta(:, iset), &
                               lb_max(jset), lb_min(jset), npgfb(jset), rpgfb(:, jset), zetb(:, jset), &
                               rab, sint)

            ENDIF

            ! debug if requested
            IF (debug) THEN
               ra = 0.0_dp
               rb = rab
               CALL overlap_ab_test(la_max(iset), la_min(iset), npgfa(iset), zeta(:, iset), &
                                    lb_max(jset), lb_min(jset), npgfb(jset), zetb(:, jset), &
                                    ra, rb, sint, dmax)
            ENDIF

            CALL ab_contract(sab(sgfa:m1, sgfb:m2), sint(1:ncoa, 1:ncob), scon_a(:, sgfa:), scon_b(:, sgfb:), &
                             ncoa, ncob, nsgfa(iset), nsgfb(jset))
            IF (calculate_forces) THEN
               DO i = 1, 3
                  CALL ab_contract(dsab(sgfa:m1, sgfb:m2, i), dsint(1:ncoa, 1:ncob, i), scon_a(:, sgfa:), &
                                   scon_b(:, sgfb:), ncoa, ncob, nsgfa(iset), nsgfb(jset))
               ENDDO
            ENDIF
         END DO
      END DO

      DEALLOCATE (sint, dsint)

      CALL timestop(handle)

   END SUBROUTINE int_overlap_ab_os

! **************************************************************************************************
!> \brief calculate integrals (a|(r-Ra)^(2m)|b)
!> \param sab integral (a|(r-Ra)^(2m)|b)
!> \param dsab derivative of sab with respect to A
!> \param rab distance vector between center A and B
!> \param fba fba basis at center A
!> \param fbb fbb basis at center B
!> \param m exponent m in operator (r-Ra)^(2m)
!> \param calculate_forces ...
! **************************************************************************************************
   SUBROUTINE int_ra2m_ab_os(sab, dsab, rab, fba, fbb, m, calculate_forces)

      REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: sab
      REAL(KIND=dp), DIMENSION(:, :, :), INTENT(INOUT), &
         OPTIONAL                                        :: dsab
      REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: rab
      TYPE(gto_basis_set_type), POINTER                  :: fba, fbb
      INTEGER, INTENT(IN)                                :: m
      LOGICAL, INTENT(IN)                                :: calculate_forces

      CHARACTER(LEN=*), PARAMETER                        :: routineN = 'int_ra2m_ab_os'

      INTEGER                                            :: handle, i, iset, jset, m1, m2, maxco, &
                                                            maxcoa, maxcob, maxl, maxla, maxlb, &
                                                            ncoa, ncob, nseta, nsetb, sgfa, sgfb
      INTEGER, DIMENSION(:), POINTER                     :: la_max, la_min, lb_max, lb_min, npgfa, &
                                                            npgfb, nsgfa, nsgfb
      INTEGER, DIMENSION(:, :), POINTER                  :: first_sgfa, first_sgfb
      LOGICAL                                            :: failure
      REAL(KIND=dp)                                      :: dab
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: sint
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: dsint
      REAL(KIND=dp), DIMENSION(:, :), POINTER            :: scon_a, scon_b, zeta, zetb

      failure = .FALSE.
      CALL timeset(routineN, handle)
      NULLIFY (la_max, la_min, lb_max, lb_min, npgfa, npgfb, nsgfa, nsgfb, &
               first_sgfa, first_sgfb, scon_a, scon_b, zeta, zetb)

      ! basis ikind
      first_sgfa => fba%first_sgf
      la_max => fba%lmax
      la_min => fba%lmin
      npgfa => fba%npgf
      nseta = fba%nset
      nsgfa => fba%nsgf_set
      scon_a => fba%scon
      zeta => fba%zet
      ! basis jkind
      first_sgfb => fbb%first_sgf
      lb_max => fbb%lmax
      lb_min => fbb%lmin
      npgfb => fbb%npgf
      nsetb = fbb%nset
      nsgfb => fbb%nsgf_set
      scon_b => fbb%scon
      zetb => fbb%zet

      CALL get_gto_basis_set(fba, maxco=maxcoa, maxl=maxla)
      CALL get_gto_basis_set(fbb, maxco=maxcob, maxl=maxlb)
      maxco = MAX(maxcoa, maxcob)
      maxl = MAX(maxla, maxlb)
      ALLOCATE (sint(maxco, maxco))
      ALLOCATE (dsint(maxco, maxco, 3))

      dab = SQRT(SUM(rab**2))

      sab = 0.0_dp
      IF (calculate_forces) THEN
         IF (PRESENT(dsab)) dsab = 0.0_dp
      END IF

      DO iset = 1, nseta

         ncoa = npgfa(iset)*(ncoset(la_max(iset)) - ncoset(la_min(iset) - 1))
         sgfa = first_sgfa(1, iset)

         DO jset = 1, nsetb

            ncob = npgfb(jset)*(ncoset(lb_max(jset)) - ncoset(lb_min(jset) - 1))
            sgfb = first_sgfb(1, jset)
            m1 = sgfa + nsgfa(iset) - 1
            m2 = sgfb + nsgfb(jset) - 1

            CALL operator_ra2m(la_max(iset), la_min(iset), npgfa(iset), zeta(:, iset), &
                               lb_max(jset), lb_min(jset), npgfb(jset), zetb(:, jset), &
                               m, rab, sint, dsint, calculate_forces)

            CALL ab_contract(sab(sgfa:m1, sgfb:m2), sint, scon_a(:, sgfa:), scon_b(:, sgfb:), &
                             ncoa, ncob, nsgfa(iset), nsgfb(jset))
            IF (calculate_forces) THEN
               DO i = 1, 3
                  CALL ab_contract(dsab(sgfa:m1, sgfb:m2, i), dsint(:, :, i), scon_a(:, sgfa:), &
                                   scon_b(:, sgfb:), ncoa, ncob, nsgfa(iset), nsgfb(jset))
               ENDDO
            ENDIF
         END DO
      END DO

      DEALLOCATE (sint, dsint)

      CALL timestop(handle)

   END SUBROUTINE int_ra2m_ab_os

! **************************************************************************************************
!> \brief calculate integrals (a,b,fa)
!> \param abaint integral (a,b,fa)
!> \param dabdaint derivative of abaint with respect to A
!> \param rab distance vector between center A and B
!> \param oba orbital basis at center A
!> \param obb orbital basis at center B
!> \param fba auxiliary basis set at center A
!> \param calculate_forces ...
!> \param debug integrals are debugged by recursive routines if requested
!> \param dmax maximal deviation between integrals when debugging
! **************************************************************************************************
   SUBROUTINE int_overlap_aba_os(abaint, dabdaint, rab, oba, obb, fba, &
                                 calculate_forces, debug, dmax)

      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: abaint
      REAL(KIND=dp), ALLOCATABLE, &
         DIMENSION(:, :, :, :), OPTIONAL                 :: dabdaint
      REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: rab
      TYPE(gto_basis_set_type), POINTER                  :: oba, obb, fba
      LOGICAL, INTENT(IN)                                :: calculate_forces, debug
      REAL(KIND=dp), INTENT(INOUT)                       :: dmax

      CHARACTER(LEN=*), PARAMETER :: routineN = 'int_overlap_aba_os'

      INTEGER                                            :: handle, i, iset, jset, kaset, m1, m2, &
                                                            m3, ncoa, ncob, ncoc, nseta, nsetb, &
                                                            nsetca, sgfa, sgfb, sgfc
      INTEGER, DIMENSION(:), POINTER                     :: la_max, la_min, lb_max, lb_min, lca_max, &
                                                            lca_min, npgfa, npgfb, npgfca, nsgfa, &
                                                            nsgfb, nsgfca
      INTEGER, DIMENSION(:, :), POINTER                  :: first_sgfa, first_sgfb, first_sgfca
      REAL(KIND=dp)                                      :: dab, dac, dbc
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: saba
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :, :)  :: sdaba
      REAL(KIND=dp), DIMENSION(3)                        :: ra, rac, rb, rbc
      REAL(KIND=dp), DIMENSION(:), POINTER               :: set_radius_a, set_radius_b, set_radius_ca
      REAL(KIND=dp), DIMENSION(:, :), POINTER            :: rpgfa, rpgfb, rpgfca, scon_a, scon_b, &
                                                            scon_ca, zeta, zetb, zetca

      CALL timeset(routineN, handle)
      NULLIFY (la_max, la_min, lb_max, lb_min, lca_max, lca_min, npgfa, npgfb, &
               npgfca, nsgfa, nsgfb, nsgfca)
      NULLIFY (first_sgfa, first_sgfb, first_sgfca, set_radius_a, set_radius_b, &
               set_radius_ca, rpgfa, rpgfb, rpgfca, scon_a, scon_b, scon_ca, &
               zeta, zetb, zetca)

      ! basis ikind
      first_sgfa => oba%first_sgf
      la_max => oba%lmax
      la_min => oba%lmin
      npgfa => oba%npgf
      nseta = oba%nset
      nsgfa => oba%nsgf_set
      rpgfa => oba%pgf_radius
      set_radius_a => oba%set_radius
      scon_a => oba%scon
      zeta => oba%zet
      ! basis jkind
      first_sgfb => obb%first_sgf
      lb_max => obb%lmax
      lb_min => obb%lmin
      npgfb => obb%npgf
      nsetb = obb%nset
      nsgfb => obb%nsgf_set
      rpgfb => obb%pgf_radius
      set_radius_b => obb%set_radius
      scon_b => obb%scon
      zetb => obb%zet

      ! basis RI A
      first_sgfca => fba%first_sgf
      lca_max => fba%lmax
      lca_min => fba%lmin
      npgfca => fba%npgf
      nsetca = fba%nset
      nsgfca => fba%nsgf_set
      rpgfca => fba%pgf_radius
      set_radius_ca => fba%set_radius
      scon_ca => fba%scon
      zetca => fba%zet

      dab = SQRT(SUM(rab**2))

      abaint = 0.0_dp
      IF (calculate_forces) THEN
         IF (PRESENT(dabdaint)) dabdaint = 0.0_dp
      END IF

      DO iset = 1, nseta

         ncoa = npgfa(iset)*(ncoset(la_max(iset)) - ncoset(la_min(iset) - 1))
         sgfa = first_sgfa(1, iset)

         DO jset = 1, nsetb

            IF (set_radius_a(iset) + set_radius_b(jset) < dab) CYCLE

            ncob = npgfb(jset)*(ncoset(lb_max(jset)) - ncoset(lb_min(jset) - 1))
            sgfb = first_sgfb(1, jset)
            m1 = sgfa + nsgfa(iset) - 1
            m2 = sgfb + nsgfb(jset) - 1

            ! calculate integrals abaint and derivative [d(a,b,a)/dA] dabdaint if requested
            rac = 0._dp
            dac = 0._dp
            rbc = -rab
            dbc = dab

            DO kaset = 1, nsetca

               IF (set_radius_b(jset) + set_radius_ca(kaset) < dab) CYCLE

               ncoc = npgfca(kaset)*(ncoset(lca_max(kaset)) - ncoset(lca_min(kaset) - 1))
               sgfc = first_sgfca(1, kaset)
               m3 = sgfc + nsgfca(kaset) - 1
               IF (ncoa*ncob*ncoc > 0) THEN
                  ALLOCATE (saba(ncoa, ncob, ncoc))
                  ! integrals
                  IF (calculate_forces) THEN
                     ALLOCATE (sdaba(ncoa, ncob, ncoc, 3))
                     CALL overlap_aab(la_max(iset), la_min(iset), npgfa(iset), rpgfa(:, iset), zeta(:, iset), &
                                      lca_max(kaset), lca_min(kaset), npgfca(kaset), rpgfca(:, kaset), zetca(:, kaset), &
                                      lb_max(jset), lb_min(jset), npgfb(jset), rpgfb(:, jset), zetb(:, jset), &
                                      rab, saba=saba, daba=sdaba)

                     DO i = 1, 3
                        CALL abc_contract(dabdaint(sgfa:m1, sgfb:m2, sgfc:m3, i), sdaba(1:ncoa, 1:ncob, 1:ncoc, i), &
                                          scon_a(:, sgfa:), scon_b(:, sgfb:), scon_ca(:, sgfc:), &
                                          ncoa, ncob, ncoc, nsgfa(iset), nsgfb(jset), nsgfca(kaset))
                     ENDDO

                     DEALLOCATE (sdaba)
                  ELSE
                     CALL overlap_aab(la_max(iset), la_min(iset), npgfa(iset), rpgfa(:, iset), zeta(:, iset), &
                                      lca_max(kaset), lca_min(kaset), npgfca(kaset), rpgfca(:, kaset), zetca(:, kaset), &
                                      lb_max(jset), lb_min(jset), npgfb(jset), rpgfb(:, jset), zetb(:, jset), &
                                      rab, saba=saba)

                  ENDIF
                  ! debug if requested
                  IF (debug) THEN
                     ra = 0.0_dp
                     rb = rab
                     CALL overlap_abc_test(la_max(iset), npgfa(iset), zeta(:, iset), la_min(iset), &
                                           lb_max(jset), npgfb(jset), zetb(:, jset), lb_min(jset), &
                                           lca_max(kaset), npgfca(kaset), zetca(:, kaset), lca_min(kaset), &
                                           ra, rb, ra, saba, dmax)
                  ENDIF
                  CALL abc_contract(abaint(sgfa:m1, sgfb:m2, sgfc:m3), saba(1:ncoa, 1:ncob, 1:ncoc), &
                                    scon_a(:, sgfa:), scon_b(:, sgfb:), scon_ca(:, sgfc:), &
                                    ncoa, ncob, ncoc, nsgfa(iset), nsgfb(jset), nsgfca(kaset))
                  DEALLOCATE (saba)
               END IF
            END DO
         END DO
      END DO

      CALL timestop(handle)

   END SUBROUTINE int_overlap_aba_os

! **************************************************************************************************
!> \brief calculate integrals (a,b,fb)
!> \param abbint integral (a,b,fb)
!> \param dabbint derivative of abbint with respect to A
!> \param rab distance vector between center A and B
!> \param oba orbital basis at center A
!> \param obb orbital basis at center B
!> \param fbb auxiliary basis set at center B
!> \param calculate_forces ...
!> \param debug integrals are debugged by recursive routines if requested
!> \param dmax maximal deviation between integrals when debugging
! **************************************************************************************************
   SUBROUTINE int_overlap_abb_os(abbint, dabbint, rab, oba, obb, fbb, calculate_forces, debug, dmax)

      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: abbint
      REAL(KIND=dp), ALLOCATABLE, &
         DIMENSION(:, :, :, :), OPTIONAL                 :: dabbint
      REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: rab
      TYPE(gto_basis_set_type), POINTER                  :: oba, obb, fbb
      LOGICAL, INTENT(IN)                                :: calculate_forces, debug
      REAL(KIND=dp), INTENT(INOUT)                       :: dmax

      CHARACTER(LEN=*), PARAMETER :: routineN = 'int_overlap_abb_os'

      INTEGER                                            :: handle, i, iset, jset, kbset, m1, m2, &
                                                            m3, ncoa, ncob, ncoc, nseta, nsetb, &
                                                            nsetcb, sgfa, sgfb, sgfc
      INTEGER, DIMENSION(:), POINTER                     :: la_max, la_min, lb_max, lb_min, lcb_max, &
                                                            lcb_min, npgfa, npgfb, npgfcb, nsgfa, &
                                                            nsgfb, nsgfcb
      INTEGER, DIMENSION(:, :), POINTER                  :: first_sgfa, first_sgfb, first_sgfcb
      REAL(KIND=dp)                                      :: dab, dac, dbc
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: sabb
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :, :)  :: sdabb
      REAL(KIND=dp), DIMENSION(3)                        :: ra, rac, rb, rbc
      REAL(KIND=dp), DIMENSION(:), POINTER               :: set_radius_a, set_radius_b, set_radius_cb
      REAL(KIND=dp), DIMENSION(:, :), POINTER            :: rpgfa, rpgfb, rpgfcb, scon_a, scon_b, &
                                                            scon_cb, zeta, zetb, zetcb

      CALL timeset(routineN, handle)
      NULLIFY (la_max, la_min, lb_max, lb_min, lcb_max, lcb_min, npgfa, npgfb, &
               npgfcb, nsgfa, nsgfb, nsgfcb)
      NULLIFY (first_sgfa, first_sgfb, first_sgfcb, set_radius_a, set_radius_b, &
               set_radius_cb, rpgfa, rpgfb, rpgfcb, scon_a, scon_b, scon_cb, &
               zeta, zetb, zetcb)

      ! basis ikind
      first_sgfa => oba%first_sgf
      la_max => oba%lmax
      la_min => oba%lmin
      npgfa => oba%npgf
      nseta = oba%nset
      nsgfa => oba%nsgf_set
      rpgfa => oba%pgf_radius
      set_radius_a => oba%set_radius
      scon_a => oba%scon
      zeta => oba%zet
      ! basis jkind
      first_sgfb => obb%first_sgf
      lb_max => obb%lmax
      lb_min => obb%lmin
      npgfb => obb%npgf
      nsetb = obb%nset
      nsgfb => obb%nsgf_set
      rpgfb => obb%pgf_radius
      set_radius_b => obb%set_radius
      scon_b => obb%scon
      zetb => obb%zet

      ! basis RI B
      first_sgfcb => fbb%first_sgf
      lcb_max => fbb%lmax
      lcb_min => fbb%lmin
      npgfcb => fbb%npgf
      nsetcb = fbb%nset
      nsgfcb => fbb%nsgf_set
      rpgfcb => fbb%pgf_radius
      set_radius_cb => fbb%set_radius
      scon_cb => fbb%scon
      zetcb => fbb%zet

      dab = SQRT(SUM(rab**2))

      abbint = 0.0_dp
      IF (calculate_forces) THEN
         IF (PRESENT(dabbint)) dabbint = 0.0_dp
      END IF

      DO iset = 1, nseta

         ncoa = npgfa(iset)*(ncoset(la_max(iset)) - ncoset(la_min(iset) - 1))
         sgfa = first_sgfa(1, iset)

         DO jset = 1, nsetb

            IF (set_radius_a(iset) + set_radius_b(jset) < dab) CYCLE

            ncob = npgfb(jset)*(ncoset(lb_max(jset)) - ncoset(lb_min(jset) - 1))
            sgfb = first_sgfb(1, jset)
            m1 = sgfa + nsgfa(iset) - 1
            m2 = sgfb + nsgfb(jset) - 1

            ! calculate integrals abbint and derivative [d(a,b,b)/dA] dabbint if requested
            rac = rab
            dac = dab
            rbc = 0._dp
            dbc = 0._dp

            DO kbset = 1, nsetcb

               IF (set_radius_a(iset) + set_radius_cb(kbset) < dab) CYCLE

               ncoc = npgfcb(kbset)*(ncoset(lcb_max(kbset)) - ncoset(lcb_min(kbset) - 1))
               sgfc = first_sgfcb(1, kbset)
               m3 = sgfc + nsgfcb(kbset) - 1
               IF (ncoa*ncob*ncoc > 0) THEN
                  ALLOCATE (sabb(ncoa, ncob, ncoc))
                  IF (calculate_forces) THEN
                     ALLOCATE (sdabb(ncoa, ncob, ncoc, 3))
                     CALL overlap_abb(la_max(iset), la_min(iset), npgfa(iset), rpgfa(:, iset), zeta(:, iset), &
                                      lb_max(jset), lb_min(jset), npgfb(jset), rpgfb(:, jset), zetb(:, jset), &
                                      lcb_max(kbset), lcb_min(kbset), npgfcb(kbset), rpgfcb(:, kbset), zetcb(:, kbset), &
                                      rab, sabb, sdabb)

                     DO i = 1, 3
                        CALL abc_contract(dabbint(sgfa:m1, sgfb:m2, sgfc:m3, i), sdabb(1:ncoa, 1:ncob, 1:ncoc, i), &
                                          scon_a(:, sgfa:), scon_b(:, sgfb:), scon_cb(:, sgfc:), &
                                          ncoa, ncob, ncoc, nsgfa(iset), nsgfb(jset), nsgfcb(kbset))
                     ENDDO
                     DEALLOCATE (sdabb)
                  ELSE
                     CALL overlap_abb(la_max(iset), la_min(iset), npgfa(iset), rpgfa(:, iset), zeta(:, iset), &
                                      lb_max(jset), lb_min(jset), npgfb(jset), rpgfb(:, jset), zetb(:, jset), &
                                      lcb_max(kbset), lcb_min(kbset), npgfcb(kbset), rpgfcb(:, kbset), zetcb(:, kbset), &
                                      rab, sabb)
                  ENDIF
                  ! debug if requested
                  IF (debug) THEN
                     ra = 0.0_dp
                     rb = rab
                     CALL overlap_abc_test(la_max(iset), npgfa(iset), zeta(:, iset), la_min(iset), &
                                           lb_max(jset), npgfb(jset), zetb(:, jset), lb_min(jset), &
                                           lcb_max(kbset), npgfcb(kbset), zetcb(:, kbset), lcb_min(kbset), &
                                           ra, rb, rb, sabb, dmax)
                  ENDIF

                  CALL abc_contract(abbint(sgfa:m1, sgfb:m2, sgfc:m3), sabb(1:ncoa, 1:ncob, 1:ncoc), &
                                    scon_a(:, sgfa:), scon_b(:, sgfb:), scon_cb(:, sgfc:), &
                                    ncoa, ncob, ncoc, nsgfa(iset), nsgfb(jset), nsgfcb(kbset))
                  DEALLOCATE (sabb)
               ENDIF
            END DO

         END DO
      END DO

      CALL timestop(handle)

   END SUBROUTINE int_overlap_abb_os

! **************************************************************************************************
!> \brief calculate overlap integrals (aa,bb)
!> \param saabb integral (aa,bb)
!> \param oba orbital basis at center A
!> \param obb orbital basis at center B
!> \param rab ...
!> \param debug integrals are debugged by recursive routines if requested
!> \param dmax maximal deviation between integrals when debugging
! **************************************************************************************************
   SUBROUTINE int_overlap_aabb_os(saabb, oba, obb, rab, debug, dmax)

      REAL(KIND=dp), DIMENSION(:, :, :, :), POINTER      :: saabb
      TYPE(gto_basis_set_type), POINTER                  :: oba, obb
      REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: rab
      LOGICAL, INTENT(IN)                                :: debug
      REAL(KIND=dp), INTENT(INOUT)                       :: dmax

      CHARACTER(LEN=*), PARAMETER :: routineN = 'int_overlap_aabb_os'

      INTEGER :: handle, iset, isgfa1, jset, jsgfa2, kset, ksgfb1, lds, lset, lsgfb2, m1, m2, m3, &
         m4, maxco, maxcoa, maxcob, maxl, maxla, maxlb, ncoa1, ncoa2, ncob1, ncob2, nseta, nsetb, &
         sgfa1, sgfa2, sgfb1, sgfb2
      INTEGER, DIMENSION(:), POINTER                     :: la_max, la_min, lb_max, lb_min, npgfa, &
                                                            npgfb, nsgfa, nsgfb
      INTEGER, DIMENSION(:, :), POINTER                  :: first_sgfa, first_sgfb
      LOGICAL                                            :: asets_equal, bsets_equal
      REAL(KIND=dp)                                      :: dab, ra(3), rb(3)
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: swork
      REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :, :)  :: sint
      REAL(KIND=dp), DIMENSION(:), POINTER               :: set_radius_a, set_radius_b
      REAL(KIND=dp), DIMENSION(:, :), POINTER            :: rpgfa, rpgfb, sphi_a, sphi_b, zeta, zetb

      CALL timeset(routineN, handle)
      NULLIFY (la_max, la_min, lb_max, lb_min, npgfa, npgfb, nsgfa, nsgfb, &
               first_sgfa, first_sgfb, set_radius_a, set_radius_b, rpgfa, rpgfb, &
               sphi_a, sphi_b, zeta, zetb)

      ! basis ikind
      first_sgfa => oba%first_sgf
      la_max => oba%lmax
      la_min => oba%lmin
      npgfa => oba%npgf
      nseta = oba%nset
      nsgfa => oba%nsgf_set
      rpgfa => oba%pgf_radius
      set_radius_a => oba%set_radius
      sphi_a => oba%sphi
      zeta => oba%zet
      ! basis jkind
      first_sgfb => obb%first_sgf
      lb_max => obb%lmax
      lb_min => obb%lmin
      npgfb => obb%npgf
      nsetb = obb%nset
      nsgfb => obb%nsgf_set
      rpgfb => obb%pgf_radius
      set_radius_b => obb%set_radius
      sphi_b => obb%sphi
      zetb => obb%zet

      CALL get_gto_basis_set(oba, maxco=maxcoa, maxl=maxla)
      CALL get_gto_basis_set(obb, maxco=maxcob, maxl=maxlb)
      maxco = MAX(maxcoa, maxcob)
      maxla = 2*maxla
      maxlb = 2*maxlb
      maxl = MAX(maxla, maxlb)
      lds = ncoset(maxl)
      ALLOCATE (sint(maxco, maxco, maxco, maxco))
      ALLOCATE (swork(lds, lds))
      sint = 0._dp
      swork = 0._dp

      dab = SQRT(SUM(rab**2))

      DO iset = 1, nseta

         ncoa1 = npgfa(iset)*ncoset(la_max(iset))
         sgfa1 = first_sgfa(1, iset)
         m1 = sgfa1 + nsgfa(iset) - 1

         DO jset = iset, nseta

            ncoa2 = npgfa(jset)*ncoset(la_max(jset))
            sgfa2 = first_sgfa(1, jset)
            m2 = sgfa2 + nsgfa(jset) - 1

            DO kset = 1, nsetb

               ncob1 = npgfb(kset)*ncoset(lb_max(kset))
               sgfb1 = first_sgfb(1, kset)
               m3 = sgfb1 + nsgfb(kset) - 1

               DO lset = kset, nsetb

                  ncob2 = npgfb(lset)*ncoset(lb_max(lset))
                  sgfb2 = first_sgfb(1, lset)
                  m4 = sgfb2 + nsgfb(lset) - 1

                  ! check if sets are identical to spare some integral evaluation
                  asets_equal = .FALSE.
                  IF (iset == jset) asets_equal = .TRUE.
                  bsets_equal = .FALSE.
                  IF (kset == lset) bsets_equal = .TRUE.
                  ! calculate integrals
                  CALL overlap_aabb(la_max(iset), la_min(iset), npgfa(iset), rpgfa(:, iset), zeta(:, iset), &
                                    la_max(jset), la_min(jset), npgfa(jset), rpgfa(:, jset), zeta(:, jset), &
                                    lb_max(kset), lb_min(kset), npgfb(kset), rpgfb(:, kset), zetb(:, kset), &
                                    lb_max(lset), lb_min(lset), npgfb(lset), rpgfb(:, lset), zetb(:, lset), &
                                    asets_equal, bsets_equal, rab, dab, sint, swork, lds)
                  ! debug if requested
                  IF (debug) THEN
                     ra = 0.0_dp
                     rb = rab
                     CALL overlap_aabb_test(la_max(iset), la_min(iset), npgfa(iset), zeta(:, iset), &
                                            la_max(jset), la_min(jset), npgfa(jset), zeta(:, jset), &
                                            lb_max(kset), lb_min(kset), npgfb(kset), zetb(:, kset), &
                                            lb_max(lset), lb_min(lset), npgfb(lset), zetb(:, lset), &
                                            ra, rb, sint, dmax)
                  ENDIF

                  CALL abcd_contract(saabb(sgfa1:m1, sgfa2:m2, sgfb1:m3, sgfb2:m4), sint, sphi_a(:, sgfa1:), &
                                     sphi_a(:, sgfa2:), sphi_b(:, sgfb1:), sphi_b(:, sgfb2:), ncoa1, ncoa2, &
                                     ncob1, ncob2, nsgfa(iset), nsgfa(jset), nsgfb(kset), nsgfb(lset))

                  ! account for the fact that some integrals are alike
                  DO isgfa1 = sgfa1, m1
                     DO jsgfa2 = sgfa2, m2
                        DO ksgfb1 = sgfb1, m3
                           DO lsgfb2 = sgfb2, m4
                              saabb(jsgfa2, isgfa1, ksgfb1, lsgfb2) = saabb(isgfa1, jsgfa2, ksgfb1, lsgfb2)
                              saabb(isgfa1, jsgfa2, lsgfb2, ksgfb1) = saabb(isgfa1, jsgfa2, ksgfb1, lsgfb2)
                              saabb(jsgfa2, isgfa1, lsgfb2, ksgfb1) = saabb(isgfa1, jsgfa2, ksgfb1, lsgfb2)
                           END DO
                        END DO
                     END DO
                  END DO

               END DO
            END DO
         END DO
      END DO

      DEALLOCATE (sint, swork)

      CALL timestop(handle)

   END SUBROUTINE int_overlap_aabb_os

END MODULE generic_os_integrals
