#Metview Macro

# **************************** LICENSE START ***********************************
#
# Copyright 2019 ECMWF. This software is distributed under the terms
# of the Apache License version 2.0. In applying this license, ECMWF does not
# waive the privileges and immunities granted to it by virtue of its status as
# an Intergovernmental Organization or submit itself to any jurisdiction.
#
# ***************************** LICENSE END ************************************

# **************************************************************************
# Function      : ml_to_hl
#
# Syntax        : fieldset ml_to_hl (f: fieldset, t:fieldset, q:fieldset, lnsp:fieldset, zs:fieldset)
#
# Category      : COMPUTATION
#
# OneLineDesc   : Interpolates model level fields to height levels
#
# Description   : Interpolates model level fields to height levels
#
# Return Value  : Fieldset interpolated to height levels
#
# Dependencies  : None
#
# **************************************************************************

function ml_to_hl(f_fs: fieldset, t_fs:fieldset, q_fs:fieldset, lnsp_fs:fieldset, zs_fs:fieldset, 
                  hlevs:list, href: string, method: string)

    z_fs = mvl_geopotential_on_ml(t_fs, q_fs, lnsp_fs, zs_fs)
    return _ml_to_hl(f_fs, z_fs, zs_fs, hlevs, href, method)

end ml_hl

function ml_to_hl(f_fs: fieldset, z_fs:fieldset, zs_fs, hlevs, href:string, method:string)

    f_name = grib_get_string(f_fs[1], "shortName")
    z_name = grib_get_string(z_fs[1], "shortName")
    
    zs_name = ""
    if zs_fs <> nil then
        zs_name = grib_get_string(zs_fs[1], "shortName")
    end if
     
    # check vertical reference
    if href <> "sea" and href <> "ground" then
        fail(ml_to_hl & ": invalid vertical reference (=\"" & href & "\") specified! Should be \"sea\" or \"ground\"")
    end if   
     
    eccTargetLevType = "" 
    if href = "sea" then
        eccTargetLevType = "heightAboveSea"
    else if href = "ground" then
        eccTargetLevType = "heightAboveGround"    
    end if
    
    opt = (caller: "ml_to_hl",           
           fPar: f_name,
           vcPar: z_name,
           surfVcPar: zs_name,
           srcLevType: "ml",
           targetLevType: "hl",
           eccSrcLevType: "hybrid",
           eccTargetLevType: eccTargetLevType,
           targetLevs: hlevs,
           method: method
          )
     
     return __vert_interpolate_core(f_fs, z_fs, zs_fs, opt)     

end ml_to_hl           

function __vert_interpolate_core(f_fs: fieldset, vc_fs:fieldset, surf_vc_fs, opt)
    
    __DEBUG_VERT_INTERPOLATE = 0
    
    if __DEBUG_VERT_INTERPOLATE then
        print("opt=", opt)
    end if
    
    # check field count
    if count(f_fs) <> count(vc_fs) then
        fail(opt.caller & ": different number of input fields in " & opt.fPar & "and " &
             opt.vcPar & "! " & count(f_fs) & " != " & count(vc_fs))
    end if          

    # we need to use it because when called from Python
    # sort_indices() returns 0-based index values and it cannot be used
    # for indexing fieldsets, which expects 1-based values.
    # So we need to adjust the result of sort_indices() to make
    # ml_to_hl() work in Python.
    fs_index_offset = 0
    if base_language = 'python' then 
	    fs_index_offset = -1
	end if

    # check levels
    meta_f = grib_get(f_fs, ["typeOfLevel", "level"])
    meta_vc= grib_get(vc_fs, ["typeOfLevel", "level"])
    for i=1 to count(meta_f) do
        if meta_f[i][1] <> opt.eccSrcLevType then
            fail(opt.caller & ": invalid level type (=" & meta_f[i] & ") for " &
                 opt.fPar & " field #" & i+fs_index_offset &
                 "! Has to be " & opt.eccSrcLevType & "!")
        end if
        if meta_vc[i][1] <> opt.eccSrcLevType then
            fail(opt.caller & ": invalid level type (=" & meta_vc[i] & ") for " &
                 opt.vcPar & " field #" & i+fs_index_offset &
                 "! Has to be " & opt.eccSrcLevType & "!")
        end if  
    end for

    # if the target is height level the vertical coordinate (vc) must be geopotential.
    # We convert the geopotential to geometric height (above sea or ground according to the
    # settings).
    if opt.targetLevType = "hl" then
        
        # height above sea
        if opt.eccTargetLevType = "heightAboveSea" then
            vc_fs = vc_fs / 9.81
        
        # height above ground
        else if opt.eccTargetLevType = "heightAboveGround" then
            if type(surf_vc_fs) <> "fieldset" then
                fail(opt.caller & ": invalid " & opt.surfVcPar & " surface field specified!")
            end if   
           
            h_fs = nil
            for i=1 to count(vc_fs) do
                h_fs = h_fs & (vc_fs[i] - surf_vc_fs[1])/9.81
            end for
            vc_fs = h_fs             
        end if 
    end if
    
    # determine if the src and target coordinate system is 
    # ascending or descending in height
    srcAscending = 0
    targetAscending = 0
  
    if opt.srcLevType = "ml" or opt.srcLevType = "pl" then
       srcAscending = 0  
    else if opt.srcLevType = "hl" then
       srcAscending = 1 
    end if
    
    if opt.targetLevType = "pl" then
       targetAscending = 0  
    else if opt.targetLevType = "hl" then
       targetAscending = 1
    end if
      
    # We do not sort the fieldsets themselves to avoid I/O 
    # but store the sort indices!
    srcLevs_vc = grib_get_double(vc_fs, "level")
    srcLevs_f = grib_get_double(f_fs, "level")
    
    # e.g. ml -> pl
    if srcAscending = 0 and targetAscending = 0 then
        fsIndex_vc = sort_indices(srcLevs_vc) 
        fsIndex_f = sort_indices(srcLevs_f)  
    # e.g. ml -> hl or pl -> hl  
    else if srcAscending = 0 and targetAscending = 1 then
        # sort in descending ML order: e.g. [137->1]!
        # E.g. fsIndex[1] should tell us the index of the field at the bottom!
        fsIndex_vc = sort_indices(srcLevs_vc, ">")
        fsIndex_f = sort_indices(srcLevs_f, ">")    
    # e.g. hl -> pl    
    else if srcAscending = 1 and targetAscending = 0 then
        fsIndex_vc = sort_indices(srcLevs_vc, ">")
        fsIndex_f = sort_indices(srcLevs_f, ">")        
    else 
        # assert()   
    end if         
        
    # we need to adjust these values to make the function work from Python
    fsIndex_vc = fsIndex_vc - fs_index_offset 
    fsIndex_f = fsIndex_f - fs_index_offset 
        
    if __DEBUG_VERT_INTERPOLATE then    
        print("srcAscending=",srcAscending)
        print("targetAscending=",targetAscending)
        print("fsIndex_vc=",fsIndex_vc)
        print("fsIndex_f=",fsIndex_f)    
    end if
     
    # check if the levels are the same in the sorted input fieldsets  
    for i=1 to count(meta_f) do
        idx_f = fsIndex_f[i]
        idx_vc = fsIndex_vc[i]
        if meta_f[idx_f][2] <> meta_vc[idx_vc][2] then
            fail(opt.fnName & ": " & opt.fPar & " and " & 
                 opt.vcPar & " field levels are not matching is sorted input fields #" & i+fs_index_offset & 
                 "! " & opt.fPar & " level=" & meta_f[idx_f][2] & "  " & opt.vcPar &
                 " level=" & meta_vc[idx_vc][2])
        end if   
    end for   
          
    # determine min and max value for each vertical coordinate field
    vcMin = nil
    vcMax = nil
    for i=1 to count(fsIndex_vc) do
        idx = fsIndex_vc[i]
        vcMin = vcMin & [minvalue(vc_fs[idx])]
        vcMax = vcMax & [maxvalue(vc_fs[idx])]
    end for     
        
    iMin = nil
    iMax = nil           
    for ihl = 1 to count(opt.targetLevs) do
        hh = opt.targetLevs[ihl]
        
        if type(hh) = "fieldset" then
            hh_max = maxvalue(hh)
            hh_min = minvalue(hh)
        else
            hh_max = hh
            hh_min = hh
        end if
        
        i1 = 1
        i2 = count(vc_fs)
        
        # e.g. ml -> pl
        if srcAscending = 0 and targetAscending = 0 then        
            for i=2 to count(vc_fs)-1 do          
                if (vcMax[i] < hh) then
                    i1 = i
                end if       
            end for            
            for i=count(vc_fs)-1 to 2 by -1 do          
                if ( vcMin[i] > hh) then
                    i2 = i
                end if
            end for  
         
         # e.g. ml -> hl
         else if srcAscending = 0 and targetAscending = 1  then        
            for i=2 to count(vc_fs)-1 do          
                if (vcMax[i] < hh_min) then
                    i1 = i
                end if       
            end for            
            for i=count(vc_fs)-1 to 2 by -1 do          
                if (vcMin[i] > hh_max) then
                    i2 = i
                end if
            end for        
        end if
                 
        iMin = iMin & [i1]
        iMax = iMax & [i2]
        
        if __DEBUG_VERT_INTERPOLATE then
            print("hh=[" & hh_min & "," & hh_max & "]")
            print(" i1=" & (i1+fs_index_offset) & " i2=" & (i2+fs_index_offset))
            print(" lev1=" & srcLevs_vc[fsIndex_vc[i1]] & " lev2=" & srcLevs_vc[fsIndex_vc[i2]])
            print(" levMin=" & vcMin[i1] & " levMax=" & vcMax[i2])
        end if
        
        if i1 > i2 then
            if type(hh) = "fieldset" then
                fail(opt.caller & ": invalid model level selection for target level field: idx=" &
                        ihl & " hh=[" & hh_min & "," & hh_max & "]")
            else
              fail(opt.caller & ": invalid model level selection for target level=" & hh)
            end if
        end if    
    end for
  
    addDeltaToLevel = 0
    if opt.eccTargetLevType = "heightAboveGround" and        
       (opt.fPar = "u" or opt.fPar = "v" or opt.fPar = "ws") then
       addDeltaToLevel = 1
    end if    
  
    # interpolate the f fields onto the target height levels
    res = nil
    for ih=1 to count(opt.targetLevs) do
        
        # the target height level
        hh=opt.targetLevs[ih]
    
        # build fielsets:
        # h1, h2: in each gridpoint contain the height values on the
        #         bounding model levels
        # f1, f2: in each gridpoint contain the field values to interpolate 
        #         values on the bounding model levels
        idx_vc = fsIndex_vc[iMin[ih]]
        h1 = 0 * vc_fs[idx_vc]
        h2 = h1
        
        idx_f = fsIndex_f[iMin[ih]]
        f1 = 0.* f_fs[idx_f]
        f2 = f1
       
        for i = iMin[ih] to iMax[ih] do         
            idx_vc = fsIndex_vc[i]
            idxNext_vc = fsIndex_vc[i+1]
            
            idx_f = fsIndex_f[i]
            idxNext_f = fsIndex_f[i+1]
            
            hmask = (vc_fs[idx_vc] <= hh) and (hh < vc_fs[idxNext_vc])
            h1 = h1 + hmask*vc_fs[idx_vc]
            h2 = h2 + hmask*vc_fs[idxNext_vc]
            
            f1 = f1 + hmask*f_fs[idx_f]
            f2 = f2 + hmask*f_fs[idxNext_f]
        end for

        # bitmap the points where no proper values were found
        h1 = bitmap(h1, 0.)
        h2 = bitmap(h2, 0.)

        # interpolation
        if opt.method = 'linear' then
            #r = f1 + (f2-f1)*(hh-h1)/(h2-h1)
            r = (f1*(h2-hh)+ f2*(hh-h1))/(h2-h1)
        else if opt.method = 'log' then
            r = f1 + (f2-f1)*log(hh/h1)/log(h2/h1)
        end if
         
        if type(hh) <> "fieldset" then
            # if the target typeOfLevel is "heightAboveGround" for certain levels and 
            # certain paremeters ecCodes silently changes the shortName!!!
            # E.g. if we set the level as 10m for param "ws" it becomes "10si"!!!
            # Here we try to avoid it by adding a small delta to the level!!! 
            if addDeltaToLevel and        
              (hh = 10 or hh = 100 or hh = 200 ) then            
                r = grib_set(r, ["typeOfLevel", opt.eccTargetLevType, "level", hh+0.00001])
            # normal case
            else
               r = grib_set(r, ["typeOfLevel", opt.eccTargetLevType, "level", hh])   
            end if
        end if
        
        # append interpolated field to result
        res = res & r
    end for
    
    return res
    
end __vert_interpolate_core           