!--------------------------------------- LICENCE BEGIN -----------------------------------
!Environment Canada - Atmospheric Science and Technology License/Disclaimer,
!                     version 3; Last Modified: May 7, 2008.
!This is free but copyrighted software; you can use/redistribute/modify it under the terms
!of the Environment Canada - Atmospheric Science and Technology License/Disclaimer
!version 3 or (at your option) any later version that should be found at:
!http://collaboration.cmc.ec.gc.ca/science/rpn.comm/license.html
!
!This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
!without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
!See the above mentioned License/Disclaimer for more details.
!You should have received a copy of the License/Disclaimer along with this software;
!if not, you can write to: EC-RPN COMM Group, 2121 TransCanada, suite 500, Dorval (Quebec),
!CANADA, H9P 1J3; or send e-mail to service.rpn@ec.gc.ca
!-------------------------------------- LICENCE END --------------------------------------


      SUBROUTINE SETFGEGPS(lcolumn,lcolumng,lobsSpaceData) 2,69
!*
!***s/r  -SETFGEGPS Sets first-guess error for all GB-GPS ZTD observations
!*
!*Author  : S. Macpherson *ARMA/MSC  December 2004
!*
!*Revisions:
!*
!*        S. Macpherson *ARMA/MSC  18 March 2010
!*           - add optional NL, TL and AD operator tests
!*        S. Macpherson *ARMA/MSC   August 2010
!*           - use new GPS ZTD observation operator (from GPS-RO modules)
!*        S. Macpherson *ARMA/MSC   December 2012
!*           - update from Rev189 to Rev213
!            - use new ZTD-specific GPS modules modgps04profilezd, modgps08ztdop
!            - LTESTOP option now set in 3dvar namelist
!            - if numGPSZTD=0, does nothing and returns
!*    -------------------
!**    Purpose: Set FGE for all GPS ZTD observations using
!*                Jacobians from ZTD observation operator
!         OPTION: Test ZTD operators (compares H(x+dx)-H(x) with (dH/dx)*dx
!                 when LTESTOP = .true.)
!*
      use EarthConstants_mod
      use MathPhysConstants_mod
      use modgps04profilezd
      use modgps08ztdop
      use bufr
      use columnData_mod
      use obsSpaceData_mod
      use verticalCoord_mod
      use modgpsztd_mod
      IMPLICIT NONE
!   lcolumn  contains background errors for control variables on model levels
!   lcolumng contains lo-res first guess profiles at obs locations
      type(struct_columnData) :: lcolumn, lcolumng
      type(struct_obs) :: lobsSpaceData
      type(struct_vco), pointer :: vco_anl
      REAL*8 ZLAT, Lat
      REAL*8 ZLON, Lon
      REAL*8, allocatable :: ZPP(:)
      REAL*8, allocatable :: ZDP(:)
      REAL*8, allocatable :: ZTT(:)
      REAL*8, allocatable :: ZHU(:)
      REAL*8, allocatable :: ZGZ(:)
      REAL*8, allocatable :: ZTTB(:)
      REAL*8, allocatable :: ZHUB(:)
      REAL*8, allocatable :: ZQQB(:)
      REAL*8, allocatable :: ZQQ(:)
      REAL*8, allocatable :: ZTTB_P(:)
      REAL*8, allocatable :: ZQQB_P(:)
      REAL*8, allocatable :: RZHUB_P(:)
      REAL*8, allocatable :: ZPP_P(:)
      
      REAL*8 ZP0
      REAL*8 ZP0B, ZP0B_P
      REAL*8 ZPT, ZPR, ZCF
      REAL*8 ZMT, ZTOP, ZBOT
 
      REAL*8 JAC(ngpscvmx)
      REAL*8 DX (ngpscvmx)

      REAL*8 ZOER, ZLEV, ZTDOBS, ZVAR, ZPSMOD
      REAL*8 ZJP0, ZLSUM
      REAL*8 DELTAH_NL, DELTAH_TL
      REAL*8 PERTFAC, ZTDM
      REAL*8 ZDZMIN, ZSUMTEST

      INTEGER INDEX_HEADER, FIRST_HEADER
      INTEGER IDATYP, ITYP
      INTEGER IDATA, IDATEND, INDEX_BODY
      INTEGER JL, JK, NFLEV_T, ILYR, IOBS
      INTEGER INOBS_OPT, INOBS_JAC, icount, status, iversion

      LOGICAL  ASSIM, LLOK, LSTAG
      CHARACTER*9  STN_JAC
      
      CHARACTER(len=2) :: varType
      
      TYPE(GPSPROFILEZD)    :: PRF, PRFP
      TYPE(GPSDIFF)         :: ZTDopv, ZTDopvP

      IF (numGPSZTD .EQ. 0) RETURN

!C
!C     * 1.  Initializations
!C     *     ---------------
!C
      NFLEV_T = col_getNumLev(lcolumng,'TH')
      allocate(ZPP(NFLEV_T))
      allocate(ZDP(NFLEV_T))
      allocate(ZTT(NFLEV_T))
      allocate(ZHU(NFLEV_T))
      allocate(ZGZ(NFLEV_T))
      allocate(ZTTB(NFLEV_T))
      allocate(ZHUB(NFLEV_T))
      allocate(ZQQB(NFLEV_T))
      allocate(ZQQ(NFLEV_T))

!c     Number of locations/sites for observation operator test
      INOBS_OPT = 50
!c     Number of locations/sites for Jacobian printout
      INOBS_JAC  = 5
!c     Factor to multiply background errors for perturbation vector
      PERTFAC = 0.75d0
!C
      STN_JAC = 'FSL_BRFT '
!c      
      ZDZMIN = DZMIN
!c
      vco_anl => col_getVco(lcolumng)
      status = vgd_get(vco_anl%vgrid,key='ig_1 - vertical coord code',value=iversion)
      if (iversion .eq. 5002) then
         LSTAG = .TRUE. 
         WRITE(*,*)'VERTICAL COORD OF ANALYSIS FIELDS IS STAGGERED'
         WRITE(*,*)'VCODE= ',iversion,' LSTAG= ',LSTAG
      else
         LSTAG = .FALSE.
         WRITE(*,*)'VERTICAL COORD OF ANALYSIS FIELDS IS NOT STAGGERED'
         WRITE(*,*)'VCODE= ',iversion,' LSTAG= ',LSTAG
         ZPR = vco_anl%dprf_M
         ZCF = vco_anl%drcf1
         WRITE(*,*) 'ZPR = ', ZPR
         WRITE(*,*) 'ZCF = ', ZCF
      endif

!C
      first_header=-1
      icount = 0
!C
      ! loop over all header indices of the 'GP' family
      call obs_set_current_header_list(lobsSpaceData,'GP')
      HEADER: do
         index_header = obs_getHeaderIndex(lobsSpaceData)
         if (index_header < 0) exit HEADER
         if (first_header .eq. -1) first_header = index_header
!C     
!C     *    .   Process only zenith delay data (codtyp 189 and BUFR_NEZD)
!C
               IDATYP = obs_headElem_i(lobsSpaceData,OBS_ITY,INDEX_HEADER)
               IF ( IDATYP .EQ. 189 ) THEN
!C
!C                 Loop over data in the observations
!C
                  IDATA   = obs_headElem_i(lobsSpaceData,OBS_RLN,INDEX_HEADER)
                  IDATEND = obs_headElem_i(lobsSpaceData,OBS_NLV,INDEX_HEADER) + IDATA - 1
                  ASSIM = .FALSE.
!C
!C                 Scan for requested assimilations, and count them.
!C
                  DO INDEX_BODY= IDATA, IDATEND
                     ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
                     LLOK = ( (ITYP .EQ. BUFR_NEZD) .AND. (obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY) .EQ. 1) )
                     IF ( LLOK ) THEN
                        ASSIM = .TRUE.
                        ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
                        icount = icount + 1
                     ENDIF
                  ENDDO
!C
!C     *           If assimilations are requested, apply the AD observation operator
!C
                  IF (ASSIM) THEN
!C     
!C     *        LR background profile and background errors at the observation location x :
!C
                     Lat  = obs_headElem_r(lobsSpaceData,OBS_LAT,INDEX_HEADER)
                     Lon  = obs_headElem_r(lobsSpaceData,OBS_LON,INDEX_HEADER)
                     ZLAT = Lat * MPC_DEGREES_PER_RADIAN_R8
                     ZLON = Lon * MPC_DEGREES_PER_RADIAN_R8
                     ZP0B = col_getElem(lcolumng,1,INDEX_HEADER,'P0')
                     DO JL = 1, NFLEV_T
                       ZPP(JL)  = col_getPressure(lcolumng,JL,INDEX_HEADER,'TH')
!C                     Get ZDP = dP/dP0
                       ZDP(JL)  = col_getPressureDeriv(lcolumng,JL,INDEX_HEADER,'TH')
                       ZTTB(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'TT')- 273.15d0
                       ZTT(JL)  = col_getElem(lcolumn,JL,INDEX_HEADER,'TT')
                       DX(JL)   = ZTT(JL)
                       ZHUB(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'HU')
                       ZQQB(JL) = EXP(ZHUB(JL))
                       ZHU(JL)  = col_getElem(lcolumn,JL,INDEX_HEADER,'HU')
                       DX(NFLEV_T+JL) = ZHU(JL)
                       ZGZ(JL)  = col_getHeight(lcolumng,JL,INDEX_HEADER,'TH')
                     ENDDO
                     ZP0  = col_getElem(lcolumn,1,INDEX_HEADER,'P0')
                     DX(2*NFLEV_T+1) = ZP0
                     ZPT  = col_getPressure(lcolumng,1,INDEX_HEADER,'TH')
                     ZMT  = ZGZ(NFLEV_T)/GRAV
                     CALL gpsstructztd(NFLEV_T,Lat,Lon,ZMT,ZPP,ZDP,ZTTB,ZHUB,LBEVIS,IREFOPT,PRF)
                     CALL gpsZTDopv(ZLEV,PRF,LBEVIS,ZDZMIN,ZTDopv,ZPSMOD,IZTDOP)
                     JAC = ZTDopv%DVar
!c
                     DO INDEX_BODY= IDATA, IDATEND
                        ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
                        IF ( obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1 .AND. ITYP.EQ.BUFR_NEZD ) THEN
!C
!C     *                    Observation error    SDERR
!c                           ZOER = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)

!C     *                    Observation height (m)
                           ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)

                           ZLSUM  = 0.0d0
!C
                           DO JL = 1, 2*NFLEV_T+1
                             ZLSUM = ZLSUM + (JAC(JL)*DX(JL))**2
                           ENDDO
                           call obs_bodySet_r(lobsSpaceData,OBS_HPHT,index_body,SQRT(ZLSUM))

                           IF (icount .LE. INOBS_JAC) THEN
!                           IF ( obs_elem_c(lobsSpaceData,'STID',INDEX_HEADER) .EQ. STN_JAC ) THEN
                             WRITE(*,'(A11,A9)') 'SETFGEGPS: ',obs_elem_c(lobsSpaceData,'STID',INDEX_HEADER)
                             WRITE(*,*) '  ZTD, ZTD FGE = ', ZTDopv%Var, SQRT(ZLSUM)
                             WRITE(*,'(A11,A9,3(1x,f7.2))')   &
                               'SETFGEGPS: ',obs_elem_c(lobsSpaceData,'STID',INDEX_HEADER),ZLAT,ZLON,ZLEV
                             WRITE(*,*) 'JL JACT JACQ FGE_T FGE_LQ QQ'
                             DO JL = 1, NFLEV_T
                               WRITE(*,'(1X,I2,5(1x,E13.6))') JL,JAC(JL),JAC(JL+NFLEV_T)/ZQQB(JL),ZTT(JL),ZHU(JL),ZQQB(JL)
                             ENDDO                         
                             WRITE(*,*) 'JACPS FGE_PS'
                             WRITE(*,'(2(1x,E13.6))') JAC(2*NFLEV_T+1), ZP0
                           ENDIF

                        ENDIF
                     ENDDO
                  ENDIF
               ENDIF

      ENDDO HEADER

!c---------------------------------------------------------------------------------------------------------------

      IF ( LTESTOP ) THEN
      
      allocate(ZTTB_P(NFLEV_T))
      allocate(ZQQB_P(NFLEV_T))
      allocate(RZHUB_P(NFLEV_T))
      allocate(ZPP_P(NFLEV_T))

      icount = 0
      ZSUMTEST = 0
!C
      ! loop over all header indices of the 'GP' family
      call obs_set_current_header_list(lobsSpaceData,'GP')
      HEADER2: DO
         index_header = obs_getHeaderIndex(lobsSpaceData)
         if (index_header < 0) exit HEADER2
         if (icount > INOBS_OPT ) exit HEADER2

!C       Loop over data in the observations
!C
         IDATA   = obs_headElem_i(lobsSpaceData,OBS_RLN,INDEX_HEADER)
         IDATEND = obs_headElem_i(lobsSpaceData,OBS_NLV,INDEX_HEADER) + IDATA - 1
!C     
!C       LR background profile and background errors at the observation location x :
!C
         Lat  = obs_headElem_r(lobsSpaceData,OBS_LAT,INDEX_HEADER)
         Lon  = obs_headElem_r(lobsSpaceData,OBS_LON,INDEX_HEADER)
         ZLAT = Lat * MPC_DEGREES_PER_RADIAN_R8
         ZLON = Lon * MPC_DEGREES_PER_RADIAN_R8
         ZP0B = col_getElem(lcolumng,1,INDEX_HEADER,'P0')
         DO JL = 1, NFLEV_T
            ZPP(JL)  = col_getPressure(lcolumng,JL,INDEX_HEADER,'TH')
!C          Get ZDP = dP/dP0
            ZDP(JL)  = col_getPressureDeriv(lcolumng,JL,INDEX_HEADER,'TH')
            ZTTB(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'TT')- 273.15d0
            ZTT(JL)  = col_getElem(lcolumn,JL,INDEX_HEADER,'TT') * PERTFAC
            ZHUB(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'HU')
            ZQQB(JL) = EXP(ZHUB(JL))
            ZHU(JL)  = col_getElem(lcolumn,JL,INDEX_HEADER,'HU')
            ZQQ(JL)  = ZQQB(JL) * ZHU(JL) * PERTFAC
            ZHU(JL)  = ZHU(JL) * PERTFAC
            ZGZ(JL)  = col_getHeight(lcolumng,JL,INDEX_HEADER,'TH')
         ENDDO
         ZP0  = col_getElem(lcolumn,1,INDEX_HEADER,'P0') * PERTFAC
         ZPT  = col_getPressure(lcolumng,1,INDEX_HEADER,'TH')
         ZMT  = ZGZ(NFLEV_T)/GRAV

         DO JL = 1, NFLEV_T
             DX (      JL) = ZTT(JL)
             DX (NFLEV_T+JL) = ZHU(JL)
         ENDDO
         DX (2*NFLEV_T+1) = ZP0

         ZTDOBS = -1.0d0
         DO INDEX_BODY = IDATA, IDATEND
           ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
           IOBS = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
           IF ( obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1 .AND. ITYP .EQ. BUFR_NEZD ) THEN
             varType = vnl_vartypeFromVarnum(ITYP)
             ZTDOBS  = obs_bodyElem_r(lobsSpaceData,OBS_VAR,INDEX_BODY)
             ZLEV    = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
             ILYR    = obs_bodyElem_i(lobsSpaceData,OBS_LYR,INDEX_BODY)
             ZTOP    = col_getHeight(lcolumng,ILYR,IOBS,varType)/GRAV
             if ( ILYR .LT. NFLEV_T ) then
               ZBOT    = col_getHeight(lcolumng,ILYR+1,IOBS,varType)/GRAV
             else
               ZBOT    = ZTOP
             endif
             icount  = icount + 1
           ENDIF
         ENDDO

         IF ( ZTDOBS .GT. 0.d0 ) THEN
!c         Create the pertubation control vector
           DO JL = 1, NFLEV_T
             ZPP_P(JL)  = ZPP(JL)  + ZDP(JL)*ZP0
             ZTTB_P(JL) = ZTTB(JL) + ZTT(JL)
             ZQQB_P(JL) = ZQQB(JL) + ZQQ(JL)
!c             RZHUB_P(JL) = RZHUB(JL) + ZHU(JL)
             RZHUB_P(JL) = LOG(ZQQB_P(JL))
           ENDDO
           ZP0B_P = ZP0B + ZP0
!C
!C         Non-linear observation operator --> delta_H = H(x+delta_x) - H(x)
!c
           CALL gpsstructztd(NFLEV_T,Lat,Lon,ZMT,ZPP,ZDP,ZTTB,ZHUB,LBEVIS,IREFOPT,PRF)
           CALL gpsstructztd(NFLEV_T,Lat,Lon,ZMT,ZPP_P,ZDP,ZTTB_P,RZHUB_P,LBEVIS,IREFOPT,PRFP)
           CALL gpsZTDopv(ZLEV,PRF,LBEVIS,ZDZMIN,ZTDopv,ZPSMOD,IZTDOP)
           JAC  = ZTDopv%DVar
           ZTDM = ZTDopv%Var
           CALL gpsZTDopv(ZLEV,PRFP,LBEVIS,ZDZMIN,ZTDopvP,ZPSMOD,IZTDOP)
           DELTAH_NL = ZTDopvP%Var - ZTDopv%Var
!c
!c         Linear  --> delta_H = dH/dx * delta_x
!c
           DELTAH_TL = 0.0d0
           DO JL = 1, 2*NFLEV_T+1
             DELTAH_TL = DELTAH_TL + JAC(JL)*DX(JL)
           ENDDO
!c
           WRITE(*,*) 'SETFGEGPS: GPS ZTD OBSOP TEST FOR SITE ', obs_elem_c(lobsSpaceData,'STID',INDEX_HEADER)
           WRITE(*,*) ' '
           WRITE(*,*) '  DZ (M), MODEL LEVEL ABOVE = ', ZLEV-ZMT, ILYR
           WRITE(*,*) '  ZLEV (M), ZTOP (M), ZBOT (M) = ', ZLEV, ZTOP, ZBOT
           WRITE(*,*) '  ZTD OBS (MM)            = ', ZTDOBS*1000.d0
           WRITE(*,*) '  ZTD_MOD                 = ', ZTDM*1000.d0
           WRITE(*,*) '  DELTAH_NL, DELTAH_TL = ', DELTAH_NL*1000.d0, DELTAH_TL*1000.d0
           WRITE(*,*) ' '
           WRITE(*,*) '  DELTAH_TL/DELTAH_NL = ', DELTAH_TL/DELTAH_NL
           WRITE(*,*) ' '  
           
           ZSUMTEST = ZSUMTEST + (DELTAH_TL/DELTAH_NL)
           
         ENDIF

      ENDDO HEADER2
      
      WRITE(*,*) ' '
      WRITE(*,*) 'SETFGEGPS: ----- GPS ZTD OBSOP TEST SUMMARY -----'
      WRITE(*,*) '           NUMBER OF TESTS (sites) = ', icount
      WRITE(*,*) '           AVG DELTAH_TL/DELTAH_NL = ', ZSUMTEST/FLOAT(icount)
      WRITE(*,*) ' '  

      deallocate(ZTTB_P)
      deallocate(ZQQB_P)
      deallocate(RZHUB_P)
      deallocate(ZPP_P)

      ENDIF
!-----------------------------------------------------------------------------------------------------------

      deallocate(ZPP)
      deallocate(ZDP)
      deallocate(ZTT)
      deallocate(ZHU)
      deallocate(ZGZ)
      deallocate(ZTTB)
      deallocate(ZHUB)
      deallocate(ZQQB)
      deallocate(ZQQ)

      RETURN
      END SUBROUTINE SETFGEGPS