#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)
    return ml_to_hl(f_fs, z_fs, zs_fs, hlevs, href, method, nil)
end ml_to_hl

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

    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
    
    if surf_fs <> nil then
        if href = "sea" then
            fail("ml_to_hl: cannot specify surface value/field when vertical reference=\"sea\"") 
        end if
 
        if type(surf_fs) = "number" or type(surf_fs) = "fieldset" then
            surf_fs = f_fs[1]*0 + surf_fs
        else if type(surf_fs) <> "fieldset" then
            fail("ml_to_hl: invalid surf_fs (type=" & type(surf_fs) & ") specified! Type must be nil, number or fieldset!")
        end if
        
        if zs_fs = nil then
            fail("ml_to_hl: invalid surface geopotential field (=nil) specified!")
        end if
        
        # add the surface f field and z field as a fake model level
        lev = maxvalue(vector(grib_get_double(f_fs, "level")))
        if lev = nil then
            fail("ml_to_hl: could not determine maximum level")
        end if
        lev = lev + 1
        lev = int(lev)
        
        # print("SFC level=", lev)
        surf_fs = grib_set_long(surf_fs, ["level", lev])
        f_fs = f_fs & surf_fs
           
        z_surf_fs = z_fs[1]*0 + zs_fs
        z_surf_fs = grib_set_long(z_surf_fs, ["level", lev])
        z_fs = z_fs & z_surf_fs
            
    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     
     
    #for i=57 to count(vcMin) do
    #     print("i=" & i & " " & vcMin[i] & " -> " & vcMax[i])   
    #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
           
        # out of range         
        if hh_min > vcMax[i2] or hh_max < vcMin[i1] then
            i1 = -1
            i2 = -1
        end if        
      
        if i1 = i2 then
            if i2 = count(vc_fs) then
                i1 = -1
                i2 = -1
            else
                i2 = i1+2
            end if
        end if
          
        iMin = iMin & [i1]
        iMax = iMax & [i2]
        
        if __DEBUG_VERT_INTERPOLATE then
            print("hh=[" & hh_min & "," & hh_max & "]")
            if i1 = -1 or i2 = -1 then
                print(" out of range. Range min=" & vcMin[1] & " max=" & vcMax[count(vc_fs)])
            else
                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
        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    
   
    # the fill value for vc must be below the min value for all the vc layers. 
    # This value will work fine for height levels  
    fill_val = -10       
        
    # use fieldsets or vectors in the computations    
    use_fs = 0   
        
    # 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]
    
        # out of range
        if iMin[ih] = -1 or iMax[ih] = -1 then
            r = bitmap(f_fs[1]*0, 0)
        
        # fieldsets
        else if use_fs then
        
            idx_vc = fsIndex_vc[iMin[ih]]
            idx_f = fsIndex_f[iMin[ih]] 
        
            # build fielssets:
            # 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
            h1 = vc_fs[idx_vc]*0 + fill_val
            h2 = h1
            
            idx_f = fsIndex_f[iMin[ih]]
            f1 = f_fs[idx_f] * 0.
            f2 = f1
           
            if __DEBUG_VERT_INTERPOLATE then  
                print("hh=",hh)
                print(" idx_cv=" & idx_vc & " idx_f=" & idx_f & " vcRange=" & 
                    vcMin[iMin[ih]] & "->" & vcMax[iMax[ih]] &
                    " fill_val=" & fill_val)
            end if
           
            for i = iMin[ih] to iMax[ih]-1 do         
                idx_vc = fsIndex_vc[i]
                idxNext_vc = fsIndex_vc[i+1]
                
                idx_f = fsIndex_f[i]
                idxNext_f = fsIndex_f[i+1]
                
                # this is the critial bit. We assume that for a given point hmask can only be 
                # 1 for only one of the i values!!!
                hmask = (vc_fs[idx_vc] <= hh) and (hh < vc_fs[idxNext_vc])
               
                h1 = h1 + hmask*vc_fs[idx_vc] - hmask*fill_val
                h2 = h2 + hmask*vc_fs[idxNext_vc] - hmask*fill_val
                
                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, bitmap(h1 > fill_val + 1, 0))
            h2 = bitmap(h2, bitmap(h2 > fill_val + 1, 0))
          
            method = opt.method
            # we cannot use log interpolation near 0 or negative vc values
            if method = 'log' and (hh < 1E-5 or vcMin[iMin[ih]] < 1E-5) then
                method = 'linear'
            end if 

            # interpolation
            if method = 'linear' then
                #r = f1 + (f2-f1)*(hh-h1)/(h2-h1)
                r = (f1*(h2-hh) + f2*(hh-h1))/(h2-h1)
            else if method = 'log' then
                r = f1 + (f2-f1)*log(hh/h1)/log(h2/h1)
            end if
            
        # vector based computations
        else
   
            idx_vc = fsIndex_vc[iMin[ih]]
            h1_val = values(vc_fs[idx_vc])*0 + fill_val
            h2_val = h1_val
            
            idx_f = fsIndex_f[iMin[ih]]
            f1_val = values(f_fs[idx_f])*0
            f2_val = f1_val
           
            if type(hh) = "fieldset" then
                hh_val = values(hh)
            else
                hh_val = hh
            end if
           
            if __DEBUG_VERT_INTERPOLATE then  
                print("hh=",hh)
                print(" idx_cv=" & idx_vc & " idx_f=" & idx_f & " vcRange=" & 
                    vcMin[iMin[ih]] & "->" & vcMax[iMax[ih]] &
                    " fill_val=" & fill_val)
            end if
          
            for i = iMin[ih] to iMax[ih]-1 do         
                idx_vc = fsIndex_vc[i]
                idxNext_vc = fsIndex_vc[i+1]
                
                idx_f = fsIndex_f[i]
                idxNext_f = fsIndex_f[i+1]
                    
                vc_act_val = values(vc_fs[idx_vc])
                vc_next_val = values(vc_fs[idxNext_vc])
                
                # this is the critial bit. We assume that for a given point hmask can only be 
                # 1 for only one of the i values!!!
                hmask = (vc_act_val <= hh_val) and (hh_val < vc_next_val)
                
                #h1 = h1 + hmask*(vc_fs[idx_vc] - fill_val)
                #h2 = h2 + hmask*(vc_fs[idxNext_vc] - fill_val)
                
                h1_val = h1_val + hmask*(vc_act_val - fill_val)
                h2_val = h2_val + hmask*(vc_next_val - fill_val)
                
                vc_act_val = nil
                vc_next_val = nil
                
                f_act_val = values(f_fs[idx_f])
                f1_val = f1_val + hmask*f_act_val
                f_act_val = nil
                
                f_next_val = values(f_fs[idxNext_f])
                f2_val = f2_val + hmask*f_next_val
                f_next_val = nil
                
                hmask = nil  
            end for
                  
            # bitmap the points where no proper values were found
         
            hmask = bitmap(h1_val < (fill_val + 1), 1)
            h1_val = h1_val + hmask
            hmask = bitmap(h2_val < (fill_val + 1), 1)
            h2_val = h2_val + hmask
            hmask = nil
          
            method = opt.method
            # we cannot use log interpolation near 0 or negative vc values
            if method = 'log' and (hh < 1E-5 or vcMin[iMin[ih]] < 1E-5) then
                method = 'linear'
            end if 

            # interpolation
            if method = 'linear' then
                f1_val = f1_val + (f2_val-f1_val)*(hh_val-h1_val)/(h2_val-h1_val)
                #r = (f1_val*(h2_val-hh) + f2_val*(hh-h1_val))/(h2_val-h1_val)
            else if method = 'log' then
                f1_val = f1_val + (f2_val-f1_val)*log(hh_val/h1_val)/log(h2_val/h1_val)
            end if
              
            h1_val = nil
            h2_val = nil
            f2_val = nil
            r = set_values(f_fs[idx_f], f1_val)  
            f1_val = nil
        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           