#include "GAD_OPTIONS.h"

CBOP
C     !ROUTINE: GAD_IMPLICIT_R
C     !INTERFACE:
      SUBROUTINE GAD_IMPLICIT_R(
     I      implicitAdvection, advectionScheme, tracerIdentity,
     I      deltaTLev,
     I      kappaRX, recip_hFac, wFld, tracer,
     U      gTracer,
     I      bi, bj, myTime, myIter, myThid )
C     !DESCRIPTION:
C     Solve implicitly vertical advection and diffusion for one tracer.

C     !USES:
      IMPLICIT NONE
C     == Global data ==
#include "SIZE.h"
#include "EEPARAMS.h"
#include "PARAMS.h"
#include "GRID.h"
#include "SURFACE.h"
#include "GAD.h"

C !INPUT/OUTPUT PARAMETERS:
C == Routine Arguments ==
C implicitAdvection :: if True, treat vertical advection implicitly
C advectionScheme   :: advection scheme to use
C tracerIdentity    :: Identifier for the tracer
C kappaRX           :: 3-D array for vertical diffusion coefficient
C recip_hFac        :: inverse of cell open-depth factor
C wFld              :: Advection velocity field, vertical component
C tracer            :: tracer field at current time step
C gTracer           :: future tracer field
C bi,bj             :: tile indices
C myTime            :: current time
C myIter            :: current iteration number
C myThid            :: thread number
      LOGICAL implicitAdvection
      INTEGER advectionScheme
      INTEGER tracerIdentity
      _RL     deltaTLev(Nr)
      _RL kappaRX   (1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RS recip_hFac(1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RL wFld      (1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RL tracer    (1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RL gTracer   (1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      INTEGER bi, bj
      _RL myTime
      INTEGER myIter, myThid

#ifdef ALLOW_DIAGNOSTICS
C !FUNCTIONS:
      CHARACTER*4 GAD_DIAG_SUFX
      EXTERNAL    GAD_DIAG_SUFX
      LOGICAL     DIAGNOSTICS_IS_ON
      EXTERNAL    DIAGNOSTICS_IS_ON
#endif

C !LOCAL VARIABLES:
C == Local variables ==
C iMin,iMax,jMin,jMax :: computational domain
C i,j,k     :: loop indices
C a5d       :: 2nd  lower diagonal of the pentadiagonal matrix
C b5d       :: 1rst lower diagonal of the pentadiagonal matrix
C c5d       :: main diagonal       of the pentadiagonal matrix
C d5d       :: 1rst upper diagonal of the pentadiagonal matrix
C e5d       :: 2nd  upper diagonal of the pentadiagonal matrix
C rTrans    :: vertical volume transport at interface k
C localTr   :: local copy of tracer (for Non-Lin Adv.Scheme)
C diagonalNumber :: number of non-zero diagonals in the matrix
C errCode   :: > 0 if singular matrix
C msgBuf    :: Informational/error message buffer
      INTEGER iMin,iMax,jMin,jMax
      PARAMETER( iMin = 1, iMax = sNx )
      PARAMETER( jMin = 1, jMax = sNy )
      INTEGER i,j,k
      INTEGER diagonalNumber, errCode
      _RL a5d(1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RL b5d(1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RL c5d(1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RL d5d(1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RL e5d(1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
      _RL rTrans (1-OLx:sNx+OLx,1-OLy:sNy+OLy)
      _RL localTr(1-OLx:sNx+OLx,1-OLy:sNy+OLy,Nr)
#ifdef ALLOW_DIAGNOSTICS
      CHARACTER*8 diagName
      CHARACTER*4 diagSufx
      LOGICAL     diagDif, diagAdv
      INTEGER km1, km2, kp1, kp2
      _RL df (1-OLx:sNx+OLx,1-OLy:sNy+OLy)
      _RL af (1-OLx:sNx+OLx,1-OLy:sNy+OLy)
      _RL div(1-OLx:sNx+OLx,1-OLy:sNy+OLy)
      _RL flx(1-OLx:sNx+OLx,1-OLy:sNy+OLy)
# ifdef SOLVE_DIAGONAL_LOWMEMORY
      CHARACTER*(MAX_LEN_MBUF) msgBuf
# endif /* SOLVE_DIAGONAL_LOWMEMORY */
#endif /* ALLOW_DIAGNOSTICS */
CEOP

C--   no need to solve anything with only 1 level:
      IF (Nr.GT.1) THEN

C--   Initialise
      DO k=1,Nr
       DO j=1-OLy,sNy+OLy
        DO i=1-OLx,sNx+OLx
         a5d(i,j,k) = 0. _d 0
         b5d(i,j,k) = 0. _d 0
         c5d(i,j,k) = 1. _d 0
         d5d(i,j,k) = 0. _d 0
         e5d(i,j,k) = 0. _d 0
        ENDDO
       ENDDO
      ENDDO
      diagonalNumber = 1

      IF (implicitDiffusion) THEN
C--   set the tri-diagonal matrix to solve the implicit diffusion problem
       diagonalNumber = 3
C-     1rst lower diagonal :
       DO k=2,Nr
        DO j=jMin,jMax
         DO i=iMin,iMax
           b5d(i,j,k) = -deltaTLev(k)*maskC(i,j,k-1,bi,bj)
     &                  *recip_hFac(i,j,k)*recip_drF(k)
     &                  *recip_deepFac2C(k)*recip_rhoFacC(k)
     &                  *kappaRX(i,j, k )*recip_drC( k )
     &                  *deepFac2F( k )*rhoFacF( k )
         ENDDO
        ENDDO
       ENDDO
C-     1rst upper diagonal :
       DO k=1,Nr-1
        DO j=jMin,jMax
         DO i=iMin,iMax
           d5d(i,j,k) = -deltaTLev(k)*maskC(i,j,k+1,bi,bj)
     &                  *recip_hFac(i,j,k)*recip_drF(k)
     &                  *recip_deepFac2C(k)*recip_rhoFacC(k)
     &                  *kappaRX(i,j,k+1)*recip_drC(k+1)
     &                  *deepFac2F(k+1)*rhoFacF(k+1)
         ENDDO
        ENDDO
       ENDDO
C-     Main diagonal :
       DO k=1,Nr
        DO j=jMin,jMax
         DO i=iMin,iMax
           c5d(i,j,k) = 1. _d 0 - ( b5d(i,j,k) + d5d(i,j,k) )
C      to recover older (prior to 2016-10-05) results:
c          c5d(i,j,k) = 1. _d 0 - b5d(i,j,k) - d5d(i,j,k)
         ENDDO
        ENDDO
       ENDDO

C--   end if implicitDiffusion
      ENDIF

      IF (implicitAdvection) THEN

C--   Non-Linear Advection scheme: keep a local copy of tracer field
       IF ( advectionScheme.EQ.ENUM_FLUX_LIMIT .OR.
     &      advectionScheme.EQ.ENUM_DST3_FLUX_LIMIT ) THEN
        IF ( multiDimAdvection ) THEN
         DO k=1,Nr
          DO j=1-OLy,sNy+OLy
           DO i=1-OLx,sNx+OLx
            localTr(i,j,k) = gTracer(i,j,k)
           ENDDO
          ENDDO
         ENDDO
        ELSE
         DO k=1,Nr
          DO j=1-OLy,sNy+OLy
           DO i=1-OLx,sNx+OLx
            localTr(i,j,k) = tracer(i,j,k)
           ENDDO
          ENDDO
         ENDDO
        ENDIF
       ENDIF

       DO k=Nr,1,-1

C--    Compute transport
        IF (k.EQ.1) THEN
         DO j=1-OLy,sNy+OLy
          DO i=1-OLx,sNx+OLx
            rTrans(i,j) = 0. _d 0
          ENDDO
         ENDDO
        ELSE
         DO j=1-OLy,sNy+OLy
          DO i=1-OLx,sNx+OLx
            rTrans(i,j) = wFld(i,j,k)*rA(i,j,bi,bj)
     &                               *deepFac2F(k)*rhoFacF(k)
     &                               *maskC(i,j,k-1,bi,bj)
          ENDDO
         ENDDO
        ENDIF

#ifdef ALLOW_AIM
C- a hack to prevent Water-Vapor vert.transport into the stratospheric level Nr
        IF ( k.GE.2 .AND.
     &     (.NOT.useAIM .OR.tracerIdentity.NE.GAD_SALINITY .OR.k.LT.Nr)
     &              ) THEN
#else
        IF ( k.GE.2 ) THEN
#endif

         IF ( advectionScheme.EQ.ENUM_CENTERED_2ND ) THEN
          diagonalNumber = 3
          CALL GAD_C2_IMPL_R( bi,bj,k, iMin,iMax,jMin,jMax,
     I                        deltaTLev, rTrans, recip_hFac,
     U                        b5d, c5d, d5d,
     I                        myThid )
         ELSEIF ( advectionScheme.EQ.ENUM_UPWIND_1RST
     &       .OR. advectionScheme.EQ.ENUM_DST2 ) THEN
          diagonalNumber = 3
          CALL GAD_DST2U1_IMPL_R( bi,bj,k, iMin,iMax,jMin,jMax,
     I                        advectionScheme, deltaTLev,
     I                        rTrans, recip_hFac,
     U                        b5d, c5d, d5d,
     I                        myThid )
         ELSEIF ( advectionScheme.EQ.ENUM_FLUX_LIMIT ) THEN
          diagonalNumber = 3
          CALL GAD_FLUXLIMIT_IMPL_R( bi,bj,k, iMin,iMax,jMin,jMax,
     I                        deltaTLev, rTrans, recip_hFac, localTr,
     U                        b5d, c5d, d5d,
     I                        myThid )
         ELSEIF ( advectionScheme.EQ.ENUM_UPWIND_3RD
     &       .OR. advectionScheme.EQ.ENUM_CENTERED_4TH
     &       .OR. advectionScheme.EQ.ENUM_DST3 ) THEN
          diagonalNumber = 5
          CALL GAD_U3C4_IMPL_R( bi,bj,k, iMin,iMax,jMin,jMax,
     I                        advectionScheme, deltaTLev,
     I                        rTrans, recip_hFac,
     U                        a5d, b5d, c5d, d5d, e5d,
     I                        myThid )
         ELSEIF ( advectionScheme.EQ.ENUM_DST3_FLUX_LIMIT ) THEN
          diagonalNumber = 5
          CALL GAD_DST3FL_IMPL_R( bi,bj,k, iMin,iMax,jMin,jMax,
     I                        deltaTLev, rTrans, recip_hFac, localTr,
     U                        a5d, b5d, c5d, d5d, e5d,
     I                        myThid )
         ELSE
          STOP 'GAD_IMPLICIT_R: Adv.Scheme in Impl form not yet coded'
         ENDIF

        ENDIF

C--     end k loop
       ENDDO

C--   end if implicitAdvection
      ENDIF

      IF ( diagonalNumber .EQ. 3 ) THEN
C--   Solve tri-diagonal system :
        errCode = -1
        CALL SOLVE_TRIDIAGONAL( iMin,iMax, jMin,jMax,
     I                          b5d, c5d, d5d,
     U                          gTracer,
     O                          errCode,
     I                          bi, bj, myThid )
        IF (errCode.GE.1) THEN
          STOP 'GAD_IMPLICIT_R: error when solving 3-Diag problem'
        ENDIF
      ELSEIF ( diagonalNumber .EQ. 5 ) THEN
C--   Solve penta-diagonal system :
        errCode = -1
        CALL SOLVE_PENTADIAGONAL( iMin,iMax, jMin,jMax,
     I                            a5d, b5d, c5d, d5d, e5d,
     U                            gTracer,
     O                            errCode,
     I                            bi, bj, myThid )
        IF (errCode.GE.1) THEN
          STOP 'GAD_IMPLICIT_R: error when solving 5-Diag problem'
        ENDIF
      ELSEIF ( diagonalNumber .NE. 1 ) THEN
        STOP 'GAD_IMPLICIT_R: no solver available'
      ENDIF

#ifdef ALLOW_DIAGNOSTICS
C--   Set diagnostic suffix for the current tracer
      IF ( useDiagnostics ) THEN
        diagSufx = GAD_DIAG_SUFX( tracerIdentity, myThid )
        diagName = 'DFrI'//diagSufx
        diagDif = implicitDiffusion
        IF ( diagDif ) diagDif = DIAGNOSTICS_IS_ON(diagName,myThid)
        diagName = 'ADVr'//diagSufx
        diagAdv = implicitAdvection
        IF ( diagAdv ) diagAdv = DIAGNOSTICS_IS_ON(diagName,myThid)

        IF ( diagDif .OR. diagAdv ) THEN
         DO j=1-OLy,sNy+OLy
          DO i=1-OLx,sNx+OLx
            flx(i,j) = 0. _d 0
          ENDDO
         ENDDO
C--      start diagnostics k loop
         DO k= Nr,1,-1

          IF ( implicitDiffusion .AND. k.GE.2 ) THEN
            DO j=jMin,jMax
             DO i=iMin,iMax
               df(i,j) =
cc#ifdef ALLOW_AUTODIFF_OPENAD
cc     &             -rA(i,j,bi,bj)%v
cc#else
     &             -rA(i,j,bi,bj)*deepFac2F(k)*rhoFacF(k)
cc#endif
     &            * kappaRX(i,j,k)*recip_drC(k)*rkSign
     &            * (gTracer(i,j,k) - gTracer(i,j,k-1))
     &            * maskC(i,j,k,bi,bj)
     &            * maskC(i,j,k-1,bi,bj)
             ENDDO
            ENDDO
          ELSE
            DO j=1-OLy,sNy+OLy
             DO i=1-OLx,sNx+OLx
               df(i,j) = 0. _d 0
             ENDDO
            ENDDO
          ENDIF

C-  Note: Needs to explicitly increment counter (call DIAGNOSTICS_COUNT)
C         since skipping k=1 DIAGNOSTICS_FILL call.
          IF ( diagDif .AND. k.GE.2 ) THEN
           diagName = 'DFrI'//diagSufx
           CALL DIAGNOSTICS_FILL(df,diagName, k,1, 2,bi,bj, myThid)
           IF ( k.EQ.2 ) CALL DIAGNOSTICS_COUNT(diagName,bi,bj,myThid)
#ifdef ALLOW_LAYERS
           IF ( useLayers ) THEN
             CALL LAYERS_FILL( df, tracerIdentity, 'DFR',
     &                           k, 1, 2,bi,bj, myThid )
           ENDIF
#endif /* ALLOW_LAYERS */
          ENDIF

          IF ( diagAdv ) THEN
#ifdef SOLVE_DIAGONAL_LOWMEMORY
           diagName = 'ADVr'//diagSufx
           WRITE(msgBuf,'(4A)') 'GAD_IMPLICIT_R: ',
     &      'unable to compute Diagnostic "', diagName, '" with'
           CALL PRINT_MESSAGE( msgBuf, errorMessageUnit,
     &                         SQUEEZE_RIGHT, myThid )
           WRITE(msgBuf,'(4A)') 'GAD_IMPLICIT_R: ',
     &      '#define SOLVE_DIAGONAL_LOWMEMORY (in CPP_OPTIONS.h)'
           CALL PRINT_MESSAGE( msgBuf, errorMessageUnit,
     &                         SQUEEZE_RIGHT, myThid )
           STOP 'ABNORMAL END: S/R GAD_IMPLICIT_R'
#endif /* SOLVE_DIAGONAL_LOWMEMORY */
           km1=MAX(1,k-1)
           km2=MAX(1,k-2)
           kp1=MIN(Nr,k+1)
           kp2=MIN(Nr,k+2)
C--   Flux_divergence*deltaT = Tr^n - Tr^n+1 = [A-I](Tr^n+1)
C                            = deltaT*rkSign*[ Flx_k+1 - Flx_k ]/dz
           DO j=jMin,jMax
            DO i=iMin,iMax
              div(i,j) = gTracer(i,j,k)*( c5d(i,j,k) - 1. _d 0 )
     &                 + gTracer(i,j,km1)*b5d(i,j,k)
     &                 + gTracer(i,j,kp1)*d5d(i,j,k)
            ENDDO
           ENDDO
           IF ( diagonalNumber .EQ. 5 ) THEN
            DO j=jMin,jMax
             DO i=iMin,iMax
              div(i,j) = div(i,j)
     &                 + gTracer(i,j,km2)*a5d(i,j,k)
     &                 + gTracer(i,j,kp2)*e5d(i,j,k)
             ENDDO
            ENDDO
           ENDIF
#ifdef NONLIN_FRSURF
           IF ( nonlinFreeSurf.GT.0 ) THEN
C--    use future hFac to stay consistent with solver matrix
            IF ( select_rStar.GT.0 ) THEN
             DO j=jMin,jMax
              DO i=iMin,iMax
               div(i,j) = div(i,j)*h0FacC(i,j,k,bi,bj)*drF(k)
     &                            *rStarFacC(i,j,bi,bj)
              ENDDO
             ENDDO
            ELSEIF ( selectSigmaCoord.NE.0 ) THEN
             DO j=jMin,jMax
              DO i=iMin,iMax
               div(i,j) = div(i,j)*(
     &               _hFacC(i,j,k,bi,bj)*drF(k)
     &              + dBHybSigF(k)*dEtaHdt(i,j,bi,bj)*deltaTFreeSurf
     &                             )
              ENDDO
             ENDDO
            ELSE
             DO j=jMin,jMax
              DO i=iMin,iMax
               IF ( k.EQ.kSurfC(i,j,bi,bj) ) THEN
                div(i,j) = div(i,j)*hFac_surfC(i,j,bi,bj)*drF(k)
               ELSE
                div(i,j) = div(i,j)*_hFacC(i,j,k,bi,bj)*drF(k)
               ENDIF
              ENDDO
             ENDDO
            ENDIF
           ELSE
#else /* NONLIN_FRSURF */
           IF ( .TRUE. ) THEN
#endif /* NONLIN_FRSURF */
C--    use current hFac (consistent with solver matrix)
             DO j=jMin,jMax
              DO i=iMin,iMax
               div(i,j) = div(i,j)*_hFacC(i,j,k,bi,bj)*drF(k)
              ENDDO
             ENDDO
           ENDIF
           DO j=jMin,jMax
            DO i=iMin,iMax
              flx(i,j) = flx(i,j)
cc#ifdef ALLOW_AUTODIFF_OPENAD
cc     &                - rkSign*div(i,j)*rA(i,j,bi,bj)%v/deltaTLev(k)
cc#else
     &                - rkSign*div(i,j)*rA(i,j,bi,bj)
     &                        *deepFac2C(k)*rhoFacC(k)/deltaTLev(k)
cc#endif
              af(i,j) = flx(i,j) - df(i,j)
            ENDDO
           ENDDO
           diagName = 'ADVr'//diagSufx
           CALL DIAGNOSTICS_FILL(af,diagName, k,1, 2,bi,bj, myThid)
#ifdef ALLOW_LAYERS
           IF ( useLayers ) THEN
             CALL LAYERS_FILL(af,tracerIdentity,'AFR',
     &                            k,1,2,bi,bj,myThid)
           ENDIF
#endif /* ALLOW_LAYERS */
          ENDIF

C--      end diagnostics k loop
         ENDDO
        ENDIF
      ENDIF
#endif /* ALLOW_DIAGNOSTICS */

C--   end if Nr > 1
      ENDIF

      RETURN
      END
