!***********************************************************************
!*                   GNU General Public License                        *
!* This file is a part of fvGFS.                                       *
!*                                                                     *
!* fvGFS is free software; you can redistribute it and/or modify it    *
!* and are expected to follow the terms of the GNU General Public      *
!* License as published by the Free Software Foundation; either        *
!* version 2 of the License, or (at your option) any later version.    *
!*                                                                     *
!* fvGFS 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 GNU   *
!* General Public License for more details.                            *
!*                                                                     *
!* For the full text of the GNU General Public License,                *
!* write to: Free Software Foundation, Inc.,                           *
!*           675 Mass Ave, Cambridge, MA 02139, USA.                   *
!* or see:   http://www.gnu.org/licenses/gpl.html                      *
!***********************************************************************

!***********************************************************************
!> @file
!! @brief Provides Moving Nest functionality in FV3 dynamic core
!! @author W. Ramstrom, AOML/HRD  01/15/2021
!! @email William.Ramstrom@noaa.gov
!=======================================================================!


!=======================================================================!
!
! Notes
!
!------------------------------------------------------------------------
! Moving Nest Subroutine Naming Convention
!-----------------------------------------------------------------------
!
! mn_meta_* subroutines perform moving nest operations for FV3 metadata.
!               These routines will run only once per nest move.
!
! mn_var_*  subroutines perform moving nest operations for an individual FV3 variable.
!               These routines will run many times per nest move.
!
! mn_prog_* subroutines perform moving nest operations for the list of prognostic fields.
!               These routines will run only once per nest move.
!
! mn_phys_* subroutines perform moving nest operations for the list of physics fields.
!               These routines will run only once per nest move.
!
! =======================================================================!

#define REMAP 1

module fv_moving_nest_mod

  use block_control_mod,      only : block_control_type
  use fms_mod,                only : mpp_clock_id, mpp_clock_begin, mpp_clock_end, CLOCK_ROUTINE, clock_flag_default, CLOCK_SUBCOMPONENT
  use mpp_mod,                only : mpp_pe, mpp_sync, mpp_sync_self, mpp_send, mpp_error, NOTE, FATAL, WARNING
  use mpp_domains_mod,        only : mpp_update_domains, mpp_get_data_domain, mpp_get_global_domain
  use mpp_domains_mod,        only : mpp_define_nest_domains, mpp_shift_nest_domains, nest_domain_type, domain2d
  use mpp_domains_mod,        only : mpp_get_C2F_index, mpp_update_nest_fine
  use mpp_domains_mod,        only : mpp_get_F2C_index, mpp_update_nest_coarse
  use mpp_domains_mod,        only : NORTH, SOUTH, EAST, WEST, CORNER, CENTER
  use mpp_domains_mod,        only : NUPDATE, SUPDATE, EUPDATE, WUPDATE, DGRID_NE

#ifdef GFS_TYPES
  use GFS_typedefs,           only: IPD_data_type => GFS_data_type, &
      IPD_control_type => GFS_control_type, kind_phys
#else
  use IPD_typedefs,           only: IPD_data_type, IPD_control_type, kind_phys => IPD_kind_phys
#endif
  use GFS_init,               only: GFS_grid_populate

  use boundary_mod,           only: update_coarse_grid, update_coarse_grid_mpp
  use bounding_box_mod,       only: bbox, bbox_get_C2F_index, fill_bbox
  use constants_mod,          only: cp_air, omega, rdgas, grav, rvgas, kappa, pstd_mks, hlv
  use field_manager_mod,      only: MODEL_ATMOS
  use fv_arrays_mod,          only: fv_atmos_type, fv_nest_type, fv_grid_type, R_GRID
  use fv_arrays_mod,          only: allocate_fv_nest_bc_type, deallocate_fv_nest_bc_type
  use fv_grid_tools_mod,      only: init_grid
  use fv_grid_utils_mod,      only: grid_utils_init, ptop_min, dist2side_latlon
  use fv_mapz_mod,            only: Lagrangian_to_Eulerian, moist_cv, compute_total_energy
  use fv_nesting_mod,         only: dealloc_nested_buffers
  use fv_nwp_nudge_mod,       only: do_adiabatic_init
  use init_hydro_mod,         only: p_var
  use tracer_manager_mod,     only: get_tracer_index, get_tracer_names
  use fv_moving_nest_types_mod, only: fv_moving_nest_prog_type, fv_moving_nest_physics_type, Moving_nest
  use fv_moving_nest_utils_mod,  only: alloc_halo_buffer, load_nest_latlons_from_nc, grid_geometry, output_grid_to_nc
  use fv_moving_nest_utils_mod,  only: fill_nest_from_buffer, fill_nest_from_buffer_cell_center, fill_nest_from_buffer_nearest_neighbor
  use fv_moving_nest_utils_mod,  only: fill_nest_halos_from_parent, fill_grid_from_supergrid, fill_weight_grid
  use fv_moving_nest_utils_mod,  only: alloc_read_data

  implicit none

#ifdef NO_QUAD_PRECISION
  ! 64-bit precision (kind=8)
  integer, parameter:: f_p = selected_real_kind(15)
#else
  ! Higher precision (kind=16) for grid geometrical factors:
  integer, parameter:: f_p = selected_real_kind(20)
#endif

#ifdef OVERLOAD_R4
  real, parameter:: real_snan=x'FFBFFFFF'
#else
  real, parameter:: real_snan=x'FFF7FFFFFFFFFFFF'
#endif

  logical :: debug_log = .false.

#include <fms_platform.h>

  !! Step 2
  interface mn_var_fill_intern_nest_halos
    module procedure mn_var_fill_intern_nest_halos_r4_2d
    module procedure mn_var_fill_intern_nest_halos_r4_3d
    module procedure mn_var_fill_intern_nest_halos_r4_4d

    module procedure mn_var_fill_intern_nest_halos_r8_2d
    module procedure mn_var_fill_intern_nest_halos_r8_3d
    module procedure mn_var_fill_intern_nest_halos_r8_4d

    module procedure mn_var_fill_intern_nest_halos_wind
  end interface mn_var_fill_intern_nest_halos


  !! Step 6
  interface mn_var_shift_data
    module procedure mn_var_shift_data_r4_2d
    module procedure mn_var_shift_data_r4_3d
    module procedure mn_var_shift_data_r4_4d

    module procedure mn_var_shift_data_r8_2d
    module procedure mn_var_shift_data_r8_3d
    module procedure mn_var_shift_data_r8_4d
  end interface mn_var_shift_data

  !! Step 8
  interface mn_var_dump_to_netcdf
    module procedure mn_var_dump_2d_to_netcdf
    module procedure mn_var_dump_3d_to_netcdf
  end interface mn_var_dump_to_netcdf

  interface mn_static_read_hires
    module procedure  mn_static_read_hires_r4
    module procedure  mn_static_read_hires_r8
  end interface mn_static_read_hires

contains

  !!=====================================================================================
  !! Step 1.9 -- Allocate and fill the temporary variable(s)
  !!            This is to manage variables that are not allocated with a halo
  !!            on the Atm structure
  !!=====================================================================================

  !>@brief The subroutine 'mn_prog_fill_temp_variables' fills the temporary variable for delz
  !>@details The delz variable does not have haloes so we need a temporary variable to move it.
  subroutine mn_prog_fill_temp_variables(Atm, n, child_grid_num, is_fine_pe, npz)
    type(fv_atmos_type), allocatable, target, intent(in)     :: Atm(:)     !< Array of atmospheric data
    integer, intent(in)                              :: n, child_grid_num  !< This level and nest level
    logical, intent(in)                              :: is_fine_pe         !< Is this the nest PE?
    integer, intent(in)                              :: npz                !< Number of vertical levels

    integer :: isd, ied, jsd, jed
    integer :: is, ie, js, je
    integer :: this_pe
    type(fv_moving_nest_prog_type), pointer :: mn_prog

    mn_prog => Moving_nest(n)%mn_prog

    this_pe = mpp_pe()

    isd = Atm(n)%bd%isd
    ied = Atm(n)%bd%ied
    jsd = Atm(n)%bd%jsd
    jed = Atm(n)%bd%jed

    is = Atm(n)%bd%is
    ie = Atm(n)%bd%ie
    js = Atm(n)%bd%js
    je = Atm(n)%bd%je

    ! Reset this to a dummy value, to help flag if the halos don't get updated later.
    mn_prog%delz = +99999.9
    mn_prog%delz(is:ie, js:je, 1:npz) =  Atm(n)%delz(is:ie, js:je, 1:npz)

  end subroutine mn_prog_fill_temp_variables

  !>@brief The subroutine 'mn_prog_apply_temp_variables' fills the Atm%delz value from the temporary variable after nest move
  !>@details The delz variable does not have haloes so we need a temporary variable to move it.
  subroutine mn_prog_apply_temp_variables(Atm, n, child_grid_num, is_fine_pe, npz)
    type(fv_atmos_type), allocatable, target, intent(inout)  :: Atm(:)             !< Array of atmospheric data
    integer, intent(in)                                      :: n, child_grid_num  !< This level and nest level
    logical, intent(in)                                      :: is_fine_pe         !< Is this the nest PE?
    integer, intent(in)                                      :: npz                !< Number of vertical levels

    integer :: is, ie, js, je
    type(fv_moving_nest_prog_type), pointer :: mn_prog

    mn_prog => Moving_nest(n)%mn_prog

    if (is_fine_pe) then
      is = Atm(n)%bd%is
      ie = Atm(n)%bd%ie
      js = Atm(n)%bd%js
      je = Atm(n)%bd%je

      Atm(n)%delz(is:ie, js:je, 1:npz) =  mn_prog%delz(is:ie, js:je, 1:npz)
    endif

  end subroutine mn_prog_apply_temp_variables


  !!=====================================================================================
  !! Step 2 -- Fill the nest edge halos from parent grid before nest motion
  !!            OR Refill the nest edge halos from parent grid after nest motion
  !!            Parent and nest PEs need to execute these subroutines
  !!=====================================================================================

  !>@brief The subroutine 'mn_prog_fill_nest_halos_from_parent' fills the nest edge halos from the parent
  !>@details Parent and nest PEs must run this subroutine.  It transfers data and interpolates onto fine nest.
  subroutine mn_prog_fill_nest_halos_from_parent(Atm, n, child_grid_num, is_fine_pe, nest_domain, nz)
    type(fv_atmos_type), allocatable, target, intent(inout)  :: Atm(:)             !< Array of atmospheric data
    integer, intent(in)                                      :: n, child_grid_num  !< This level and nest level
    logical, intent(in)                                      :: is_fine_pe         !< Is this the nest PE?
    type(nest_domain_type), intent(inout)                    :: nest_domain        !< Domain structure for nest
    integer, intent(in)                                      :: nz                 !< Number of vertical levels

    integer  :: position, position_u, position_v
    integer  :: interp_type, interp_type_u, interp_type_v
    integer  :: x_refine, y_refine
    type(fv_moving_nest_prog_type), pointer :: mn_prog

    mn_prog => Moving_nest(n)%mn_prog

    !  TODO Rename this from interp_type to stagger_type
    interp_type = 1    ! cell-centered A-grid
    interp_type_u = 4  ! D-grid
    interp_type_v = 4  ! D-grid

    position = CENTER
    position_u = NORTH
    position_v = EAST

    x_refine = Atm(child_grid_num)%neststruct%refinement
    y_refine = x_refine

    !  Fill centered-grid variables
    call fill_nest_halos_from_parent("q_con", Atm(n)%q_con, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
        Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    call fill_nest_halos_from_parent("pt", Atm(n)%pt, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
        Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    call fill_nest_halos_from_parent("w", Atm(n)%w, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
        Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    !call fill_nest_halos_from_parent("omga", Atm(n)%omga, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
    !     Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    call fill_nest_halos_from_parent("delp", Atm(n)%delp, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
        Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    call fill_nest_halos_from_parent("delz", mn_prog%delz, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
        Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    call fill_nest_halos_from_parent("q", Atm(n)%q, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
        Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    ! Interpolate terrain from coarse grid
    if (Moving_nest(n)%mn_flag%terrain_smoother .eq. 4) then
      call fill_nest_halos_from_parent("phis", Atm(n)%phis, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
          Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position)
    endif

    !  Move the A-grid winds.  TODO consider recomputing them from D grid instead
    call fill_nest_halos_from_parent("ua", Atm(n)%ua, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
        Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    call fill_nest_halos_from_parent("va", Atm(n)%va, interp_type, Atm(child_grid_num)%neststruct%wt_h, &
        Atm(child_grid_num)%neststruct%ind_h, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    !  Fill staggered D-grid variables
    call fill_nest_halos_from_parent("u", Atm(n)%u, interp_type_u, Atm(child_grid_num)%neststruct%wt_u, &
        Atm(child_grid_num)%neststruct%ind_u, x_refine, y_refine, is_fine_pe, nest_domain, position_u, nz)
    call fill_nest_halos_from_parent("v", Atm(n)%v, interp_type_v, Atm(child_grid_num)%neststruct%wt_v, &
        Atm(child_grid_num)%neststruct%ind_v, x_refine, y_refine, is_fine_pe, nest_domain, position_v, nz)

  end subroutine mn_prog_fill_nest_halos_from_parent

  !!============================================================================
  !! Step 3 -- Redefine the nest domain to new location
  !!   This calls mpp_shift_nest_domains.
  !!  --  Similar to med_nest_configure() from HWRF
  !!============================================================================

  !>@brief The subroutine 'mn_meta_move_nest' resets the metadata for the nest
  !>@details Parent and  nest PEs run this subroutine.
  subroutine mn_meta_move_nest(delta_i_c, delta_j_c, pelist, is_fine_pe, extra_halo, nest_domain, domain_fine, domain_coarse, &
      istart_coarse, iend_coarse, jstart_coarse, jend_coarse,  istart_fine, iend_fine, jstart_fine, jend_fine)

    implicit none

    integer, intent(in)                   :: delta_i_c, delta_j_c                                    !< Coarse grid delta i,j for nest move
    integer, allocatable, intent(in)      :: pelist(:)                                               !< List of involved PEs
    logical, intent(in)                   :: is_fine_pe                                              !< Is this a nest PE?
    integer, intent(in)                   :: extra_halo                                              !< Extra halo points (not fully implemented)
    type(nest_domain_type), intent(inout) :: nest_domain                                             !< Nest domain structure
    type(domain2d), intent(inout)         :: domain_coarse, domain_fine                              !< Coarse and fine domain structures
    integer, intent(inout)                :: istart_coarse, iend_coarse, jstart_coarse, jend_coarse  !< Bounds of coarse grid
    integer, intent(in)                   :: istart_fine, iend_fine, jstart_fine, jend_fine          !< Bounds of fine grid

    !  Local variables
    integer   :: num_nest
    integer   :: this_pe

    integer   :: delta_i_coarse(1), delta_j_coarse(1)

    this_pe = mpp_pe()

    !  Initial implementation only supports single moving nest.  Update this later.
    !  mpp_shift_nest_domains has a call signature to support multiple moving nests, though has not been tested for correctness.
    delta_i_coarse(1) = delta_i_c
    delta_j_coarse(1) = delta_j_c

    !!===========================================================
    !!
    !! Relocate where the nest is aligned on the parent
    !!
    !!===========================================================

    istart_coarse = istart_coarse + delta_i_c
    iend_coarse = iend_coarse + delta_i_c

    jstart_coarse = jstart_coarse + delta_j_c
    jend_coarse = jend_coarse + delta_j_c

    ! The fine nest will maintain the same indices

    num_nest = nest_domain%num_nest

    ! TODO Verify whether rerunning this will cause (small) memory leaks.
    if (is_fine_pe) then
      call mpp_shift_nest_domains(nest_domain, domain_fine, delta_i_coarse, delta_j_coarse, extra_halo)
    else
      call mpp_shift_nest_domains(nest_domain, domain_coarse, delta_i_coarse, delta_j_coarse, extra_halo)
    endif

  end subroutine mn_meta_move_nest


  !================================================================================
  !! Step 4 --  Updates the internal nest tile halos
  !================================================================================

  !>@brief The subroutine 'mn_prog_fill_intern_nest_halos' fill internal nest halos for prognostic variables
  !>@details Only nest PEs call this subroutine.
  subroutine mn_prog_fill_intern_nest_halos(Atm, domain_fine, is_fine_pe)
    type(fv_atmos_type), target, intent(inout)  :: Atm           !< Single instance of atmospheric data
    type(domain2d), intent(inout)               :: domain_fine   !< Domain structure for nest
    logical, intent(in)                         :: is_fine_pe    !< Is this a nest PE?

    integer :: this_pe
    type(fv_moving_nest_prog_type), pointer :: mn_prog

    integer :: child_grid_num = 2

    mn_prog => Moving_nest(2)%mn_prog  ! TODO allow nest number to vary
    this_pe = mpp_pe()

    call mn_var_fill_intern_nest_halos(Atm%q_con, domain_fine, is_fine_pe)
    call mn_var_fill_intern_nest_halos(Atm%pt, domain_fine, is_fine_pe)
    call mn_var_fill_intern_nest_halos(Atm%w, domain_fine, is_fine_pe)
    !call mn_var_fill_intern_nest_halos(Atm%omga, domain_fine, is_fine_pe)
    call mn_var_fill_intern_nest_halos(Atm%delp, domain_fine, is_fine_pe)
    call mn_var_fill_intern_nest_halos(mn_prog%delz, domain_fine, is_fine_pe)

    if (Moving_nest(child_grid_num)%mn_flag%terrain_smoother .eq. 4) then
      call mn_var_fill_intern_nest_halos(Atm%phis, domain_fine, is_fine_pe)
    endif

    call mn_var_fill_intern_nest_halos(Atm%ua, domain_fine, is_fine_pe)
    call mn_var_fill_intern_nest_halos(Atm%va, domain_fine, is_fine_pe)

    ! The vector form of the subroutine takes care of the staggering of the wind variables internally.
    call mn_var_fill_intern_nest_halos(Atm%u, Atm%v, domain_fine, is_fine_pe)

    call mn_var_fill_intern_nest_halos(Atm%q, domain_fine, is_fine_pe)

  end subroutine mn_prog_fill_intern_nest_halos


  !================================================================================
  !
  !   Step 4 -- Per variable fill internal nest halos
  !
  !================================================================================

  !>@brief The subroutine 'mn_var_fill_intern_nest_halos_r4_2d' fills internal nest halos
  !>@details This version of the subroutine is for 2D arrays of single precision reals.
  subroutine mn_var_fill_intern_nest_halos_r4_2d(data_var, domain_fine, is_fine_pe)
    real*4, allocatable, intent(inout)          :: data_var(:,:)  !< Model variable data
    type(domain2d), intent(inout)               :: domain_fine    !< Nest domain structure
    logical, intent(in)                         :: is_fine_pe     !< Is this the nest PE?

    integer                      :: this_pe
    this_pe = mpp_pe()

    if (is_fine_pe) then
      ! mpp_update_domains fills the halo region of the fine grids for the interior of the nest.
      ! The fine nest boundary with the coarse grid remains unchanged.
      ! seems that this only performs communication between fine nest PEs
      ! Just transfers halo data between tiles of same resolution -- doesn't perform any interpolation!
      call mpp_update_domains(data_var, domain_fine,  flags=NUPDATE + EUPDATE + SUPDATE + WUPDATE)
    endif

  end subroutine mn_var_fill_intern_nest_halos_r4_2d

  !>@brief The subroutine 'mn_var_fill_intern_nest_halos_r8_2d' fills internal nest halos
  !>@details This version of the subroutine is for 2D arrays of double precision reals.
  subroutine mn_var_fill_intern_nest_halos_r8_2d(data_var, domain_fine, is_fine_pe)
    real*8, allocatable, intent(inout)          :: data_var(:,:)  !< Double precision model variable
    type(domain2d), intent(inout)               :: domain_fine    !< Nest domain structure
    logical, intent(in)                         :: is_fine_pe     !< Is this a nest PE?

    if (is_fine_pe) then
      call mpp_update_domains(data_var, domain_fine,  flags=NUPDATE + EUPDATE + SUPDATE + WUPDATE)
    endif

  end subroutine mn_var_fill_intern_nest_halos_r8_2d

  !>@brief The subroutine 'mn_var_fill_intern_nest_halos_r4_3d' fills internal nest halos
  !>@details This version of the subroutine is for 3D arrays of single precision reals.
  subroutine mn_var_fill_intern_nest_halos_r4_3d(data_var, domain_fine, is_fine_pe)
    real*4, allocatable, intent(inout)          :: data_var(:,:,:)  !< Single precision model variable
    type(domain2d), intent(inout)               :: domain_fine      !< Nest domain structure
    logical, intent(in)                         :: is_fine_pe       !< Is this a nest PE?

    if (is_fine_pe) then
      call mpp_update_domains(data_var, domain_fine,  flags=NUPDATE + EUPDATE + SUPDATE + WUPDATE)
    endif

  end subroutine mn_var_fill_intern_nest_halos_r4_3d

  !>@brief The subroutine 'mn_var_fill_intern_nest_halos_r8_3d' fills internal nest halos
  !>@details This version of the subroutine is for 3D arrays of double precision reals.
  subroutine mn_var_fill_intern_nest_halos_r8_3d(data_var, domain_fine, is_fine_pe)
    real*8, allocatable, intent(inout)          :: data_var(:,:,:)  !< Double precision model variable
    type(domain2d), intent(inout)               :: domain_fine      !< Nest domain structure
    logical, intent(in)                         :: is_fine_pe       !< Is this a nest PE?

    if (is_fine_pe) then
      call mpp_update_domains(data_var, domain_fine,  flags=NUPDATE + EUPDATE + SUPDATE + WUPDATE)
    endif

  end subroutine mn_var_fill_intern_nest_halos_r8_3d

  !>@brief The subroutine 'mn_var_fill_intern_nest_halos_wind' fills internal nest halos for u and v wind
  !>@details This version of the subroutine is for 3D arrays of single precision reals for each wind component
  subroutine mn_var_fill_intern_nest_halos_wind(u_var, v_var, domain_fine, is_fine_pe)
    real, allocatable, intent(inout)            :: u_var(:,:,:) !< Staggered u wind
    real, allocatable, intent(inout)            :: v_var(:,:,:) !< Staggered v wind
    type(domain2d), intent(inout)               :: domain_fine  !< Nest domain structure
    logical, intent(in)                         :: is_fine_pe   !< Is this a nest PE?

    if (is_fine_pe) then
      call mpp_update_domains(u_var, v_var, domain_fine,  flags=NUPDATE + EUPDATE + SUPDATE + WUPDATE, gridtype=DGRID_NE)
    endif

  end subroutine mn_var_fill_intern_nest_halos_wind


  !>@brief The subroutine 'mn_var_fill_intern_nest_halos_r4_4d' fills internal nest halos
  !>@details This version of the subroutine is for 4D arrays of single precision reals.
  subroutine mn_var_fill_intern_nest_halos_r4_4d(data_var, domain_fine, is_fine_pe)
    real*4, allocatable, intent(inout)          :: data_var(:,:,:,:)  !< Single prevision variable
    type(domain2d), intent(inout)               :: domain_fine        !< Nest domain structure
    logical, intent(in)                         :: is_fine_pe         !< Is this a nest PE?

    if (is_fine_pe) then
      call mpp_update_domains(data_var, domain_fine,  flags=NUPDATE + EUPDATE + SUPDATE + WUPDATE)
    endif

  end subroutine mn_var_fill_intern_nest_halos_r4_4d

  !>@brief The subroutine 'mn_var_fill_intern_nest_halos_r8_4d' fills internal nest halos
  !>@details This version of the subroutine is for 4D arrays of double precision reals.
  subroutine mn_var_fill_intern_nest_halos_r8_4d(data_var, domain_fine, is_fine_pe)
    real*8, allocatable, intent(inout)          :: data_var(:,:,:,:)  !< Double precision variable
    type(domain2d), intent(inout)               :: domain_fine        !< Nest domain structure
    logical, intent(in)                         :: is_fine_pe         !< Is this a nest PE?

    if (is_fine_pe) then
      call mpp_update_domains(data_var, domain_fine,  flags=NUPDATE + EUPDATE + SUPDATE + WUPDATE)
    endif

  end subroutine mn_var_fill_intern_nest_halos_r8_4d

  !>@brief Find the parent point that corresponds to the is,js point of the nest, and returns that nest point also
  subroutine calc_nest_alignment(Atm, n, nest_x, nest_y, parent_x, parent_y)
    type(fv_atmos_type), allocatable, intent(in) :: Atm(:)                                        !< Atm data array
    integer, intent(in)                          :: n                                             !< Grid numbers
    integer, intent(out)                         :: nest_x, nest_y, parent_x, parent_y

    integer :: refine
    integer :: child_grid_num
    integer :: ioffset, joffset

    child_grid_num = n

    refine = Atm(child_grid_num)%neststruct%refinement

    ! parent_x and parent_y are on the supergrid, so an increment of ioffset is an increment of 2*refine

    nest_x = Atm(child_grid_num)%bd%isd
    nest_y = Atm(child_grid_num)%bd%jsd

    ioffset = Atm(n)%neststruct%ioffset
    joffset = Atm(n)%neststruct%joffset

    ! Increment of 3 is for halo.  Factor of 2 is for supergrid.
    parent_x = (nest_x - 3)*2 + ioffset*refine*2
    parent_y = (nest_y - 3)*2 + joffset*refine*2

  end subroutine calc_nest_alignment



  subroutine check_nest_alignment(nest_geo, parent_geo, nest_x, nest_y, parent_x, parent_y, found)
    type(grid_geometry), intent(in)              :: nest_geo                                      !< Tile geometry
    type(grid_geometry), intent(in)              :: parent_geo                                    !< Parent grid at high-resolution geometry
    integer, intent(in)                          :: nest_x, nest_y, parent_x, parent_y
    logical, intent(out)                         :: found

    real(kind=R_GRID) :: pi = 4 * atan(1.0d0)
    real              :: rad2deg
    integer           :: this_pe

    this_pe = mpp_pe()
    rad2deg =  180.0 / pi

    found = .False.

    if (abs(parent_geo%lats(parent_x, parent_y) - nest_geo%lats(nest_x, nest_y)) .lt. 0.0001) then
      if (abs(parent_geo%lons(parent_x, parent_y) - nest_geo%lons(nest_x, nest_y)) .lt. 0.0001) then
        found = .True.
      endif
      if (abs(abs(parent_geo%lons(parent_x, parent_y) - nest_geo%lons(nest_x, nest_y)) - 2*pi)  .lt. 0.0001) then
        found = .True.
      endif
    endif

  end subroutine check_nest_alignment

  !!============================================================================
  !! Step 5.1 -- Load the latlon data from NetCDF
  !!             update parent_geo, tile_geo*, p_grid*, n_grid*
  !!============================================================================

  !>@brief The subroutine 'mn_latlon_load_parent' loads parent latlon data from netCDF
  !>@details Updates parent_geo, tile_geo*, p_grid*, n_grid*
  subroutine mn_latlon_load_parent(surface_dir, Atm, n, parent_tile, delta_i_c, delta_j_c, pelist, child_grid_num, parent_geo, tile_geo, tile_geo_u, tile_geo_v, fp_super_tile_geo, p_grid, n_grid, p_grid_u, n_grid_u, p_grid_v, n_grid_v)
    character(len=*), intent(in)                 :: surface_dir                                   !< Directory for static files
    type(fv_atmos_type), allocatable, intent(in) :: Atm(:)                                        !< Atm data array
    integer, intent(in)                          :: n, parent_tile, child_grid_num                !< Grid numbers
    integer, intent(in)                          :: delta_i_c, delta_j_c                          !< Nest motion in delta i,j
    integer, allocatable, intent(in)             :: pelist(:)                                     !< PE list for fms2_io
    type(grid_geometry), intent(inout)           :: parent_geo, tile_geo, tile_geo_u, tile_geo_v  !< Tile geometries
    type(grid_geometry), intent(in)              :: fp_super_tile_geo                             !< Parent grid at high-resolution geometry
    real(kind=R_GRID), allocatable, intent(inout):: p_grid(:,:,:)                                 !< A-stagger lat/lon grids
    real(kind=R_GRID), allocatable, intent(inout):: p_grid_u(:,:,:)                               !< u-wind staggered lat/lon grids
    real(kind=R_GRID), allocatable, intent(inout):: p_grid_v(:,:,:)                               !< v-wind staggered lat/lon grids
    real(kind=R_GRID), allocatable, intent(out)  :: n_grid(:,:,:)                                 !< A-stagger lat/lon grids
    real(kind=R_GRID), allocatable, intent(out)  :: n_grid_u(:,:,:)                               !< u-wind staggered lat/lon grids
    real(kind=R_GRID), allocatable, intent(out)  :: n_grid_v(:,:,:)                               !< v-wind staggered lat/lon grids

    character(len=256) :: grid_filename
    logical, save  :: first_nest_move = .true.
    integer, save  :: p_istart_fine, p_iend_fine, p_jstart_fine, p_jend_fine
    integer :: x, y, fp_i, fp_j
    integer :: position, position_u, position_v
    integer :: x_refine, y_refine
    integer :: this_pe

    logical, save :: first_time = .True.
    integer, save :: id_load1, id_load2, id_load3, id_load4, id_load5
    logical       :: use_timers

    use_timers = Atm(n)%flagstruct%fv_timers

    this_pe = mpp_pe()

    if (first_time) then
      if (use_timers) then
        id_load1     = mpp_clock_id ('MN LatLon Part 1 File',  flags = clock_flag_default, grain=CLOCK_SUBCOMPONENT )
        id_load2     = mpp_clock_id ('MN LatLon Part 2 File',  flags = clock_flag_default, grain=CLOCK_SUBCOMPONENT )
        id_load3     = mpp_clock_id ('MN LatLon Part 3 File',  flags = clock_flag_default, grain=CLOCK_SUBCOMPONENT )
        id_load4     = mpp_clock_id ('MN LatLon Part 4 File',  flags = clock_flag_default, grain=CLOCK_SUBCOMPONENT )
        id_load5     = mpp_clock_id ('MN LatLon Part 5 File',  flags = clock_flag_default, grain=CLOCK_SUBCOMPONENT )
      endif
      first_time = .False.
    endif

    position = CENTER
    position_u = NORTH
    position_v = EAST

    x_refine = Atm(child_grid_num)%neststruct%refinement
    y_refine = x_refine

    !  Setup parent_geo with the values for the parent tile
    !  Note that lat/lon are stored in the model in RADIANS
    !  Only the netCDF files use degrees

    if (first_nest_move) then
      if (use_timers) call mpp_clock_begin (id_load1)

      parent_geo%nxp = Atm(1)%npx
      parent_geo%nyp = Atm(1)%npy
      
      parent_geo%nx = parent_geo%nxp - 1
      parent_geo%ny = parent_geo%nyp - 1
      
      call mn_static_filename(surface_dir, parent_tile, 'grid', 1, grid_filename)
      call load_nest_latlons_from_nc(grid_filename, parent_geo%nxp, parent_geo%nyp, 1, pelist, &
          parent_geo, p_istart_fine, p_iend_fine, p_jstart_fine, p_jend_fine)


      ! These are saved between timesteps in fv_moving_nest_main.F90
      allocate(p_grid(parent_geo%nx, parent_geo%ny,2))
      allocate(p_grid_u(parent_geo%nx, parent_geo%ny+1,2))
      allocate(p_grid_v(parent_geo%nx+1, parent_geo%ny,2))

      p_grid = 0.0
      p_grid_u = 0.0
      p_grid_v = 0.0



      ! These are big (parent grid size), and do not change during the model integration.
      call assign_p_grids(parent_geo, p_grid, position)
      call assign_p_grids(parent_geo, p_grid_u, position_u)
      call assign_p_grids(parent_geo, p_grid_v, position_v)

      first_nest_move = .false.
      if (use_timers) call mpp_clock_end (id_load1)
    endif

    if (use_timers) call mpp_clock_begin (id_load2)


    !===========================================================
    !  Begin tile_geo per PE.
    !===========================================================

    !------------------------
    ! Grid Definitions
    !------------------------
    !
    ! tile_geo - lat/lons on A-grid (cell centers) for nest, on data domain (includes halo) for each PE
    ! parent_geo - lat/lons of supergrid for parent
    ! n_grid - lat/lons of cell centers for nest
    ! p_grid - lat/lons of cell centers for parent
    !
    ! gridstruct%agrid - cell centers for each PE
    ! gridstruct%grid - cell corners for each PE

    ! Allocate tile_geo just for this PE, copied from Atm(n)%gridstruct%agrid
    tile_geo%nx = ubound(Atm(n)%gridstruct%agrid, 1) - lbound(Atm(n)%gridstruct%agrid, 1)
    tile_geo%ny = ubound(Atm(n)%gridstruct%agrid, 2) - lbound(Atm(n)%gridstruct%agrid, 2)
    tile_geo%nxp = tile_geo%nx + 1
    tile_geo%nyp = tile_geo%ny + 1

    allocate(tile_geo%lons(lbound(Atm(n)%gridstruct%agrid, 1):ubound(Atm(n)%gridstruct%agrid, 1), lbound(Atm(n)%gridstruct%agrid, 2):ubound(Atm(n)%gridstruct%agrid, 2)))
    allocate(tile_geo%lats(lbound(Atm(n)%gridstruct%agrid, 1):ubound(Atm(n)%gridstruct%agrid, 1), lbound(Atm(n)%gridstruct%agrid, 2):ubound(Atm(n)%gridstruct%agrid, 2)))

    tile_geo%lats = -999.9
    tile_geo%lons = -999.9

    do x = lbound(Atm(n)%gridstruct%agrid, 1), ubound(Atm(n)%gridstruct%agrid, 1)
      do y = lbound(Atm(n)%gridstruct%agrid, 2), ubound(Atm(n)%gridstruct%agrid, 2)
        tile_geo%lons(x,y) = Atm(n)%gridstruct%agrid(x,y,1)
        tile_geo%lats(x,y) = Atm(n)%gridstruct%agrid(x,y,2)
      enddo
    enddo

    if (use_timers) call mpp_clock_end (id_load2)
    if (use_timers) call mpp_clock_begin (id_load3)

    ! Allocate tile_geo_u just for this PE, copied from Atm(n)%gridstruct%grid
    ! grid is 1 larger than agrid
    ! u(npx, npy+1)
    tile_geo_u%nx = ubound(Atm(n)%gridstruct%agrid, 1) - lbound(Atm(n)%gridstruct%agrid, 1)
    tile_geo_u%ny = ubound(Atm(n)%gridstruct%grid, 2) - lbound(Atm(n)%gridstruct%grid, 2)
    tile_geo_u%nxp = tile_geo_u%nx + 1
    tile_geo_u%nyp = tile_geo_u%ny + 1


    if (.not. allocated(tile_geo_u%lons)) then
      allocate(tile_geo_u%lons(lbound(Atm(n)%gridstruct%agrid, 1):ubound(Atm(n)%gridstruct%agrid, 1), lbound(Atm(n)%gridstruct%grid, 2):ubound(Atm(n)%gridstruct%grid, 2)))
      allocate(tile_geo_u%lats(lbound(Atm(n)%gridstruct%agrid, 1):ubound(Atm(n)%gridstruct%agrid, 1), lbound(Atm(n)%gridstruct%grid, 2):ubound(Atm(n)%gridstruct%grid, 2)))
    endif

    tile_geo_u%lons = -999.9
    tile_geo_u%lats = -999.9

    ! Allocate tile_geo_v just for this PE, copied from Atm(n)%gridstruct%grid
    ! grid is 1 larger than agrid
    ! u(npx, npy+1)
    tile_geo_v%nx = ubound(Atm(n)%gridstruct%grid, 1) - lbound(Atm(n)%gridstruct%grid, 1)
    tile_geo_v%ny = ubound(Atm(n)%gridstruct%agrid, 2) - lbound(Atm(n)%gridstruct%agrid, 2)
    tile_geo_v%nxp = tile_geo_v%nx + 1
    tile_geo_v%nyp = tile_geo_v%ny + 1

    allocate(tile_geo_v%lons(lbound(Atm(n)%gridstruct%grid, 1):ubound(Atm(n)%gridstruct%grid, 1), lbound(Atm(n)%gridstruct%agrid, 2):ubound(Atm(n)%gridstruct%agrid, 2)))
    allocate(tile_geo_v%lats(lbound(Atm(n)%gridstruct%grid, 1):ubound(Atm(n)%gridstruct%grid, 1), lbound(Atm(n)%gridstruct%agrid, 2):ubound(Atm(n)%gridstruct%agrid, 2)))

    tile_geo_v%lons = -999.9
    tile_geo_v%lats = -999.9

    !===========================================================
    !  End tile_geo per PE.
    !===========================================================

    allocate(n_grid(Atm(child_grid_num)%bd%isd:Atm(child_grid_num)%bd%ied, Atm(child_grid_num)%bd%jsd:Atm(child_grid_num)%bd%jed, 2))
    n_grid = real_snan

    allocate(n_grid_u(Atm(child_grid_num)%bd%isd:Atm(child_grid_num)%bd%ied, Atm(child_grid_num)%bd%jsd:Atm(child_grid_num)%bd%jed+1, 2))
    n_grid_u = real_snan

    allocate(n_grid_v(Atm(child_grid_num)%bd%isd:Atm(child_grid_num)%bd%ied+1, Atm(child_grid_num)%bd%jsd:Atm(child_grid_num)%bd%jed, 2))
    n_grid_v = real_snan

    ! TODO - propagate tile_geo information back to Atm structure
    ! TODO - deallocate tile_geo lat/lons
    ! TODO - ensure the allocation of tile_geo lat/lons is only performed once - outside the loop

    if (use_timers) call mpp_clock_end (id_load3)
    if (use_timers) call mpp_clock_begin (id_load4)

    call move_nest_geo(Atm, n, tile_geo, tile_geo_u, tile_geo_v, fp_super_tile_geo, delta_i_c, delta_j_c, x_refine, y_refine)

    if (use_timers) call mpp_clock_end (id_load4)
    if (use_timers) call mpp_clock_begin (id_load5)

    ! These grids are small (nest size), and change each time nest moves.
    call assign_n_grids(tile_geo, n_grid, position)
    call assign_n_grids(tile_geo_u, n_grid_u, position_u)
    call assign_n_grids(tile_geo_v, n_grid_v, position_v)

    if (use_timers) call mpp_clock_end (id_load5)

  end subroutine mn_latlon_load_parent

  !>@brief The subroutine 'mn_static_filename' generates the full pathname for a static file for each run
  !>@details Constructs the full pathname for a variable and refinement level and tests whether it exists
  subroutine mn_static_filename(surface_dir, tile_num, tag, refine, grid_filename)
    character(len=*), intent(in)       :: surface_dir     !< Directory
    character(len=*), intent(in)       :: tag             !< Variable name
    integer, intent(in)                :: tile_num        !< Tile number
    integer, intent(in)                :: refine          !< Nest refinement
    character(len=*), intent(out)      :: grid_filename   !< Output pathname to netCDF file

    character(len=256) :: refine_str, parent_str
    character(len=1)   :: divider
    logical            :: file_exists

    write(parent_str, '(I0)'), tile_num

    if (refine .eq. 1 .and. (tag .eq. 'grid' .or. tag .eq. 'oro_data')) then
      ! For 1x files in INPUT directory; go at the symbolic link
      grid_filename = trim(trim(surface_dir) // '/' // trim(tag) // '.tile' // trim(parent_str) // '.nc')
    else
      if (refine .eq. 1) then
        grid_filename = trim(trim(surface_dir) // '/' // trim(tag) // '.tile' // trim(parent_str) // '.nc')
      else
        write(refine_str, '(I0,A1)'), refine, 'x'
        grid_filename = trim(trim(surface_dir) // '/' // trim(tag) // '.tile' // trim(parent_str) // '.' // trim(refine_str) // '.nc')
      endif
    endif

    grid_filename = trim(grid_filename)

    inquire(FILE=grid_filename, EXIST=file_exists)
    if (.not. file_exists) then
      call mpp_error(FATAL, 'mn_static_filename DOES NOT EXIST '//trim(grid_filename))
    endif

  end subroutine mn_static_filename

  !>@brief The subroutine 'mn_latlon_read_hires_parent' reads in static data from a netCDF file
  subroutine mn_latlon_read_hires_parent(npx, npy, refine, pelist, fp_super_tile_geo, surface_dir, parent_tile)
    integer, intent(in)                :: npx, npy, refine     !< Number of points in x,y, and refinement
    integer, allocatable, intent(in)   :: pelist(:)            !< PE list for fms2_io
    type(grid_geometry), intent(inout) :: fp_super_tile_geo    !< Geometry of supergrid for parent tile at high resolution
    character(len=*), intent(in)       :: surface_dir          !< Surface directory to read netCDF file from
    integer, intent(in)                :: parent_tile          !< Parent tile number

    integer                            :: fp_super_istart_fine, fp_super_jstart_fine,fp_super_iend_fine, fp_super_jend_fine
    character(len=256)                 :: grid_filename

    integer :: i, j

    call mn_static_filename(surface_dir, parent_tile, 'grid',  refine, grid_filename)

    call load_nest_latlons_from_nc(trim(grid_filename), npx, npy, refine, pelist, &
        fp_super_tile_geo, fp_super_istart_fine, fp_super_iend_fine, fp_super_jstart_fine, fp_super_jend_fine)

  end subroutine mn_latlon_read_hires_parent

  !>@brief The subroutine 'mn_orog_read_hires_parent' loads parent orography data from netCDF
  !>@details Gathers a number of terrain-related variables from the netCDF file
  subroutine mn_orog_read_hires_parent(npx, npy, refine, pelist, surface_dir, filtered_terrain, orog_grid, orog_std_grid, ls_mask_grid, land_frac_grid, parent_tile)
    integer, intent(in)                :: npx, npy, refine   !< Number of points in x,y, and refinement
    integer, allocatable, intent(in)   :: pelist(:)          !< PE list for fms2_io
    character(len=*), intent(in)       :: surface_dir        !< Surface directory to read netCDF file from
    logical, intent(in)                :: filtered_terrain   !< Whether to use filtered terrain
    real, allocatable, intent(out)     :: orog_grid(:,:)     !< Output orography grid
    real, allocatable, intent(out)     :: orog_std_grid(:,:) !< Output orography standard deviation grid
    real, allocatable, intent(out)     :: ls_mask_grid(:,:)  !< Output land sea mask grid
    real, allocatable, intent(out)     :: land_frac_grid(:,:)!< Output land fraction grid
    integer, intent(in)                :: parent_tile        !< Parent tile number

    integer :: nx_cubic, nx, ny, fp_nx, fp_ny, mid_nx, mid_ny
    integer :: fp_istart_fine, fp_iend_fine, fp_jstart_fine, fp_jend_fine
    character(len=512) :: nc_filename
    character(len=16)  :: orog_var_name
    integer :: this_pe

    this_pe = mpp_pe()

    nx_cubic = npx - 1
    nx = npx - 1
    ny = npy - 1

    fp_istart_fine = 0
    fp_iend_fine = nx * refine
    fp_jstart_fine = 0
    fp_jend_fine = ny * refine

    fp_nx = fp_iend_fine - fp_istart_fine
    fp_ny = fp_jend_fine - fp_jstart_fine

    mid_nx = (fp_iend_fine - fp_istart_fine) / 2
    mid_ny = (fp_jend_fine - fp_jstart_fine) / 2

    call mn_static_filename(surface_dir, parent_tile, 'oro_data', refine, nc_filename)

    if (filtered_terrain) then
      orog_var_name = 'orog_filt'
    else
      orog_var_name = 'orog_raw'
    endif

    call alloc_read_data(nc_filename, orog_var_name, fp_nx, fp_ny, orog_grid, pelist)
    call alloc_read_data(nc_filename, 'slmsk', fp_nx, fp_ny, ls_mask_grid, pelist)

    call alloc_read_data(nc_filename, 'stddev', fp_nx, fp_ny, orog_std_grid, pelist)      ! TODO validate if this is needed
    call alloc_read_data(nc_filename, 'land_frac', fp_nx, fp_ny, land_frac_grid, pelist)  ! TODO validate if this is needed

  end subroutine mn_orog_read_hires_parent

  !>@brief The subroutine 'mn_static_read_hires_r4' loads high resolution data from netCDF
  !>@details Gathers a single variable from the netCDF file
  subroutine mn_static_read_hires_r4(npx, npy, refine, pelist, surface_dir, file_prefix, var_name, data_grid, parent_tile, time)
    integer, intent(in)                :: npx, npy, refine           !< Number of x,y points and nest refinement
    integer, allocatable, intent(in)   :: pelist(:)                  !< PE list for fms2_io
    character(len=*), intent(in)       :: surface_dir, file_prefix   !< Surface directory and file tag
    character(len=*), intent(in)       :: var_name                   !< Variable name in netCDF file
    real*4, allocatable, intent(out)   :: data_grid(:,:)             !< Output data grid
    integer, intent(in)                :: parent_tile                !< Parent tile number
    integer, intent(in), optional      :: time                       !< Optional month number for time-varying parameters

    character(len=512) :: nc_filename
    integer :: nx_cubic, nx, ny, fp_nx, fp_ny
    integer :: fp_istart_fine, fp_iend_fine, fp_jstart_fine, fp_jend_fine

    nx_cubic = npx - 1
    nx = npx - 1
    ny = npy - 1

    fp_istart_fine = 0
    fp_iend_fine = nx * refine
    fp_jstart_fine = 0
    fp_jend_fine = ny * refine

    fp_nx = fp_iend_fine - fp_istart_fine
    fp_ny = fp_jend_fine - fp_jstart_fine

    call mn_static_filename(surface_dir, parent_tile, file_prefix, refine, nc_filename)

    if (present(time)) then
      call alloc_read_data(nc_filename, var_name, fp_nx, fp_ny, data_grid, pelist, time)
    else
      call alloc_read_data(nc_filename, var_name, fp_nx, fp_ny, data_grid, pelist)
    endif

  end subroutine mn_static_read_hires_r4

  !>@brief The subroutine 'mn_static_read_hires_r8' loads high resolution data from netCDF
  !>@details Gathers a single variable from the netCDF file
  subroutine mn_static_read_hires_r8(npx, npy, refine, pelist, surface_dir, file_prefix, var_name, data_grid, parent_tile)
    integer, intent(in)                :: npx, npy, refine           !< Number of x,y points and nest refinement
    integer, allocatable, intent(in)   :: pelist(:)                  !< PE list for fms2_io
    character(len=*), intent(in)       :: surface_dir, file_prefix   !< Surface directory and file tag
    character(len=*), intent(in)       :: var_name                   !< Variable name in netCDF file
    real*8, allocatable, intent(out)   :: data_grid(:,:)             !< Output data grid
    integer, intent(in)                :: parent_tile                !< Parent tile number

    character(len=512) :: nc_filename

    integer :: nx_cubic, nx, ny, fp_nx, fp_ny
    integer :: fp_istart_fine, fp_iend_fine, fp_jstart_fine, fp_jend_fine

    nx_cubic = npx - 1
    nx = npx - 1
    ny = npy - 1

    fp_istart_fine = 0
    fp_iend_fine = nx * refine
    fp_jstart_fine = 0
    fp_jend_fine = ny * refine

    fp_nx = fp_iend_fine - fp_istart_fine
    fp_ny = fp_jend_fine - fp_jstart_fine

    ! TODO consider adding optional time argument as in mn_static_read_hires_r4

    call mn_static_filename(surface_dir, parent_tile, file_prefix, refine, nc_filename)

    call alloc_read_data(nc_filename, var_name, fp_nx, fp_ny, data_grid, pelist)

  end subroutine mn_static_read_hires_r8


  !!============================================================================
  !! Step 5.2 -- Recalculate nest halo weights
  !!============================================================================

  !>@brief The subroutine 'mn_meta_recalc' recalculates nest halo weights
  subroutine mn_meta_recalc( delta_i_c, delta_j_c, x_refine, y_refine, tile_geo, parent_geo, fp_super_tile_geo, &
      is_fine_pe, nest_domain, position, p_grid, n_grid, wt, istart_coarse, jstart_coarse, ind)
    integer, intent(in)                           :: delta_i_c, delta_j_c                     !< Nest motion in delta i,j
    integer, intent(in)                           :: x_refine, y_refine                       !< Nest refinement
    type(grid_geometry), intent(inout)            :: tile_geo, parent_geo, fp_super_tile_geo  !< tile geometries
    logical, intent(in)                           :: is_fine_pe                               !< Is this a nest PE?
    type(nest_domain_type), intent(in)            :: nest_domain                              !< Nest domain structure
    real(kind=R_GRID), allocatable, intent(inout) :: p_grid(:,:,:)                            !< Parent lat/lon grid
    real(kind=R_GRID), allocatable, intent(inout) :: n_grid(:,:,:)                            !< Nest lat/lon grid
    real, allocatable, intent(inout)              :: wt(:,:,:)                                !< Interpolation weights
    integer, intent(inout)                        :: position                                 !< Stagger
    integer, intent(in)                           :: istart_coarse, jstart_coarse             !< Initian nest offsets
    integer, allocatable, intent(in)              :: ind(:,:,:)

    type(bbox) :: wt_fine, wt_coarse
    integer    :: istag, jstag
    integer    :: this_pe

    this_pe = mpp_pe()

    ! Update the coarse and fine indices after shifting the nest
    if (is_fine_pe) then

      !!===========================================================
      !!
      !!  Recalculate halo weights
      !!
      !!===========================================================

      if (position .eq. CENTER) then
        istag = 0
        jstag = 0
      elseif (position .eq. NORTH) then
        ! for p_grid_u
        istag = 1
        jstag = 0

        !! This aligns with boundary.F90 settings
        !!istag = 0
        !!jstag = 1

      elseif (position .eq. EAST) then
        ! for p_grid_v
        istag = 0
        jstag = 1

        !! This aligns with boundary.F90 settings
        !istag = 1
        !jstag = 0

      endif


      call bbox_get_C2F_index(nest_domain, wt_fine, wt_coarse, EAST,  position)
      call calc_nest_halo_weights(wt_fine, wt_coarse, p_grid, n_grid, wt, istart_coarse, jstart_coarse, x_refine, y_refine, istag, jstag, ind)

      call bbox_get_C2F_index(nest_domain, wt_fine, wt_coarse, WEST,  position)
      call calc_nest_halo_weights(wt_fine, wt_coarse, p_grid, n_grid, wt, istart_coarse, jstart_coarse, x_refine, y_refine, istag, jstag, ind)

      call bbox_get_C2F_index(nest_domain, wt_fine, wt_coarse, NORTH,  position)
      call calc_nest_halo_weights(wt_fine, wt_coarse, p_grid, n_grid, wt, istart_coarse, jstart_coarse, x_refine, y_refine, istag, jstag, ind)

      call bbox_get_C2F_index(nest_domain, wt_fine, wt_coarse, SOUTH,  position)
      call calc_nest_halo_weights(wt_fine, wt_coarse, p_grid, n_grid, wt, istart_coarse, jstart_coarse, x_refine, y_refine, istag, jstag, ind)

    endif

  end subroutine mn_meta_recalc


  !!============================================================================
  !! Step 5.3 -- Adjust index by delta_i_c, delta_j_c
  !!============================================================================

  !>@brief The subroutine 'mn_shift_index' adjusts the index array for a nest move
  !>@details Fast routine to increment indices by the delta in i,j direction
  subroutine mn_shift_index(delta_i_c, delta_j_c, ind)
    integer, intent(in)                    :: delta_i_c, delta_j_c    !< Nest move deltas in i,j
    integer, allocatable, intent(inout)    :: ind(:,:,:)              !< Nest to parent index

    ! Shift the index by the delta of this nest move.
    ! TODO -- validate that we are not moving off the edge of the parent grid.
    integer  :: i, j

    do i = lbound(ind,1), ubound(ind,1)
      do j = lbound(ind,2), ubound(ind,2)
        ind(i,j,1) = ind(i,j,1) + delta_i_c
        ind(i,j,2) = ind(i,j,2) + delta_j_c
      enddo
    enddo

  end subroutine mn_shift_index


  !================================================================================
  !
  !  Prognostic and Physics Variable Nest Motion
  !
  !================================================================================

  !!============================================================================
  !! Step 6   Shift the data on each nest PE
  !!            -- similar to med_nest_move in HWRF
  !!============================================================================

  !>@brief The subroutine 'mn_prog_shift_data' shifts the data on each nest PE
  !>@details Iterates through the prognostic variables
  subroutine mn_prog_shift_data(Atm, n, child_grid_num, wt_h, wt_u, wt_v, &
      delta_i_c, delta_j_c, x_refine, y_refine, &
      is_fine_pe, nest_domain, nz)
    type(fv_atmos_type), allocatable, target, intent(inout)  :: Atm(:)                                      !< Atm data array
    integer, intent(in)                                      :: n, child_grid_num                           !< Grid numbers
    real, allocatable, intent(in)                            :: wt_h(:,:,:), wt_u(:,:,:), wt_v(:,:,:)       !< Interpolation weights
    integer, intent(in)                                      :: delta_i_c, delta_j_c, x_refine, y_refine    !< Delta i,j, nest refinement
    logical, intent(in)                                      :: is_fine_pe                                  !< Is this is a nest PE?
    type(nest_domain_type), intent(inout)                    :: nest_domain                                 !< Nest domain structure
    integer, intent(in)                                      :: nz                                          !< Number of vertical levels

    ! Constants for mpp calls
    integer  :: interp_type   = 1    ! cell-centered A-grid
    integer  :: interp_type_u = 4    ! D-grid
    integer  :: interp_type_v = 4    ! D-grid
    integer  :: position      = CENTER ! CENTER, NORTH, EAST
    integer  :: position_u    = NORTH
    integer  :: position_v    = EAST

    type(fv_moving_nest_prog_type), pointer :: mn_prog

    mn_prog => Moving_nest(n)%mn_prog

    call mn_var_shift_data(Atm(n)%q_con, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    call mn_var_shift_data(Atm(n)%pt, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    call mn_var_shift_data(Atm(n)%w, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    !call mn_var_shift_data(Atm(n)%omga, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
    !     delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    call mn_var_shift_data(Atm(n)%delp, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    !call mn_var_shift_data(Atm(n)%delz, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
    !     delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    call mn_var_shift_data(mn_prog%delz, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    if (Moving_nest(n)%mn_flag%terrain_smoother .eq. 4) then
      call mn_var_shift_data(Atm(n)%phis, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
          delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position)
    endif

    call mn_var_shift_data(Atm(n)%ua, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    call mn_var_shift_data(Atm(n)%va, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    call mn_var_shift_data(Atm(n)%q, interp_type, wt_h, Atm(child_grid_num)%neststruct%ind_h, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    call mn_var_shift_data(Atm(n)%u, interp_type_u, wt_u, Atm(child_grid_num)%neststruct%ind_u, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position_u, nz)

    call mn_var_shift_data(Atm(n)%v, interp_type_v, wt_v, Atm(child_grid_num)%neststruct%ind_v, &
        delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position_v, nz)

  end subroutine mn_prog_shift_data


  !!============================================================================
  !! Step 6 - per variable
  !!============================================================================

  !>@brief The subroutine 'mn_prog_shift_data_r4_2d' shifts the data for a variable on each nest PE
  !>@details For single variable
  subroutine mn_var_shift_data_r4_2d(data_var, interp_type, wt, ind, delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position)
    real*4, allocatable, intent(inout)          :: data_var(:,:)                                !< Data variable
    integer, intent(in)                         :: interp_type                                  !< Interpolation stagger type
    real, allocatable, intent(in)               :: wt(:,:,:)                                    !< Interpolation weight array
    integer, allocatable, intent(in)            :: ind(:,:,:)                                   !< Fine to coarse index array
    integer, intent(in)                         :: delta_i_c, delta_j_c, x_refine, y_refine     !< delta i,j for nest move.  Nest refinement.
    logical, intent(in)                         :: is_fine_pe                                   !< Is nest PE?
    type(nest_domain_type), intent(inout)       :: nest_domain                                  !< Nest domain structure
    integer, intent(in)                         :: position                                     !< Grid offset

    real*4, dimension(:,:), allocatable :: nbuffer, sbuffer, ebuffer, wbuffer
    type(bbox)      :: north_fine, north_coarse ! step 4
    type(bbox)      :: south_fine, south_coarse
    type(bbox)      :: east_fine, east_coarse
    type(bbox)      :: west_fine, west_coarse
    integer         :: nest_level = 1  ! TODO allow to vary

    !!===========================================================
    !!
    !! Fill halo buffers
    !!
    !!===========================================================

    call alloc_halo_buffer(nbuffer, north_fine, north_coarse, nest_domain, NORTH,  position)
    call alloc_halo_buffer(sbuffer, south_fine, south_coarse, nest_domain, SOUTH,  position)
    call alloc_halo_buffer(ebuffer, east_fine,  east_coarse,  nest_domain, EAST,   position)
    call alloc_halo_buffer(wbuffer, west_fine,  west_coarse,  nest_domain, WEST,   position)

    !====================================================
    ! Passes data from coarse grid to fine grid's halo buffers; requires nest_domain to be intent(inout)
    call mpp_update_nest_fine(data_var, nest_domain, wbuffer, sbuffer, ebuffer, nbuffer, nest_level, position=position)

    if (is_fine_pe) then

      !!===========================================================
      !!
      !! Shift grids internal to each nest PE
      !!
      !!===========================================================

      if ( delta_i_c .ne. 0 ) then
        data_var = eoshift(data_var, x_refine * delta_i_c, DIM=1)
      endif

      if (delta_j_c .ne.  0) then
        data_var = eoshift(data_var, y_refine * delta_j_c, DIM=2)
      endif

      !!===========================================================
      !!
      !! Apply halo data
      !!
      !!===========================================================


      call fill_nest_from_buffer(interp_type, data_var, nbuffer, north_fine, north_coarse, NORTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, sbuffer, south_fine, south_coarse, SOUTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, ebuffer, east_fine, east_coarse, EAST, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, wbuffer, west_fine, west_coarse, WEST, x_refine, y_refine, wt, ind)
    endif

    deallocate(nbuffer)
    deallocate(sbuffer)
    deallocate(ebuffer)
    deallocate(wbuffer)

  end subroutine mn_var_shift_data_r4_2d

  !>@brief The subroutine 'mn_prog_shift_data_r8_2d' shifts the data for a variable on each nest PE
  !>@details For one double precision 2D variable
  subroutine mn_var_shift_data_r8_2d(data_var, interp_type, wt, ind, delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position)

    real*8, allocatable, intent(inout)          :: data_var(:,:)                            !< Data variable
    integer, intent(in)                         :: interp_type                              !< Interpolation stagger type
    real, allocatable, intent(in)               :: wt(:,:,:)                                !< Interpolation weight array
    integer, allocatable, intent(in)            :: ind(:,:,:)                               !< Fine to coarse index array
    integer, intent(in)                         :: delta_i_c, delta_j_c, x_refine, y_refine !< delta i,j for nest move.  Nest refinement.
    logical, intent(in)                         :: is_fine_pe                                   !< Is nest PE?
    type(nest_domain_type), intent(inout)       :: nest_domain                                  !< Nest domain structure
    integer, intent(in)                         :: position                                     !< Grid offset

    real*8, dimension(:,:), allocatable :: nbuffer, sbuffer, ebuffer, wbuffer
    type(bbox)      :: north_fine, north_coarse ! step 4
    type(bbox)      :: south_fine, south_coarse
    type(bbox)      :: east_fine, east_coarse
    type(bbox)      :: west_fine, west_coarse
    integer         :: nest_level = 1  ! TODO allow to vary

    !!===========================================================
    !!
    !! Fill halo buffers
    !!
    !!===========================================================

    call alloc_halo_buffer(nbuffer, north_fine, north_coarse, nest_domain, NORTH,  position)
    call alloc_halo_buffer(sbuffer, south_fine, south_coarse, nest_domain, SOUTH,  position)
    call alloc_halo_buffer(ebuffer, east_fine,  east_coarse,  nest_domain, EAST,   position)
    call alloc_halo_buffer(wbuffer, west_fine,  west_coarse,  nest_domain, WEST,   position)

    ! Passes data from coarse grid to fine grid's halo buffers; requires nest_domain to be intent(inout)
    call mpp_update_nest_fine(data_var, nest_domain, wbuffer, sbuffer, ebuffer, nbuffer, nest_level, position=position)

    if (is_fine_pe) then

      !!===========================================================
      !!
      !! Shift grids internal to each nest PE
      !!
      !!===========================================================

      if ( delta_i_c .ne. 0 ) then
        data_var = eoshift(data_var, x_refine * delta_i_c, DIM=1)
      endif

      if (delta_j_c .ne.  0) then
        data_var = eoshift(data_var, y_refine * delta_j_c, DIM=2)
      endif

      !!===========================================================
      !!
      !! Apply halo data
      !!
      !!===========================================================

      call fill_nest_from_buffer(interp_type, data_var, nbuffer, north_fine, north_coarse, NORTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, sbuffer, south_fine, south_coarse, SOUTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, ebuffer, east_fine, east_coarse, EAST, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, wbuffer, west_fine, west_coarse, WEST, x_refine, y_refine, wt, ind)
    endif

    deallocate(nbuffer)
    deallocate(sbuffer)
    deallocate(ebuffer)
    deallocate(wbuffer)

  end subroutine mn_var_shift_data_r8_2d

  !>@brief The subroutine 'mn_prog_shift_data_r4_3d' shifts the data for a variable on each nest PE
  !>@details For one single precision 3D variable
  subroutine mn_var_shift_data_r4_3d(data_var, interp_type, wt, ind, delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    real*4, allocatable, intent(inout)          :: data_var(:,:,:)                          !< Data variable
    integer, intent(in)                         :: interp_type                              !< Interpolation stagger type
    real, allocatable, intent(in)               :: wt(:,:,:)                                !< Interpolation weight array
    integer, allocatable, intent(in)            :: ind(:,:,:)                               !< Fine to coarse index array
    integer, intent(in)                         :: delta_i_c, delta_j_c, x_refine, y_refine !< delta i,j for nest move.  Nest refinement.
    logical, intent(in)                         :: is_fine_pe                               !< Is nest PE?
    type(nest_domain_type), intent(inout)       :: nest_domain                              !< Nest domain structure
    integer, intent(in)                         :: position, nz                             !< Grid offset, number of vertical levels

    real*4, dimension(:,:,:), allocatable :: nbuffer, sbuffer, ebuffer, wbuffer
    type(bbox)      :: north_fine, north_coarse ! step 4
    type(bbox)      :: south_fine, south_coarse
    type(bbox)      :: east_fine, east_coarse
    type(bbox)      :: west_fine, west_coarse
    integer         :: nest_level = 1  ! TODO allow to vary

    !!===========================================================
    !!
    !! Fill halo buffers
    !!
    !!===========================================================

    call alloc_halo_buffer(nbuffer, north_fine, north_coarse, nest_domain, NORTH,  position, nz)
    call alloc_halo_buffer(sbuffer, south_fine, south_coarse, nest_domain, SOUTH,  position, nz)
    call alloc_halo_buffer(ebuffer, east_fine,  east_coarse,  nest_domain, EAST,   position, nz)
    call alloc_halo_buffer(wbuffer, west_fine,  west_coarse,  nest_domain, WEST,   position, nz)


    !====================================================
    ! Passes data from coarse grid to fine grid's halo buffers; requires nest_domain to be intent(inout)
    call mpp_update_nest_fine(data_var, nest_domain, wbuffer, sbuffer, ebuffer, nbuffer, nest_level, position=position)

    if (is_fine_pe) then

      !!===========================================================
      !!
      !! Shift grids internal to each nest PE
      !!
      !!===========================================================

      if ( delta_i_c .ne. 0 ) then
        data_var = eoshift(data_var, x_refine * delta_i_c, DIM=1)
      endif

      if (delta_j_c .ne.  0) then
        data_var = eoshift(data_var, y_refine * delta_j_c, DIM=2)
      endif

      !!===========================================================
      !!
      !! Apply halo data
      !!
      !!===========================================================

      call fill_nest_from_buffer(interp_type, data_var, nbuffer, north_fine, north_coarse, nz, NORTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, sbuffer, south_fine, south_coarse, nz, SOUTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, ebuffer, east_fine, east_coarse, nz, EAST, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, wbuffer, west_fine, west_coarse, nz, WEST, x_refine, y_refine, wt, ind)
    endif

    deallocate(nbuffer)
    deallocate(sbuffer)
    deallocate(ebuffer)
    deallocate(wbuffer)

  end subroutine mn_var_shift_data_r4_3d


  !>@brief The subroutine 'mn_prog_shift_data_r8_3d' shifts the data for a variable on each nest PE
  !>@details For one double precision 3D variable
  subroutine mn_var_shift_data_r8_3d(data_var, interp_type, wt, ind, delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)

    real*8, allocatable, intent(inout)          :: data_var(:,:,:)                              !< Data variable
    integer, intent(in)                         :: interp_type                                  !< Interpolation stagger type
    real, allocatable, intent(in)               :: wt(:,:,:)                                    !< Interpolation weight array
    integer, allocatable, intent(in)            :: ind(:,:,:)                                   !< Fine to coarse index array
    integer, intent(in)                         :: delta_i_c, delta_j_c, x_refine, y_refine     !< delta i,j for nest move.  Nest refinement.
    logical, intent(in)                         :: is_fine_pe                                   !< Is nest PE?
    type(nest_domain_type), intent(inout)       :: nest_domain                                  !< Nest domain structure
    integer, intent(in)                         :: position, nz                                 !< Grid offset, number vertical levels

    real*8, dimension(:,:,:), allocatable :: nbuffer, sbuffer, ebuffer, wbuffer
    type(bbox)      :: north_fine, north_coarse ! step 4
    type(bbox)      :: south_fine, south_coarse
    type(bbox)      :: east_fine, east_coarse
    type(bbox)      :: west_fine, west_coarse
    integer         :: nest_level = 1  ! TODO allow to vary

    !!===========================================================
    !!
    !! Fill halo buffers
    !!
    !!===========================================================

    call alloc_halo_buffer(nbuffer, north_fine, north_coarse, nest_domain, NORTH,  position, nz)
    call alloc_halo_buffer(sbuffer, south_fine, south_coarse, nest_domain, SOUTH,  position, nz)
    call alloc_halo_buffer(ebuffer, east_fine,  east_coarse,  nest_domain, EAST,   position, nz)
    call alloc_halo_buffer(wbuffer, west_fine,  west_coarse,  nest_domain, WEST,   position, nz)

    !====================================================
    ! Passes data from coarse grid to fine grid's halo buffers; requires nest_domain to be intent(inout)
    call mpp_update_nest_fine(data_var, nest_domain, wbuffer, sbuffer, ebuffer, nbuffer, nest_level, position=position)

    if (is_fine_pe) then
      !!===========================================================
      !!
      !! Shift grids internal to each nest PE
      !!
      !!===========================================================

      if ( delta_i_c .ne. 0 ) then
        data_var = eoshift(data_var, x_refine * delta_i_c, DIM=1)
      endif

      if (delta_j_c .ne.  0) then
        data_var = eoshift(data_var, y_refine * delta_j_c, DIM=2)
      endif

      !!===========================================================
      !!
      !! Apply halo data
      !!
      !!===========================================================

      call fill_nest_from_buffer(interp_type, data_var, nbuffer, north_fine, north_coarse, nz, NORTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, sbuffer, south_fine, south_coarse, nz, SOUTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, ebuffer, east_fine, east_coarse, nz, EAST, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, wbuffer, west_fine, west_coarse, nz, WEST, x_refine, y_refine, wt, ind)
    endif

    deallocate(nbuffer)
    deallocate(sbuffer)
    deallocate(ebuffer)
    deallocate(wbuffer)

  end subroutine mn_var_shift_data_r8_3d


  !>@brief The subroutine 'mn_prog_shift_data_r4_4d' shifts the data for a variable on each nest PE
  !>@details For one single precision 4D variable
  subroutine mn_var_shift_data_r4_4d(data_var, interp_type, wt, ind, delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    real*4, allocatable, intent(inout)          :: data_var(:,:,:,:)                            !< Data variable
    integer, intent(in)                         :: interp_type                                  !< Interpolation stagger type
    real, allocatable, intent(in)               :: wt(:,:,:)                                    !< Interpolation weight array
    integer, allocatable, intent(in)            :: ind(:,:,:)                                   !< Fine to coarse index array
    integer, intent(in)                         :: delta_i_c, delta_j_c, x_refine, y_refine     !< delta i,j for nest move.  Nest refinement.
    logical, intent(in)                         :: is_fine_pe                                   !< Is nest PE?
    type(nest_domain_type), intent(inout)       :: nest_domain                                  !< Nest domain structure
    integer, intent(in)                         :: position, nz                                 !< Grid offset, number of vertical levels

    real*4, dimension(:,:,:,:), allocatable     :: nbuffer, sbuffer, ebuffer, wbuffer
    type(bbox)      :: north_fine, north_coarse ! step 4
    type(bbox)      :: south_fine, south_coarse
    type(bbox)      :: east_fine, east_coarse
    type(bbox)      :: west_fine, west_coarse
    integer         :: n4d
    integer         :: nest_level = 1  ! TODO allow to vary

    n4d = ubound(data_var, 4)

    !!===========================================================
    !!
    !! Fill halo buffers
    !!
    !!===========================================================

    call alloc_halo_buffer(nbuffer, north_fine, north_coarse, nest_domain, NORTH,  position, nz, n4d)
    call alloc_halo_buffer(sbuffer, south_fine, south_coarse, nest_domain, SOUTH,  position, nz, n4d)
    call alloc_halo_buffer(ebuffer, east_fine,  east_coarse,  nest_domain, EAST,   position, nz, n4d)
    call alloc_halo_buffer(wbuffer, west_fine,  west_coarse,  nest_domain, WEST,   position, nz, n4d)

    !====================================================

    ! Passes data from coarse grid to fine grid's halo
    call mpp_update_nest_fine(data_var, nest_domain, wbuffer, sbuffer, ebuffer, nbuffer, nest_level, position=position)

    if (is_fine_pe) then
      !!===========================================================
      !!
      !! Shift grids internal to each nest PE
      !!
      !!===========================================================

      if ( delta_i_c .ne. 0 ) then
        data_var = eoshift(data_var, x_refine * delta_i_c, DIM=1)
      endif

      if (delta_j_c .ne.  0) then
        data_var = eoshift(data_var, y_refine * delta_j_c, DIM=2)
      endif

      !!===========================================================
      !!
      !! Apply halo data
      !!
      !!===========================================================

      call fill_nest_from_buffer(interp_type, data_var, nbuffer, north_fine, north_coarse, nz, NORTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, sbuffer, south_fine, south_coarse, nz, SOUTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, ebuffer, east_fine, east_coarse, nz, EAST, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, wbuffer, west_fine, west_coarse, nz, WEST, x_refine, y_refine, wt, ind)
    endif

    deallocate(nbuffer)
    deallocate(sbuffer)
    deallocate(ebuffer)
    deallocate(wbuffer)

  end subroutine mn_var_shift_data_r4_4d


  !>@brief The subroutine 'mn_prog_shift_data_r8_4d' shifts the data for a variable on each nest PE
  !>@details For one double precision 4D variable
  subroutine mn_var_shift_data_r8_4d(data_var, interp_type, wt, ind, delta_i_c, delta_j_c, x_refine, y_refine, is_fine_pe, nest_domain, position, nz)
    real*8, allocatable, intent(inout)          :: data_var(:,:,:,:)                            !< Data variable
    integer, intent(in)                         :: interp_type                                  !< Interpolation stagger type
    real, allocatable, intent(in)               :: wt(:,:,:)                                    !< Interpolation weight array
    integer, allocatable, intent(in)            :: ind(:,:,:)                                   !< Fine to coarse index array
    integer, intent(in)                         :: delta_i_c, delta_j_c, x_refine, y_refine     !< delta i,j for nest move.  Nest refinement.
    logical, intent(in)                         :: is_fine_pe                                   !< Is nest PE?
    type(nest_domain_type), intent(inout)       :: nest_domain                                  !< Nest domain structure
    integer, intent(in)                         :: position, nz                                 !< Grid offset, number of vertical levels

    real*8, dimension(:,:,:,:), allocatable     :: nbuffer, sbuffer, ebuffer, wbuffer
    type(bbox)      :: north_fine, north_coarse ! step 4
    type(bbox)      :: south_fine, south_coarse
    type(bbox)      :: east_fine, east_coarse
    type(bbox)      :: west_fine, west_coarse
    integer         :: n4d
    integer         :: nest_level = 1  ! TODO allow to vary

    n4d = ubound(data_var, 4)

    !!===========================================================
    !!
    !! Fill halo buffers
    !!
    !!===========================================================

    call alloc_halo_buffer(nbuffer, north_fine, north_coarse, nest_domain, NORTH,  position, nz, n4d)
    call alloc_halo_buffer(sbuffer, south_fine, south_coarse, nest_domain, SOUTH,  position, nz, n4d)
    call alloc_halo_buffer(ebuffer, east_fine,  east_coarse,  nest_domain, EAST,   position, nz, n4d)
    call alloc_halo_buffer(wbuffer, west_fine,  west_coarse,  nest_domain, WEST,   position, nz, n4d)

    !====================================================
    ! Passes data from coarse grid to fine grid's halo
    call mpp_update_nest_fine(data_var, nest_domain, wbuffer, sbuffer, ebuffer, nbuffer, nest_level, position=position)

    if (is_fine_pe) then
      !!===========================================================
      !!
      !! Shift grids internal to each nest PE
      !!
      !!===========================================================

      if ( delta_i_c .ne. 0 ) then
        data_var = eoshift(data_var, x_refine * delta_i_c, DIM=1)
      endif

      if (delta_j_c .ne.  0) then
        data_var = eoshift(data_var, y_refine * delta_j_c, DIM=2)
      endif

      !!===========================================================
      !!
      !! Apply halo data
      !!
      !!===========================================================

      call fill_nest_from_buffer(interp_type, data_var, nbuffer, north_fine, north_coarse, nz, NORTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, sbuffer, south_fine, south_coarse, nz, SOUTH, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, ebuffer, east_fine, east_coarse, nz, EAST, x_refine, y_refine, wt, ind)
      call fill_nest_from_buffer(interp_type, data_var, wbuffer, west_fine, west_coarse, nz, WEST, x_refine, y_refine, wt, ind)
    endif

    deallocate(nbuffer)
    deallocate(sbuffer)
    deallocate(ebuffer)
    deallocate(wbuffer)

  end subroutine mn_var_shift_data_r8_4d


  !================================================================================
  !
  !  Step 7 -- Gridstruct resetting and reallocation of static buffers
  !      init_grid() also updates the wt arrays
  !================================================================================

  !>@brief The subroutine 'mn_meta_reset_gridstruct' resets navigation data and reallocates needed data in the gridstruct after nest move
  !>@details This routine is computationally demanding and is a target for later optimization.
  subroutine mn_meta_reset_gridstruct(Atm, n, child_grid_num, nest_domain, fp_super_tile_geo, x_refine, y_refine, is_fine_pe, wt_h, wt_u, wt_v, a_step, dt_atmos)
    type(fv_atmos_type), allocatable, target, intent(inout)  :: Atm(:)                               !< Atm data array
    integer, intent(in)                              :: n, child_grid_num                            !< This level and nest level
    type(nest_domain_type),     intent(in)           :: nest_domain                                  !< Nest domain structure
    type(grid_geometry), intent(in)                  :: fp_super_tile_geo                            !< Parent high-resolution geometry
    integer, intent(in)                              :: x_refine, y_refine                           !< Nest refinement
    logical, intent(in)                              :: is_fine_pe                                   !< Is nest PE?
    real, allocatable, intent(in)                    :: wt_h(:,:,:), wt_u(:,:,:), wt_v(:,:,:)        !< Interpolation weights
    integer, intent(in)                              :: a_step                                       !< Which timestep
    real, intent(in)                                 :: dt_atmos                                     !< Timestep duration in seconds

    integer :: isg, ieg, jsg, jeg
    integer :: ng, pp, nn, parent_tile, refinement, ioffset, joffset
    integer :: this_pe, gid
    integer :: tile_coarse(2)

    real(kind=R_GRID)   :: pi = 4 * atan(1.0d0)
    real                :: rad2deg

    ! Coriolis parameter variables
    real                :: alpha = 0.
    real, pointer, dimension(:,:,:) :: grid, agrid
    real, pointer, dimension(:,:) :: fC, f0
    integer             :: isd, ied, jsd, jed
    integer             :: i, j

    logical, save       :: first_time = .true.
    integer, save       :: id_reset1, id_reset2, id_reset3, id_reset4, id_reset5, id_reset6, id_reset7

    logical             :: use_timers   !  Set this to true to generate performance profiling information in out.* file

    use_timers = Atm(n)%flagstruct%fv_timers

    if (first_time .and. use_timers) then
      id_reset1     = mpp_clock_id ('MN 7 Reset 1',  flags = clock_flag_default, grain=CLOCK_ROUTINE )
      id_reset2     = mpp_clock_id ('MN 7 Reset 2',  flags = clock_flag_default, grain=CLOCK_ROUTINE )
      id_reset3     = mpp_clock_id ('MN 7 Reset 3',  flags = clock_flag_default, grain=CLOCK_ROUTINE )
      id_reset4     = mpp_clock_id ('MN 7 Reset 4',  flags = clock_flag_default, grain=CLOCK_ROUTINE )
      id_reset5     = mpp_clock_id ('MN 7 Reset 5',  flags = clock_flag_default, grain=CLOCK_ROUTINE )
      id_reset6     = mpp_clock_id ('MN 7 Reset 6',  flags = clock_flag_default, grain=CLOCK_ROUTINE )
      id_reset7     = mpp_clock_id ('MN 7 Reset 7',  flags = clock_flag_default, grain=CLOCK_ROUTINE )
    endif

    rad2deg = 180.0 / pi

    this_pe = mpp_pe()
    gid = this_pe

    parent_tile = Atm(child_grid_num)%neststruct%parent_tile
    ioffset = Atm(child_grid_num)%neststruct%ioffset
    joffset = Atm(child_grid_num)%neststruct%joffset

    !  Reset the gridstruct values for the nest
    if (is_fine_pe) then
      ! Fill in values from high resolution, full panel, supergrid
      if (use_timers) call mpp_clock_begin (id_reset1)

      call fill_grid_from_supergrid(Atm(n)%gridstruct%grid, CORNER, fp_super_tile_geo, ioffset, joffset, &
          x_refine, y_refine)
      call fill_grid_from_supergrid(Atm(n)%gridstruct%agrid, CENTER, fp_super_tile_geo, ioffset, joffset, &
          x_refine, y_refine)
      call fill_grid_from_supergrid(Atm(n)%gridstruct%grid_64, CORNER, fp_super_tile_geo, &
          ioffset, joffset, x_refine, y_refine)
      call fill_grid_from_supergrid(Atm(n)%gridstruct%agrid_64, CENTER, fp_super_tile_geo, &
          ioffset, joffset, x_refine, y_refine)

      ! Reset the coriolis parameters, using code from external_ic.F90::get_external_ic()

      isd = Atm(n)%bd%isd
      ied = Atm(n)%bd%ied
      jsd = Atm(n)%bd%jsd
      jed = Atm(n)%bd%jed

      grid  => Atm(n)%gridstruct%grid
      agrid => Atm(n)%gridstruct%agrid
      fC    => Atm(n)%gridstruct%fC
      f0    => Atm(n)%gridstruct%f0

      ! * Initialize coriolis param:

      do j=jsd,jed+1
        do i=isd,ied+1
          fC(i,j) = 2.*omega*( -1.*cos(grid(i,j,1))*cos(grid(i,j,2))*sin(alpha) + &
              sin(grid(i,j,2))*cos(alpha) )
        enddo
      enddo

      do j=jsd,jed
        do i=isd,ied
          f0(i,j) = 2.*omega*( -1.*cos(agrid(i,j,1))*cos(agrid(i,j,2))*sin(alpha) + &
              sin(agrid(i,j,2))*cos(alpha) )
        enddo
      enddo


      !! Let this get reset in init_grid()/setup_aligned_nest()
      !call fill_grid_from_supergrid(Atm(n)%grid_global, CORNER, fp_super_tile_geo, &
      !     ioffset, joffset, x_refine, y_refine)

      if (use_timers) call mpp_clock_end (id_reset1)
      if (use_timers) call mpp_clock_begin (id_reset2)

      ! TODO should these get reset by init_grid instead??
      call fill_weight_grid(Atm(n)%neststruct%wt_h, wt_h)
      call fill_weight_grid(Atm(n)%neststruct%wt_u, wt_u)
      call fill_weight_grid(Atm(n)%neststruct%wt_v, wt_v)
      ! TODO -- Seems like this is not used anywhere, other than being allocated, filled, deallocated
      !call fill_weight_grid(Atm(n)%neststruct%wt_b, wt_b)

      if (use_timers) call mpp_clock_end (id_reset2)

    endif

    if (use_timers) call mpp_clock_begin (id_reset3)

    ! TODO Write clearer comments on what is happening here.

    ! This code runs several communications steps:
    !  1.  As npe=0, it gets the global_grid domain setup
    !  2.  sends the global_grid to the other parent PEs
    !  3.  global_grid is received in call to setup_aligned_nest() in fv_grid_tools.F90::init_grid()
    !  Other communication is contained full within setup_aligned_nest().

    ! Sends around data from the parent grids, and recomputes the update indices
    ! This code copied from fv_control.F90
    ! Need to SEND grid_global to any child grids; this is received in setup_aligned_nest in fv_grid_tools
    ! if (Atm(pp)%neststruct%nested) then

    ! TODO phrase this more carefully to choose the parent master PE grid if we are operating in a nested setup.
    ! Unlike in fv_control.F90, this will be running on Atm(1) when it's on pe=0, so we don't need to navigate to parent_grid.

    first_time = .false.

    ! Seems like we do not need to resend this -- setup_aligned_nest now saves the parent tile information during model initialization,
    !  which happens before we enter the moving nest code.
    if (this_pe .eq. 0 .and. first_time) then

      ! This is the Atm index for the nest values.
      pp = child_grid_num

      refinement = x_refine
      ng = Atm(n)%ng

      call mpp_get_global_domain( Atm(n)%domain, isg, ieg, jsg, jeg)

      !FIXME: Should replace this by generating the global grid (or at least one face thereof) on the
      ! nested PEs instead of sending it around.
      !if (gid == Atm(pp)%parent_grid%pelist(1)) then

      call mpp_send(Atm(n)%grid_global(isg-ng:ieg+1+ng,jsg-ng:jeg+1+ng,1:2,parent_tile), &
          size(Atm(n)%grid_global(isg-ng:ieg+1+ng,jsg-ng:jeg+1+ng,1:2,parent_tile)), &
          Atm(pp)%pelist(1)) !send to p_ind in setup_aligned_nest

      call mpp_sync_self()
      !endif
    endif

    !if (ngrids > 1) call setup_update_regions   ! Originally from fv_control.F90
    call mn_setup_update_regions(Atm, n, nest_domain)

    if (use_timers) call mpp_clock_end (id_reset3)
    if (use_timers) call mpp_clock_begin (id_reset4)

    if (Atm(n)%neststruct%nested) then
      ! New code from fv_control.F90
      ! call init_grid(Atm(this_grid), Atm(this_grid)%flagstruct%grid_name, Atm(this_grid)%flagstruct%grid_file, &
      !    Atm(this_grid)%flagstruct%npx, Atm(this_grid)%flagstruct%npy, Atm(this_grid)%flagstruct%npz, Atm(this_grid)%flagstruct%ndims, Atm(this_grid)%flagstruct%ntiles, Atm(this_grid)%ng, tile_coarse)

      ! Atm(n)%neststruct%parent_tile            = tile_coarse(n)

      ! Old Code
      !call init_grid(Atm(n), Atm(n)%flagstruct%grid_name, Atm(n)%flagstruct%grid_file, &
      !     Atm(n)%npx, Atm(n)%npy, Atm(n)%npz, Atm(n)%flagstruct%ndims, Atm(n)%flagstruct%ntiles, Atm(n)%ng)

      !tile_coarse(1) = Atm(n)%neststruct%parent_tile
      tile_coarse(1) = parent_tile
      tile_coarse(2) = parent_tile

      call init_grid(Atm(n), Atm(n)%flagstruct%grid_name, Atm(n)%flagstruct%grid_file, &
          Atm(n)%flagstruct%npx, Atm(n)%flagstruct%npy, Atm(n)%flagstruct%npz, &
          Atm(n)%flagstruct%ndims, Atm(n)%flagstruct%ntiles, Atm(n)%ng, tile_coarse)
    endif

    if (use_timers) call mpp_clock_end (id_reset4)
    if (use_timers) call mpp_clock_begin (id_reset5)

    !  Reset the gridstruct values for the nest
    if (is_fine_pe) then
      call grid_utils_init(Atm(n), Atm(n)%npx, Atm(n)%npy, Atm(n)%npz, &
          Atm(n)%flagstruct%non_ortho, Atm(n)%flagstruct%grid_type, Atm(n)%flagstruct%c2l_ord)
    endif

    if (use_timers) call mpp_clock_end (id_reset5)
    if (use_timers) call mpp_clock_begin (id_reset6)

    !call mpp_sync(full_pelist)   ! Used to make debugging easier.  Can be removed.

    ! Needs to run for parent and nest Atm(2)
    !    Nest PEs update ind_update_h  -- this now seems obsolete
    !    Parent tile PEs update isu, ieu, jsu, jeu
    !    Global tiles that are not parent have no changes

    ! Update:  This is now accomplished with the earlier call to setup_update_regions()
    !call reinit_parent_indices(Atm(2))
    !!call reinit_parent_indices(Atm(n))

    ! Reallocate the halo buffers in the neststruct, as some are now the wrong size
    !   Optimization would be to only deallocate the edges that have changed.

    ! TODO Write comments on the t0 and t1 buffers
    if (use_timers) call mpp_clock_end (id_reset6)
    if (use_timers) call mpp_clock_begin (id_reset7)

    if (is_fine_pe) then
      !call reallocate_BC_buffers(Atm(child_grid_num))
      call reallocate_BC_buffers(Atm(1))

      ! Reallocate buffers that are declared in fv_nesting.F90
      call dealloc_nested_buffers(Atm(1))

      ! Set both to true so the call to setup_nested_grid_BCs() (at the beginning of fv_dynamics()) will reset t0 buffers
      ! They will be returned to false by setup_nested_grid_BCs()

      Atm(n)%neststruct%first_step = .true.
      !Atm(n)%flagstruct%make_nh= .true.

      !! Fill in the BC time1 buffers
      !call setup_nested_grid_BCs(npx, npy, npz, zvir, ncnst, &
      !     u, v, w, pt, delp, delz, q, uc, vc, pkz, &
      !     neststruct%nested, flagstruct%inline_q, flagstruct%make_nh, ng, &
      !     gridstruct, flagstruct, neststruct, &
      !     neststruct%nest_timestep, neststruct%tracer_nest_timestep, &
      !     domain, bd, nwat)

      ! Transfer the BC time1 buffers to time0

      !call set_NH_BCs_t0(neststruct)
      !call set_BCs_t0(ncnst, flagstruct%hydrostatic, neststruct)

    endif
    if (use_timers) call mpp_clock_end (id_reset7)

  end subroutine mn_meta_reset_gridstruct


  ! Copied and adapted from fv_control.F90::setup_update_regions(); where it is an internal subroutine
  ! Modifications only to pass necessary variables as arguments

  !>@brief The subroutine 'mn_setup_update_regions' performs some of the tasks of fv_control.F90::setup_update_regions() for nest motion
  !>@details This routine only updates indices, so is computationally efficient
  subroutine mn_setup_update_regions(Atm, this_grid, nest_domain)
    type(fv_atmos_type), allocatable, intent(INOUT) :: Atm(:)                 !< Array of atmospheric data
    integer, intent(IN)                             :: this_grid              !< Parent or child grid number
    type(nest_domain_type),     intent(in)          :: nest_domain            !< Nest domain structure

    integer :: isu, ieu, jsu, jeu ! update regions
    integer :: isc, jsc, iec, jec
    integer :: upoff
    integer :: ngrids, n, nn
    integer :: isu_stag, isv_stag, jsu_stag, jsv_stag
    integer :: ieu_stag, iev_stag, jeu_stag, jev_stag
    integer :: this_pe

    this_pe = mpp_pe()

    ! Need to get the following variables from nest_domain
    !   tile_coarse()
    !   icount_coarse()
    !         from mpp_define_nest_domains.inc:  iend_coarse(n) = istart_coarse(n) + icount_coarse(n) - 1
    !         rearrange to: iend_coarse(n) - istart_coarse(n) + 1 = icount_coarse(n)
    !   jcount_coarse()
    !   nest_ioffsets()
    !      in fv_control.F90. pass nest_ioffsets as istart_coarse
    !   nest_joffsets()

    isc = Atm(this_grid)%bd%isc
    jsc = Atm(this_grid)%bd%jsc
    iec = Atm(this_grid)%bd%iec
    jec = Atm(this_grid)%bd%jec

    upoff = Atm(this_grid)%neststruct%upoff

    ngrids = size(Atm)

    do n=2,ngrids
      nn = n - 1  !  TODO revise this to handle multiple nests.  This adjusts to match fv_control.F90 where these
      !  arrays are passed in to mpp_define_nest_domains with bounds (2:ngrids)

      ! Updated code from new fv_control.F90   November 8. 2021 Ramstrom

      if (nest_domain%tile_coarse(nn) == Atm(this_grid)%global_tile) then

        !isu = nest_ioffsets(n)
        isu = nest_domain%istart_coarse(nn)
        !ieu = isu + icount_coarse(n) - 1
        ieu = isu + (nest_domain%iend_coarse(nn) - nest_domain%istart_coarse(nn) + 1) - 1

        !jsu = nest_joffsets(n)
        jsu = nest_domain%jstart_coarse(nn)
        !jeu = jsu + jcount_coarse(n) - 1
        jeu = jsu + (nest_domain%jend_coarse(nn) - nest_domain%jstart_coarse(nn) + 1) - 1

!!! Begin new
        isu_stag = isu
        jsu_stag = jsu
        ieu_stag = ieu
        jeu_stag = jeu+1

        isv_stag = isu
        jsv_stag = jsu
        iev_stag = ieu+1
        jev_stag = jeu
!!! End new


        !update offset adjustment
        isu = isu + upoff
        ieu = ieu - upoff
        jsu = jsu + upoff
        jeu = jeu - upoff

!!! Begin new
        isu_stag = isu_stag + upoff
        ieu_stag = ieu_stag - upoff
        jsu_stag = jsu_stag + upoff
        jeu_stag = jeu_stag - upoff

        isv_stag = isv_stag + upoff
        iev_stag = iev_stag - upoff
        jsv_stag = jsv_stag + upoff
        jev_stag = jev_stag - upoff

        ! Absolute boundary for the staggered point update region on the parent.
        ! This is used in remap_uv to control the update of the last staggered point
        ! when the the update region coincides with a pe domain to avoid cross-restart repro issues

        Atm(n)%neststruct%jeu_stag_boundary = jeu_stag
        Atm(n)%neststruct%iev_stag_boundary = iev_stag

        if (isu > iec .or. ieu < isc .or. &
            jsu > jec .or. jeu < jsc ) then
          isu = -999 ; jsu = -999 ; ieu = -1000 ; jeu = -1000
        else
          isu = max(isu,isc) ; jsu = max(jsu,jsc)
          ieu = min(ieu,iec) ; jeu = min(jeu,jec)
        endif

        ! Update region for staggered quantity to avoid cross repro issues when the pe domain boundary
        ! coincide with the nest. Basically write the staggered update on compute domains

        if (isu_stag > iec .or. ieu_stag < isc .or. &
            jsu_stag > jec .or. jeu_stag < jsc ) then
          isu_stag = -999 ; jsu_stag = -999 ; ieu_stag = -1000 ; jeu_stag = -1000
        else
          isu_stag = max(isu_stag,isc) ; jsu_stag = max(jsu_stag,jsc)
          ieu_stag = min(ieu_stag,iec) ; jeu_stag = min(jeu_stag,jec)
        endif

        if (isv_stag > iec .or. iev_stag < isc .or. &
            jsv_stag > jec .or. jev_stag < jsc ) then
          isv_stag = -999 ; jsv_stag = -999 ; iev_stag = -1000 ; jev_stag = -1000
        else
          isv_stag = max(isv_stag,isc) ; jsv_stag = max(jsv_stag,jsc)
          iev_stag = min(iev_stag,iec) ; jev_stag = min(jev_stag,jec)
        endif
!!! End new

        if (isu > iec .or. ieu < isc .or. &
            jsu > jec .or. jeu < jsc ) then
          isu = -999 ; jsu = -999 ; ieu = -1000 ; jeu = -1000
        else
          isu = max(isu,isc) ; jsu = max(jsu,jsc)
          ieu = min(ieu,iec) ; jeu = min(jeu,jec)
        endif

        ! lump indices
        isu=max(isu, isu_stag, isv_stag)
        jsu=max(jsu, jsu_stag, jsv_stag)
        jeu_stag=max(jeu, jeu_stag)
        jev_stag=max(jeu, jev_stag)
        ieu_stag=max(ieu ,ieu_stag)
        iev_stag=max(ieu ,iev_stag)

        Atm(n)%neststruct%isu = isu
        Atm(n)%neststruct%ieu = ieu_stag
        Atm(n)%neststruct%jsu = jsu
        Atm(n)%neststruct%jeu = jev_stag

        Atm(n)%neststruct%jeu_stag = jeu_stag
        Atm(n)%neststruct%iev_stag = iev_stag
      endif
    enddo

  end subroutine mn_setup_update_regions


  !==================================================================================================
  !
  !  Recalculation Section -- Buffers that have to change size after nest motion
  !
  !==================================================================================================

  !>@brief The subroutine 'reallocate_BC_buffers' reallocates boundary condition buffers - some need to change size after a nest move.
  !>@details Thought they would be reallocated in boundary.F90 nested_grid_BC_recv() when needed, but seem not to.
  subroutine reallocate_BC_buffers(Atm)
    type(fv_atmos_type), intent(inout)  :: Atm     !< Single instance of atmospheric data

    integer :: n, ns
    logical :: dummy = .false. ! same as grids_on_this_pe(n)

    call deallocate_fv_nest_BC_type(Atm%neststruct%delp_BC)
    call deallocate_fv_nest_BC_type(Atm%neststruct%u_BC)
    call deallocate_fv_nest_BC_type(Atm%neststruct%v_BC)
    call deallocate_fv_nest_BC_type(Atm%neststruct%uc_BC)
    call deallocate_fv_nest_BC_type(Atm%neststruct%vc_BC)
    call deallocate_fv_nest_BC_type(Atm%neststruct%divg_BC)

    if (allocated(Atm%neststruct%q_BC)) then
      do n=1,size(Atm%neststruct%q_BC)
        call deallocate_fv_nest_BC_type(Atm%neststruct%q_BC(n))
      enddo
    endif

#ifndef SW_DYNAMICS
    call deallocate_fv_nest_BC_type(Atm%neststruct%pt_BC)
#ifdef USE_COND
    call deallocate_fv_nest_BC_type(Atm%neststruct%q_con_BC)
#ifdef MOIST_CAPPA
    call deallocate_fv_nest_BC_type(Atm%neststruct%cappa_BC)
#endif
#endif
    if (.not.Atm%flagstruct%hydrostatic) then
      call deallocate_fv_nest_BC_type(Atm%neststruct%w_BC)
      call deallocate_fv_nest_BC_type(Atm%neststruct%delz_BC)
    endif
#endif

    ! Reallocate the buffers

    ns = Atm%neststruct%nsponge

    call allocate_fv_nest_BC_type(Atm%neststruct%delp_BC,Atm,ns,0,0,dummy)
    call allocate_fv_nest_BC_type(Atm%neststruct%u_BC,Atm,ns,0,1,dummy)
    call allocate_fv_nest_BC_type(Atm%neststruct%v_BC,Atm,ns,1,0,dummy)
    call allocate_fv_nest_BC_type(Atm%neststruct%uc_BC,Atm,ns,1,0,dummy)
    call allocate_fv_nest_BC_type(Atm%neststruct%vc_BC,Atm,ns,0,1,dummy)
    call allocate_fv_nest_BC_type(Atm%neststruct%divg_BC,Atm,ns,1,1,dummy)

    !  if (ncnst > 0) then
    !     allocate(Atm%neststruct%q_BC(ncnst))
    !     do n=1,ncnst
    !        call allocate_fv_nest_BC_type(Atm%neststruct%q_BC(n),Atm,ns,0,0,dummy)
    !     enddo
    !  endif

    if (allocated(Atm%neststruct%q_BC)) then
      do n=1,size(Atm%neststruct%q_BC)
        call allocate_fv_nest_BC_type(Atm%neststruct%q_BC(n),Atm,ns,0,0,dummy)
      enddo
    endif

#ifndef SW_DYNAMICS
    call allocate_fv_nest_BC_type(Atm%neststruct%pt_BC,Atm,ns,0,0,dummy)
#ifdef USE_COND
    call allocate_fv_nest_BC_type(Atm%neststruct%q_con_BC,Atm,ns,0,0,dummy)
#ifdef MOIST_CAPPA
    call allocate_fv_nest_BC_type(Atm%neststruct%cappa_BC,Atm,ns,0,0,dummy)
#endif
#endif
    if (.not.Atm%flagstruct%hydrostatic) then
      call allocate_fv_nest_BC_type(Atm%neststruct%w_BC,Atm,ns,0,0,dummy)
      call allocate_fv_nest_BC_type(Atm%neststruct%delz_BC,Atm,ns,0,0,dummy)
    endif
#endif

  end subroutine reallocate_BC_buffers


  !!============================================================================
  !!  Step 8 -- Moving Nest Output to NetCDF
  !!============================================================================

  !>@brief The subroutine 'mn_prog_dump_to_netcdf' dumps selected prognostic variables to netCDF file.
  !>@details Can be modified to output more of the prognostic variables if wanted.  Certain 3D variables were commented out for performance.
  subroutine mn_prog_dump_to_netcdf(Atm, time_val, file_prefix, is_fine_pe, domain_coarse, domain_fine, nz)
    type(fv_atmos_type), intent(in)            :: Atm                                  !< Single instance of atmospheric data
    integer, intent(in)                        :: time_val                             !< Timestep number
    character(len=*), intent(in)               :: file_prefix                          !< Filename prefix
    logical, intent(in)                        :: is_fine_pe                           !< Is nest PE?
    type(domain2d), intent(in)                 :: domain_coarse, domain_fine           !< Domain structures
    integer, intent(in)                        :: nz                                   !< Number of vertical levels

    integer            :: n_moist
    character(len=16)  :: out_var_name
    integer            :: position = CENTER
    !integer            :: position_u = NORTH
    !integer            :: position_v = EAST

    call mn_var_dump_to_netcdf(Atm%pt   , is_fine_pe, domain_coarse, domain_fine, position, nz, &
        time_val, Atm%global_tile, file_prefix, "tempK")
    !call mn_var_dump_to_netcdf(Atm%pt(:,:,64)   , is_fine_pe, domain_coarse, domain_fine, position, nz, &
    !    time_val, Atm%global_tile, file_prefix, "T64")
    !call mn_var_dump_to_netcdf(Atm%delp , is_fine_pe, domain_coarse, domain_fine, position, nz, &
    !     time_val, Atm%global_tile, file_prefix, "DELP")
    call mn_var_dump_to_netcdf(Atm%delz , is_fine_pe, domain_coarse, domain_fine, position, nz, &
        time_val, Atm%global_tile, file_prefix, "DELZ")
    call mn_var_dump_to_netcdf(Atm%q_con, is_fine_pe, domain_coarse, domain_fine, position, nz, &
        time_val, Atm%global_tile, file_prefix, "qcon")

    !call mn_var_dump_to_netcdf(Atm%w    , is_fine_pe, domain_coarse, domain_fine, position, nz, &
    !     time_val, Atm%global_tile, file_prefix, "WWND")
    !call mn_var_dump_to_netcdf(Atm%ua   , is_fine_pe, domain_coarse, domain_fine, position, nz, &
    !     time_val, Atm%global_tile, file_prefix, "UA")
    !call mn_var_dump_to_netcdf(Atm%va   , is_fine_pe, domain_coarse, domain_fine, position, nz, &
    !     time_val, Atm%global_tile, file_prefix, "VA")

    call mn_var_dump_to_netcdf(Atm%ps   , is_fine_pe, domain_coarse, domain_fine, position, &
        time_val, Atm%global_tile, file_prefix, "PS")

    !! TODO figure out what to do with ze0;  different bounds - only compute domain

    !! TODO Wind worked fine when in its own file.  Can it merge in with the regular file??
    !!call mn_var_dump_to_netcdf(Atm%u, is_fine_pe, domain_coarse, domain_fine, position_u, nz, &
    !!     time_val, Atm%global_tile, "wxvarU", "UWND")
    !!call mn_var_dump_to_netcdf(Atm%v, is_fine_pe, domain_coarse, domain_fine, position_v, nz, &
    !!     time_val, Atm%global_tile, "wxvarU", "VWND")

    ! Latitude and longitude in radians
    call mn_var_dump_to_netcdf( Atm%gridstruct%agrid(:,:,2), is_fine_pe, domain_coarse, domain_fine, position, &
        time_val, Atm%global_tile, file_prefix, "latrad")
    call mn_var_dump_to_netcdf( Atm%gridstruct%agrid(:,:,1), is_fine_pe, domain_coarse, domain_fine, position, &
        time_val, Atm%global_tile, file_prefix, "lonrad")

    !do n_moist = lbound(Atm%q, 4), ubound(Atm%q, 4)
    !   call get_tracer_names(MODEL_ATMOS, n_moist, out_var_name)
    !   call mn_var_dump_to_netcdf( Atm%q(:,:,:,n_moist), is_fine_pe, domain_coarse, domain_fine, position, nz, &
    !        time_val, Atm%global_tile, file_prefix, trim(out_var_name))
    !enddo

  end subroutine mn_prog_dump_to_netcdf


  !!  Step 8 -- Moving Nest Output Individual Variables

  !>@brief The subroutine 'mn_var_dump_3d_to_netcdf' dumps a 3D single precision variable to netCDF file.
  subroutine mn_var_dump_3d_to_netcdf( data_var, is_fine_pe, domain_coarse, domain_fine, position, nz, time_step, this_tile, file_prefix, var_name)
    real, intent(in)                            :: data_var(:,:,:)                      !< Single precision model variable
    logical, intent(in)                         :: is_fine_pe                           !< Is nest PE?
    type(domain2d), intent(in)                  :: domain_coarse, domain_fine           !< Domain structures
    integer, intent(in)                         :: position, nz, time_step, this_tile   !< Stagger, number vertical levels, timestep, tile number
    character(len=*)                            :: file_prefix, var_name                !< Filename prefix, and netCDF variable name

    integer                      :: isd_coarse, ied_coarse, jsd_coarse, jed_coarse
    integer                      :: isd_fine, ied_fine, jsd_fine, jed_fine
    integer                      :: this_pe
    character(len=64)            :: prefix_fine, prefix_coarse

    this_pe = mpp_pe()

    prefix_fine = trim(file_prefix) // "_fine"
    prefix_coarse = trim(file_prefix) // "_coarse"

    !!===========================================================
    !!
    !! Output the grid data from both nest grids and parent grids to netCDF
    !!
    !!===========================================================

    if (is_fine_pe) then
      call mpp_get_data_domain(domain_fine, isd_fine, ied_fine, jsd_fine, jed_fine, position=position)

      call output_grid_to_nc("GH", isd_fine, ied_fine, jsd_fine, jed_fine, nz, data_var, prefix_fine, var_name, time_step, domain_fine, position)

    else
      if (this_tile == 6) then
        !call mpp_get_compute_domain(domain_coarse, isc_coarse, iec_coarse, jsc_coarse, jec_coarse, position=position)
        call mpp_get_data_domain(domain_coarse, isd_coarse, ied_coarse, jsd_coarse, jed_coarse, position=position)
        !call mpp_get_memory_domain(domain_coarse, ism_coarse, iem_coarse, jsm_coarse, jem_coarse, position=position)

        call output_grid_to_nc("GH", isd_coarse, ied_coarse, jsd_coarse, jed_coarse, nz, data_var, prefix_coarse, var_name, time_step, domain_coarse, position)

      endif
    endif

  end subroutine mn_var_dump_3d_to_netcdf

  !>@brief The subroutine 'mn_var_dump_2d_to_netcdf' dumps a 3D single precision variable to netCDF file.
  subroutine mn_var_dump_2d_to_netcdf( data_var, is_fine_pe, domain_coarse, domain_fine, position, time_step, this_tile, file_prefix, var_name)
    implicit none
    real, intent(in)                            :: data_var(:,:)                         !< Data variable
    logical, intent(in)                         :: is_fine_pe                            !< Is nest PE?
    type(domain2d), intent(in)                  :: domain_coarse, domain_fine            !< Domain structures
    integer, intent(in)                         :: position, time_step, this_tile    !< Stagger, number vertical levels, timestep, tile number
    character(len=*)                            :: file_prefix, var_name                 !< Filename prefix, and netCDF variable name

    !integer                      :: isc_coarse, iec_coarse, jsc_coarse, jec_coarse
    !integer                      :: isc_fine, iec_fine, jsc_fine, jec_fine
    !integer                      :: ism_coarse, iem_coarse, jsm_coarse, jem_coarse
    !integer                      :: ism_fine, iem_fine, jsm_fine, jem_fine

    integer                      :: isd_fine, ied_fine, jsd_fine, jed_fine
    integer                      :: isd_coarse, ied_coarse, jsd_coarse, jed_coarse
    integer                      :: this_pe
    character(len=64)            :: prefix_fine, prefix_coarse

    this_pe = mpp_pe()

    prefix_fine = trim(file_prefix) // "_fine"
    prefix_coarse = trim(file_prefix) // "_coarse"

    !!===========================================================
    !!
    !! Output the grid data from both nest grids and parent grids to netCDF
    !!
    !!===========================================================

    if (is_fine_pe) then
      ! Maybe don't need to call mpp_get_compute_domain here?
      !call mpp_get_compute_domain(domain_fine, isc_fine, iec_fine, jsc_fine, jec_fine, position=position)
      call mpp_get_data_domain(domain_fine, isd_fine, ied_fine, jsd_fine, jed_fine, position=position)
      !call mpp_get_memory_domain(domain_fine, ism_fine, iem_fine, jsm_fine, jem_fine, position=position)

      call output_grid_to_nc("GH", isd_fine, ied_fine, jsd_fine, jed_fine, data_var, prefix_fine, var_name, time_step, domain_fine, position)
    else

      if (this_tile == 6) then
        !call mpp_get_compute_domain(domain_coarse, isc_coarse, iec_coarse, jsc_coarse, jec_coarse, position=position)
        call mpp_get_data_domain(domain_coarse, isd_coarse, ied_coarse, jsd_coarse, jed_coarse, position=position)
        !call mpp_get_memory_domain(domain_coarse, ism_coarse, iem_coarse, jsm_coarse, jem_coarse, position=position)

        call output_grid_to_nc("GH", isd_coarse, ied_coarse, jsd_coarse, jed_coarse, data_var, prefix_coarse, var_name, time_step, domain_coarse, position)

      endif
    endif

  end subroutine mn_var_dump_2d_to_netcdf


  !!=========================================================================================
  !! Step 9 -- Perform vertical remapping on nest(s) and recalculate auxiliary pressures
  !!           Should help stabilize the fields before dynamics runs
  !!=========================================================================================

  !>@brief The subroutine 'recalc_aux_pressures' updates auxiliary pressures after a nest move.
  subroutine recalc_aux_pressures(Atm)
    type(fv_atmos_type), intent(inout) :: Atm      !< Single Atm structure

    !  Update the auxiliary pressure variables
    !  In nest moving code, we moved delp and delz; this will update ps, pk, pe, peln, and pkz
    !  Note this routine makes hydrostatic calculations (but has non-hydrostatic branches)
    !  Perhaps not appropriate for a non-hydrostatic run.
    !  May need to find or write a non-hydrostatic version of this routine

    ! TODO determine if this is the correct way to recalculate the auxiliary pressure variables

    call p_var(Atm%npz, Atm%bd%is, Atm%bd%ie, Atm%bd%js, Atm%bd%je, Atm%ptop, ptop_min,  &
        Atm%delp, Atm%delz, &
        Atm%pt, Atm%ps, &
        Atm%pe, Atm%peln,   &
        Atm%pk,   Atm%pkz, kappa, &
        Atm%q, Atm%ng, Atm%flagstruct%ncnst, Atm%gridstruct%area_64, 0.,  &
        .false.,  .false., & !mountain argument not used
        Atm%flagstruct%moist_phys,  Atm%flagstruct%hydrostatic, &
        Atm%flagstruct%nwat, Atm%domain, .false.)

  end subroutine recalc_aux_pressures


  !==================================================================================================
  !
  !  Utility Section  -- After Step 9
  !
  !==================================================================================================

  !>@brief The subroutine 'init_ijk_mem' was copied from dyn_core.F90 to avoid circular dependencies
  subroutine init_ijk_mem(i1, i2, j1, j2, km, array, var)
    integer, intent(in):: i1, i2, j1, j2, km
    real, intent(inout):: array(i1:i2,j1:j2,km)
    real, intent(in):: var
    integer:: i, j, k

    !$OMP parallel do default(none) shared(i1,i2,j1,j2,km,array,var)
    do k=1,km
      do j=j1,j2
        do i=i1,i2
          array(i,j,k) = var
        enddo
      enddo
    enddo

  end subroutine init_ijk_mem

  !>@brief The function 'almost_equal' tests whether real values are within a tolerance of one another.
  function almost_equal(a, b)
    logical :: almost_equal
    real, intent(in):: a,b

    real :: tolerance = 0.00001

    if ( abs(a - b) < tolerance ) then
      almost_equal = .true.
    else
      almost_equal = .false.
    endif
  end function almost_equal



  !>@brief The subroutine 'move_nest_geo' shifts tile_geo values using the data from fp_super_tile_geo
  subroutine move_nest_geo(Atm, n, tile_geo, tile_geo_u, tile_geo_v, fp_super_tile_geo, delta_i_c, delta_j_c, x_refine, y_refine)
    implicit none
    type(fv_atmos_type), allocatable, intent(in) :: Atm(:)                                        !< Atm data array
    integer, intent(in)                          :: n                                             !< Grid numbers
    type(grid_geometry), intent(inout)  :: tile_geo                                     !< A-grid tile geometry
    type(grid_geometry), intent(inout)  :: tile_geo_u                                   !< u-wind tile geometry
    type(grid_geometry), intent(inout)  :: tile_geo_v                                   !< v-wind tile geometry
    type(grid_geometry), intent(in)     :: fp_super_tile_geo                            !< Parent high-resolution supergrid tile geometry
    integer, intent(in)                 :: delta_i_c, delta_j_c, x_refine, y_refine     !< delta i,j for nest move.  Nest refinement.

    integer    :: nest_x, nest_y, parent_x, parent_y
    type(bbox) :: tile_bbox, fp_tile_bbox, tile_bbox_u, tile_bbox_v
    integer    :: i, j, fp_i, fp_j
    integer    :: this_pe
    logical    :: found
    character(len=48) :: errstring

    ! tile_geo is cell-centered, at nest refinement
    ! fp_super_tile_geo is a supergrid, at nest refinement

    this_pe = mpp_pe()

    call fill_bbox(tile_bbox, tile_geo%lats)
    call fill_bbox(tile_bbox_u, tile_geo_u%lats)
    call fill_bbox(tile_bbox_v, tile_geo_v%lats)
    call fill_bbox(fp_tile_bbox, fp_super_tile_geo%lats)

    !! Calculate new parent alignment -- supergrid at the refine ratio
    !!  delta_{i,j}_c are at the coarse center grid resolution
    !parent_x = parent_x + delta_i_c * 2 * x_refine
    !parent_y = parent_y + delta_j_c * 2 * y_refine

    call calc_nest_alignment(Atm, n, nest_x, nest_y, parent_x, parent_y)

    ! Brute force repopulation of full tile_geo grids.
    ! Optimization would be to use EOSHIFT and bring in just leading edge
    do i = tile_bbox%is, tile_bbox%ie
      do j = tile_bbox%js, tile_bbox%je
        fp_i = (i - nest_x) * 2 + parent_x
        fp_j = (j - nest_y) * 2 + parent_y

        if (fp_i < fp_tile_bbox%is .or. fp_i > fp_tile_bbox%ie) then
          write(errstring, "(A,I0,A,I0,A,I0)") "fp_i=", fp_i," is=",fp_tile_bbox%is," ie=",fp_tile_bbox%ie
          call mpp_error(FATAL, "move_nest_geo invalid bounds tile_geo i: " // errstring)
        endif
        if (fp_j < fp_tile_bbox%js .or. fp_j > fp_tile_bbox%je) then
          write(errstring, "(A,I0,A,I0,A,I0)") "fp_j=", fp_j," js=",fp_tile_bbox%js," je=",fp_tile_bbox%je
          call mpp_error(FATAL, "move_nest_geo invalid bounds tile_geo j " // errstring)
        endif

        tile_geo%lats(i,j) = fp_super_tile_geo%lats(fp_i, fp_j)
        tile_geo%lons(i,j) = fp_super_tile_geo%lons(fp_i, fp_j)
      enddo
    enddo

    do i = tile_bbox_u%is, tile_bbox_u%ie
      do j = tile_bbox_u%js, tile_bbox_u%je
        fp_i = (i - nest_x) * 2 + parent_x
        fp_j = (j - nest_y) * 2 + parent_y - 1

        if (fp_i < fp_tile_bbox%is .or. fp_i > fp_tile_bbox%ie) then
          write(errstring, "(A,I0,A,I0,A,I0)") "fp_i=", fp_i," is=",fp_tile_bbox%is," ie=",fp_tile_bbox%ie
          call mpp_error(FATAL, "move_nest_geo invalid bounds tile_geo_u i " // errstring)
        endif
        if (fp_j < fp_tile_bbox%js .or. fp_j > fp_tile_bbox%je) then
          write(errstring, "(A,I0,A,I0,A,I0)") "fp_j=", fp_j," js=",fp_tile_bbox%js," je=",fp_tile_bbox%je
          call mpp_error(FATAL, "move_nest_geo invalid bounds tile_geo_u j " // errstring)
        endif

        tile_geo_u%lats(i,j) = fp_super_tile_geo%lats(fp_i, fp_j)
        tile_geo_u%lons(i,j) = fp_super_tile_geo%lons(fp_i, fp_j)
      enddo
    enddo

    do i = tile_bbox_v%is, tile_bbox_v%ie
      do j = tile_bbox_v%js, tile_bbox_v%je
        fp_i = (i - nest_x) * 2 + parent_x - 1
        fp_j = (j - nest_y) * 2 + parent_y

        if (fp_i < fp_tile_bbox%is .or. fp_i > fp_tile_bbox%ie) then
          write(errstring, "(A,I0,A,I0,A,I0)") "fp_i=", fp_i," is=",fp_tile_bbox%is," ie=",fp_tile_bbox%ie
          call mpp_error(FATAL, "move_nest_geo invalid bounds tile_geo_v i " // errstring)
        endif
        if (fp_j < fp_tile_bbox%js .or. fp_j > fp_tile_bbox%je) then
          write(errstring, "(A,I0,A,I0,A,I0)") "fp_j=", fp_j," js=",fp_tile_bbox%js," je=",fp_tile_bbox%je
          call mpp_error(FATAL, "move_nest_geo invalid bounds tile_geo_v j " // errstring)
        endif

        tile_geo_v%lats(i,j) = fp_super_tile_geo%lats(fp_i, fp_j)
        tile_geo_v%lons(i,j) = fp_super_tile_geo%lons(fp_i, fp_j)
      enddo
    enddo

    ! Validate at the end
    call check_nest_alignment(tile_geo, fp_super_tile_geo, nest_x, nest_y, parent_x, parent_y, found)

  end subroutine move_nest_geo

  !>@brief The subroutine 'assign_n_p_grids' sets values for parent and nest grid arrays from the grid_geometry structures.
  subroutine assign_n_p_grids(parent_geo, tile_geo, p_grid, n_grid, position)
    type(grid_geometry), intent(in)          ::  parent_geo, tile_geo                        !< Parent geometry, nest geometry
    real(kind=R_GRID), allocatable, intent(inout)            :: p_grid(:,:,:)                !< Parent grid
    real(kind=R_GRID), allocatable, intent(inout)            :: n_grid(:,:,:)                !< Nest grid
    integer, intent(in)                      :: position                                     !< Grid offset

    integer :: i,j

    if (position == CENTER) then
      do j = lbound(tile_geo%lats,2), ubound(tile_geo%lats,2)
        do i = lbound(tile_geo%lats,1), ubound(tile_geo%lats,1)
          ! centered grid version
          n_grid(i, j, 1) = tile_geo%lons(i, j)
          n_grid(i, j, 2) = tile_geo%lats(i, j)
        enddo
      enddo

      do j = 1, parent_geo%ny
        do i = 1, parent_geo%nx
          ! centered grid version
          p_grid(i, j, 1) = parent_geo%lons(2*i, 2*j)
          p_grid(i, j, 2) = parent_geo%lats(2*i, 2*j)
        enddo
      enddo

      ! u(npx, npy+1)
    elseif (position == NORTH) then  ! u wind on D-stagger
      do j = lbound(tile_geo%lats,2), ubound(tile_geo%lats,2)
        do i = lbound(tile_geo%lats,1), ubound(tile_geo%lats,1)
          ! centered grid version
          n_grid(i, j, 1) = tile_geo%lons(i, j)
          n_grid(i, j, 2) = tile_geo%lats(i, j)
        enddo
      enddo

      do j = 1, parent_geo%ny
        do i = 1, parent_geo%nx
          ! centered grid version
          p_grid(i, j, 1) = parent_geo%lons(2*i, 2*j-1)
          p_grid(i, j, 2) = parent_geo%lats(2*i, 2*j-1)
        enddo
      enddo

      ! v(npx+1, npy)
    elseif (position == EAST) then  ! v wind on D-stagger
      do j = lbound(tile_geo%lats,2), ubound(tile_geo%lats,2)
        do i = lbound(tile_geo%lats,1), ubound(tile_geo%lats,1)
          ! centered grid version
          n_grid(i, j, 1) = tile_geo%lons(i, j)
          n_grid(i, j, 2) = tile_geo%lats(i, j)
        enddo
      enddo

      do j = 1, parent_geo%ny
        do i = 1, parent_geo%nx
          ! centered grid version
          p_grid(i, j, 1) = parent_geo%lons(2*i-1, 2*j)
          p_grid(i, j, 2) = parent_geo%lats(2*i-1, 2*j)
        enddo
      enddo

    endif

  end subroutine assign_n_p_grids

  !>@brief The subroutine 'assign_p_grids' sets values for parent grid arrays from the grid_geometry structures.  This is static through the model run.
  subroutine assign_p_grids(parent_geo, p_grid, position)
    type(grid_geometry), intent(in)               :: parent_geo       !< Parent geometry
    real(kind=R_GRID), allocatable, intent(inout) :: p_grid(:,:,:)     !< Parent grid
    integer, intent(in)                           :: position          !< Grid offset

    integer :: i,j

    integer :: num_zeros, num_vals, full_zeros

    num_zeros = 0
    full_zeros = 0
    num_vals = 0

    if (position == CENTER) then
      do j = 1, ubound(p_grid,2)
        do i = 1, ubound(p_grid,1)
          ! centered grid version
          p_grid(i, j, :) = 0.0
          
          if (2*i .gt. ubound(parent_geo%lons,1)) then
            print '("[ERROR] WDR PG_CLONi npe=",I0," 2*i=",I0," ubound=",I0)', mpp_pe(), 2*i, ubound(parent_geo%lons,1)
          elseif (2*j .gt. ubound(parent_geo%lons,2)) then
            print '("[ERROR] WDR PG_CLONj npe=",I0," 2*j=",I0," ubound=",I0)', mpp_pe(), 2*j, ubound(parent_geo%lons,2)
          else
            p_grid(i, j, 1) = parent_geo%lons(2*i, 2*j)
            p_grid(i, j, 2) = parent_geo%lats(2*i, 2*j)
          endif
        enddo
      enddo
      

    do i = 1, ubound(p_grid,1)
      do j = 1, ubound(p_grid,2)

        if (p_grid(i,j,1) .eq. 0.0) then
          num_zeros = num_zeros + 1
          if (p_grid(i,j,2) .eq. 0.0) then
            full_zeros = full_zeros + 1
            print '("[INFO] WDR set p_grid FULL_ZERO npe=",I0," i=",I0," j=",I0)', mpp_pe(), i, j
          endif
        endif
        if (p_grid(i,j,1) .ne. 0.0) num_vals = num_vals + 1
      enddo
    enddo
    
    if (num_zeros .gt. 0) print '("[INFO] WDR set p_grid npe=",I0," num_zeros=",I0," full_zeros=",I0," num_vals=",I0" nxp=",I0," nyp=",I0," parent_geo%lats(",I0,",",I0,")"," p_grid(",I0,",",I0,",2)")', mpp_pe(), num_zeros, full_zeros, num_vals, parent_geo%nxp, parent_geo%nyp, ubound(parent_geo%lats,1), ubound(parent_geo%lats,2), ubound(p_grid,1), ubound(p_grid,2)


      ! u(npx, npy+1)
    elseif (position == NORTH) then  ! u wind on D-stagger
      do j = 1, ubound(p_grid,2)
        do i = 1, ubound(p_grid,1)
          ! u grid version
          p_grid(i, j, :) = 0.0
          if (2*i .gt. ubound(parent_geo%lons,1)) then
            print '("[ERROR] WDR PG_ULONi npe=",I0," 2*i=",I0," ubound=",I0)', mpp_pe(), 2*i, ubound(parent_geo%lons,1)
          elseif (2*j-1 .gt. ubound(parent_geo%lons,2)) then
            print '("[ERROR] WDR PG_ULONj npe=",I0," 2*j-1=",I0," ubound=",I0)', mpp_pe(), 2*j-1, ubound(parent_geo%lons,2)
          else
            
            ! This seems correct
            p_grid(i, j, 1) = parent_geo%lons(2*i, 2*j-1)
          p_grid(i, j, 2) = parent_geo%lats(2*i, 2*j-1)
        endif
      enddo
    enddo
    
    
    do i = 1, ubound(p_grid,1)
      do j = 1, ubound(p_grid,2)

        if (p_grid(i,j,1) .eq. 0.0) then
          num_zeros = num_zeros + 1
          if (p_grid(i,j,2) .eq. 0.0) then
            full_zeros = full_zeros + 1
            print '("[INFO] WDR set p_grid_u FULL_ZERO npe=",I0," i=",I0," j=",I0)', mpp_pe(), i, j
          endif
        endif
        if (p_grid(i,j,1) .ne. 0.0) num_vals = num_vals + 1
      enddo
    enddo

    if (num_zeros .gt. 0) print '("[INFO] WDR set p_grid_u npe=",I0," num_zeros=",I0," full_zeros=",I0," num_vals=",I0" nxp=",I0," nyp=",I0," parent_geo%lats(",I0,",",I0,")"," p_grid(",I0,",",I0,",2)")', mpp_pe(), num_zeros, full_zeros, num_vals, parent_geo%nxp, parent_geo%nyp, ubound(parent_geo%lats,1), ubound(parent_geo%lats,2), ubound(p_grid,1), ubound(p_grid,2)


      ! v(npx+1, npy)
    elseif (position == EAST) then  ! v wind on D-stagger
      do j = 1, ubound(p_grid,2)
        do i = 1, ubound(p_grid,1)
          ! v grid version
          p_grid(i, j, :) = 0.0
          if (2*i-1 .gt. ubound(parent_geo%lons,1)) then
            print '("[ERROR] WDR PG_VLONi npe=",I0," 2*i-1=",I0," ubound=",I0)', mpp_pe(), 2*i-1, ubound(parent_geo%lons,1)
          elseif (2*j .gt. ubound(parent_geo%lons,2)) then
            print '("[ERROR] WDR PG_VLONj npe=",I0," 2*j=",I0," ubound=",I0)', mpp_pe(), 2*j, ubound(parent_geo%lons,2)
          else	
            ! This seems correct
            p_grid(i, j, 1) = parent_geo%lons(2*i-1, 2*j)
            p_grid(i, j, 2) = parent_geo%lats(2*i-1, 2*j)
          endif
        enddo
      enddo
      

      do i = 1, ubound(p_grid,1)
        do j = 1, ubound(p_grid,2)
          
          if (p_grid(i,j,1) .eq. 0.0) then
            num_zeros = num_zeros + 1
            if (p_grid(i,j,2) .eq. 0.0) then
              full_zeros = full_zeros + 1
              print '("[INFO] WDR set p_grid_v FULL_ZERO npe=",I0," i=",I0," j=",I0)', mpp_pe(), i, j
            endif
          endif
          if (p_grid(i,j,1) .ne. 0.0) num_vals = num_vals + 1
        enddo
      enddo
      
      if (num_zeros .gt. 0) print '("[INFO] WDR set p_grid_v npe=",I0," num_zeros=",I0," full_zeros=",I0," num_vals=",I0" nxp=",I0," nyp=",I0," parent_geo%lats(",I0,",",I0,")"," p_grid(",I0,",",I0,",2)")', mpp_pe(), num_zeros, full_zeros, num_vals, parent_geo%nxp, parent_geo%nyp, ubound(parent_geo%lats,1), ubound(parent_geo%lats,2), ubound(p_grid,1), ubound(p_grid,2)
      
      
      
    endif
    
  end subroutine assign_p_grids



  !>@brief The subroutine 'assign_n_grids' sets values for nest grid arrays from the grid_geometry structures.
  subroutine assign_n_grids(tile_geo, n_grid, position)
    type(grid_geometry), intent(in)                :: tile_geo           !< Parent geometry, nest geometry
    real(kind=R_GRID), allocatable, intent(inout)  :: n_grid(:,:,:)      !< Nest grid
    integer, intent(in)                            :: position           !< Grid offset

    integer :: i,j

    if (position == CENTER) then
      do j = lbound(tile_geo%lats,2), ubound(tile_geo%lats,2)
        do i = lbound(tile_geo%lats,1), ubound(tile_geo%lats,1)
          ! centered grid version
          n_grid(i, j, 1) = tile_geo%lons(i, j)
          n_grid(i, j, 2) = tile_geo%lats(i, j)
        enddo
      enddo

      ! u(npx, npy+1)
    elseif (position == NORTH) then  ! u wind on D-stagger
      do j = lbound(tile_geo%lats,2), ubound(tile_geo%lats,2)
        do i = lbound(tile_geo%lats,1), ubound(tile_geo%lats,1)
          ! centered grid version
          n_grid(i, j, 1) = tile_geo%lons(i, j)
          n_grid(i, j, 2) = tile_geo%lats(i, j)
        enddo
      enddo

      ! v(npx+1, npy)
    elseif (position == EAST) then  ! v wind on D-stagger
      do j = lbound(tile_geo%lats,2), ubound(tile_geo%lats,2)
        do i = lbound(tile_geo%lats,1), ubound(tile_geo%lats,1)
          ! centered grid version
          n_grid(i, j, 1) = tile_geo%lons(i, j)
          n_grid(i, j, 2) = tile_geo%lats(i, j)
        enddo
      enddo

    endif

  end subroutine assign_n_grids


  subroutine calc_inside(p_grid, ic, jc, n_grid1, n_grid2, istag, jstag, is_inside, verbose)
    real(kind=R_GRID), allocatable, intent(in)   :: p_grid(:,:,:)
    real(kind=R_GRID), intent(in)                :: n_grid1, n_grid2
    integer, intent(in)                          :: ic, jc, istag, jstag
    logical, intent(out)                         :: is_inside
    logical, intent(in)                         :: verbose

    
    real(kind=R_GRID) :: max1, max2, min1, min2, eps

    max1 = max(p_grid(ic,jc,1), p_grid(ic,jc+1,1), p_grid(ic,jc+1,1), p_grid(ic+1,jc+1,1),  p_grid(ic+1,jc+1,1), p_grid(ic+1,jc,1))
    max2 = max(p_grid(ic,jc,2), p_grid(ic,jc+1,2), p_grid(ic,jc+1,2), p_grid(ic+1,jc+1,2),  p_grid(ic+1,jc+1,2), p_grid(ic+1,jc,2))

    min1 = min(p_grid(ic,jc,1), p_grid(ic,jc+1,1), p_grid(ic,jc+1,1), p_grid(ic+1,jc+1,1),  p_grid(ic+1,jc+1,1), p_grid(ic+1,jc,1))
    min2 = min(p_grid(ic,jc,2), p_grid(ic,jc+1,2), p_grid(ic,jc+1,2), p_grid(ic+1,jc+1,2),  p_grid(ic+1,jc+1,2), p_grid(ic+1,jc,2))
    
    is_inside = .False.
    
    eps = 0.00001
    !eps = 0.000001

    if (n_grid1 .le. max1+eps .and. n_grid1 .ge. min1-eps) then
      if (n_grid2 .le. max2+eps .and. n_grid2 .ge. min2-eps) then
        is_inside = .True.
        !if (verbose) print '("[INFO] WDR is_inside TRUE npe=",I0," ic=",I0," jc=",I0," n_grid1=",F12.8," min1=",F12.8," max1=",F12.8," n_grid2=",F12.8," min2=",F12.8," max2=", F12.8," p_grid(",I0,",",I0,",2) istag=",I0," jstag=",I0)', mpp_pe(), ic, jc, n_grid1, min1, max1, n_grid2, min2, max2, ubound(p_grid,1), ubound(p_grid,2), istag, jstag
      endif
    endif

    if (verbose .and. .not. is_inside) then
      print '("[INFO] WDR is_inside FALSE npe=",I0," ic=",I0," jc=",I0," n_grid1=",F12.8," min1=",F12.8," max1=",F12.8," n_grid2=",F12.8," min2=",F12.8," max2=", F10.4," p_grid(",I0,",",I0,",2) istag=",I0," jstag=",I0)', mpp_pe(), ic, jc, n_grid1, min1, max1, n_grid2, min2, max2, ubound(p_grid,1), ubound(p_grid,2), istag, jstag
    endif


  end subroutine calc_inside

  !>@brief The subroutine 'calc_nest_halo_weights' calculates the interpolation weights
  !>@details Computationally demanding; target for optimization after nest moves
  subroutine calc_nest_halo_weights(bbox_fine, bbox_coarse, p_grid, n_grid, wt, istart_coarse, jstart_coarse, x_refine, y_refine, istag, jstag, ind)
    implicit none

    type(bbox), intent(in)                       :: bbox_coarse, bbox_fine                            !< Bounding boxes of parent and nest
    real(kind=R_GRID), allocatable, intent(in)   :: p_grid(:,:,:), n_grid(:,:,:)                      !< Latlon rids of parent and nest in radians
    real, allocatable, intent(inout)             :: wt(:,:,:)                                         !< Interpolation weight array
    integer, intent(in)                          :: istart_coarse, jstart_coarse, x_refine, y_refine  !< Offsets and nest refinements
    integer, intent(in)                          :: istag, jstag                                      !< Staggers
    integer, allocatable, intent(in)             :: ind(:,:,:)

    integer       :: i, j, k, ic, jc
    real          :: dist1, dist2, dist3, dist4, sum
    logical       :: verbose = .false.
    !logical       :: verbose = .true.
    logical       :: is_inside, adjusted
    integer       :: this_pe

    real(kind=R_GRID)  :: pi = 4 * atan(1.0d0)
    real               :: pi180
    real               :: rad2deg, deg2rad
    real               :: old_weight(4), diff_weight(4)
    integer            :: di, dj

    pi180 = pi / 180.0
    deg2rad = pi / 180.0
    rad2deg = 1.0 / pi180

    this_pe = mpp_pe()

    if ( bbox_coarse%is == 0 .and. bbox_coarse%ie == -1 ) then
      ! Skip this one
      ;
    else
      ! Calculate the bounding parent grid points for the nest grid point
      ! Rely on the nest being aligned
      ! code is from $CUBE/tools/fv_grid_tools.F90
      !

      do j = bbox_fine%js, bbox_fine%je
        do i = bbox_fine%is, bbox_fine%ie
          jc = ind(i,j,2)         ! reset this if the UPDATE code altered it
          ic = ind(i,j,1)
          if (ic+1 .gt. ubound(p_grid, 1)) print '("[ERROR] WDR CALCWT off end of p_grid i npe=",I0," ic+1=",I0," bound=",I0)', mpp_pe(), ic+1, ubound(p_grid,1)
          if (jc+1 .gt. ubound(p_grid, 2)) print '("[ERROR] WDR CALCWT off end of p_grid j npe=",I0," jc+1=",I0," bound=",I0)', mpp_pe(), jc+1, ubound(p_grid,2)
          
          ! dist2side_latlon takes points in longitude-latitude coordinates.
          dist1 = dist2side_latlon(p_grid(ic,jc,:),     p_grid(ic,jc+1,:),   n_grid(i,j,:))
          dist2 = dist2side_latlon(p_grid(ic,jc+1,:),   p_grid(ic+1,jc+1,:), n_grid(i,j,:))
          dist3 = dist2side_latlon(p_grid(ic+1,jc+1,:), p_grid(ic+1,jc,:),   n_grid(i,j,:))
          dist4 = dist2side_latlon(p_grid(ic,jc,:),     p_grid(ic+1,jc,:),   n_grid(i,j,:))

          call calc_inside(p_grid, ic, jc, n_grid(i,j,1), n_grid(i,j,2), istag, jstag, is_inside, .True.)

!          if (.not. is_inside) then
!            adjusted = .False.
!
!            do di = -2,2
!              do dj = -2,1
!                if (.not. adjusted) then
!                  call calc_inside(p_grid, ic+di, jc+dj, n_grid(i,j,1), n_grid(i,j,2), istag, jstag, is_inside, .False.)
!                  if (is_inside) then
!                    ic = ic + di
!                    jc = jc + dj
!                                        
!                    print '("[INFO] WDR is_inside UPDATED npe=",I0," ic=",I0," jc=",I0," istart_coarse=",I0," jstart_coarse=",I0," i=",I0," j=",I0," di=",I0," dj=",I0," n_grid1=",F12.8," n_grid2=",F12.8," istag=",I0," jstag=",I0)', mpp_pe(), ic, jc, istart_coarse, jstart_coarse, i, j,  di, dj, n_grid(i,j,1), n_grid(i,j,2), istag, jstag
                    
!                    dist1 = dist2side_latlon(p_grid(ic,jc,:),     p_grid(ic,jc+1,:),   n_grid(i,j,:))
!                    dist2 = dist2side_latlon(p_grid(ic,jc+1,:),   p_grid(ic+1,jc+1,:), n_grid(i,j,:))
!                    dist3 = dist2side_latlon(p_grid(ic+1,jc+1,:), p_grid(ic+1,jc,:),   n_grid(i,j,:))
!                    dist4 = dist2side_latlon(p_grid(ic,jc,:),     p_grid(ic+1,jc,:),   n_grid(i,j,:))
!                    
!                    adjusted = .True.
!                  endif
!                endif
!              enddo
!            enddo
!            if (.not. adjusted) print '("[ERROR] WDR is_inside UPDATE FAILED npe=",I0," i=",I0," j=",I0," ic=",I0," jc=",I0," n_grid1=",F12.8," n_grid2=",F12.8," istag=",I0," jstag=",I0)', mpp_pe(), i, j, ic, jc, n_grid(i,j,1), n_grid(i,j,2), istag, jstag
!
!          endif
          
          old_weight = wt(i,j,:)

          wt(i,j,1)=dist2*dist3      ! ic,   jc    weight
          wt(i,j,2)=dist3*dist4      ! ic,   jc+1  weight
          wt(i,j,3)=dist4*dist1      ! ic+1, jc+1  weight
          wt(i,j,4)=dist1*dist2      ! ic+1, jc    weight

          ! If sum is zero, this is a problem

          sum=wt(i,j,1)+wt(i,j,2)+wt(i,j,3)+wt(i,j,4)

          if (sum .eq. 0.0) then

            call mpp_error(WARNING, "WARNING: calc_nest_halo_weights sum of weights is zero.")
            wt(i,j,:) = 0.25
            
          else
            wt(i,j,:)=wt(i,j,:)/sum            
          endif

          diff_weight = old_weight - wt(i,j,:)
          do k=1,4
            if (abs(diff_weight(k)) .ge. 0.01) then
              print '("[WARN] WDR DIFFWT npe=",I0," old_wt=",F10.6," wt(",I0,",",I0,",",I0,")=",F10.6," diff=",F10.6," istag=",I0," jstag=",I0)', &
                  mpp_pe(), old_weight(k), i, j, k, wt(i,j,k), diff_weight(k), istag, jstag
              
            endif
          enddo
        enddo
      enddo
    endif

  end subroutine calc_nest_halo_weights

end module fv_moving_nest_mod