module cam_map_utils
  use pio,                 only: iMap=>PIO_OFFSET_KIND
  use cam_abortutils,      only: endrun
  use cam_logfile,         only: iulog
!!XXgoldyXX: v
use spmd_utils, only: npes, iam, mpicom, masterproc
use shr_sys_mod, only: shr_sys_flush
!!XXgoldyXX: ^

  implicit none
  private

  public iMap

!!XXgoldyXX: v
logical, public, save :: goldy_debug = .false.
!!XXgoldyXX: ^
  integer, private, save      :: unique_map_index = 0
  integer, private, parameter :: max_srcs         = 2
  integer, private, parameter :: max_dests        = 2

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_t: Information for a CAM map (between data array and
  !                 NetCDF file)
  !
  !    The map targets one or two dimensions of the NetCDF file.
  !    The 2-D map is useful for blocks of data (2 dimensions of array which
  !      map to one or two dimensions in the NetCDF file). For a 1-1 mapping,
  !      the first dimension is of size 3 (instead of 4).
  !      map(1, i) = index value of first src (e.g., lon, ncol)
  !      map(2, i) = index value of second src (e.g., lat, chunk)
  !      map(3, i) = global offset of first dest (e.g., lon, ncol)
  !      map(4, i) = global offset of second dest (e.g., lat, NA)
  !    src is the array dimension position corresponding to the map dimension
  !      in normal (not permuted) arrays.
  !      A negative pos denotes counting backwards from the last array dimension
  !    dest entries are the NetCDF dimension positions (must be increasing)
  !    It is an error to have src(1) == src(2).
  !
  !---------------------------------------------------------------------------
  type, public :: cam_filemap_t
    private
    integer                    :: index              =  0
    integer(iMap),   pointer   :: map(:,:)           => NULL()
    integer                    :: src(max_srcs)      =  0
    integer                    :: dest(max_dests)    =  0
    integer(iMap)              :: limits(max_srcs,2) =  -1
    integer                    :: nmapped            = -1
    integer,         pointer   :: blcksz(:)          => NULL() !e.g.,ncol(lchnk)
  contains
    procedure          :: init          => cam_filemap_init
    procedure          :: get_index     => cam_filemap_getIndex
    procedure          :: new_index     => cam_filemap_newIndex
    procedure          :: clear         => cam_filemap_clear
    procedure          :: copy          => cam_filemap_copy
    procedure          :: copy_elem     => cam_filemap_copyElem
    procedure          :: num_elem      => cam_filemap_size
    procedure          :: is_mapped     => cam_filemap_isMapped
    procedure          :: num_mapped    => cam_filemap_numMapped
    procedure          :: map_val       => cam_filemap_mapVal
    procedure          :: coord_vals    => cam_filemap_coordVals
    procedure          :: coord_dests   => cam_filemap_coordDests
    procedure          :: get_filemap   => cam_filemap_get_filemap
    procedure          :: has_blocksize => cam_filemap_has_blocksize
    procedure          :: blocksize     => cam_filemap_get_blocksize
    procedure          :: array_bounds  => cam_filemap_get_array_bounds
    procedure          :: active_cols   => cam_filemap_get_active_cols
    procedure          :: columnize     => cam_filemap_columnize
    procedure          :: compact       => cam_filemap_compact
!!XXgoldyXX: Cleanup when working
!    procedure          :: init_latlon   => cam_filemap_init_latlon
!    procedure          :: init_unstruct => cam_filemap_init_unstruct
!    generic, public    :: init          => init_latlon, init_unstruct
  end type cam_filemap_t

  !---------------------------------------------------------------------------
  !
  !  END: types BEGIN: private interfaces
  !
  !---------------------------------------------------------------------------

contains

!!#######################################################################
!!
!! index sorting routines:
!!  XXgoldyXX: Move to generic location?
!!
!!#######################################################################

  subroutine index_sort_vector(data, indices, compressval, dups_ok_in)
    use spmd_utils,      only: mpi_integer, mpi_integer8, iam, mpicom, npes
    use shr_mpi_mod,     only: shr_mpi_chkerr
    use cam_abortutils,  only: endrun
    use m_MergeSorts,    only: IndexSet, IndexSort

    ! Dummy arguments
    integer(iMap), pointer,  intent(in)    :: data(:)
    integer,       pointer,  intent(inout) :: indices(:)
    integer(iMap), optional, intent(in)    :: compressval
    logical,       optional, intent(in)    :: dups_ok_in

    ! Local variables
    integer                                :: num_elem   ! # mapped elements
    integer                                :: num_active ! # mapped pes
    integer                                :: ierr
    integer                                :: i
    integer                                :: lb, ub
    integer                                :: mycnt, my_first_elem
    integer                                :: mpi_group_world
    integer                                :: mpi_sort_group
    integer                                :: mpi_sort_comm
    integer                                :: my_sort_rank
    integer,       allocatable             :: sort_pes(:)
    integer,       allocatable             :: displs(:)
    integer,       allocatable             :: recvcounts(:)
    integer(iMap), allocatable             :: elements(:)
    integer(iMap), allocatable             :: local_elem(:)
    integer,       allocatable             :: ind(:)
    integer,       allocatable             :: temp(:)
    logical                                :: dups_ok ! .true. iff duplicates OK
    character(len=*),           parameter  :: subname = 'INDEX_SORT_VECTOR'

    ! Allow duplicate values?
    if (present(dups_ok_in)) then
      dups_ok = dups_ok_in
      if ((.not. dups_ok) .and. (.not. present(compressval))) then
        call endrun(trim(subname)//': dups_ok=.false. requires a compressval')
      end if
    else
      dups_ok = .true.
    end if

    ! The patch mapped values are in the number space of the master grid.
    ! They need to be compressed to go from 1 to the number of elements in the
    ! patch.
    ! Figure out the mapped elements in my portion of the patch mask
    if (.not. associated(data)) then
      mycnt = 0
      allocate(local_elem(mycnt))
      allocate(ind(mycnt))
      num_elem = 0
      lb = 0
      ub = -1
    else if (present(compressval)) then
      mycnt = COUNT(data /= compressval)
      allocate(local_elem(mycnt))
      allocate(ind(mycnt))
      num_elem = 0
      lb = LBOUND(data, 1)
      ub = UBOUND(data, 1)
      do i = lb, ub
        if (data(i) /= compressval) then
          num_elem = num_elem + 1
          local_elem(num_elem) = data(i)
          ind(num_elem) = i
        end if
      end do
    else
      lb = LBOUND(data, 1)
      ub = UBOUND(data, 1)
      mycnt = size(data)
      allocate(local_elem(mycnt))
      local_elem(1:mycnt) = data(lb:ub)
      num_elem = mycnt
    end if

    ! Find the tasks which have elements in this patch
    ! temp used for # elements per PE
    allocate(temp(0:npes-1))
    call MPI_allgather(mycnt, 1, MPI_integer, temp, 1, MPI_integer,           &
         mpicom, ierr)
    call shr_mpi_chkerr(ierr, subname//': MPI_allgather elements')
    num_active = COUNT(temp > 0)
    if (num_active > 1) then
      allocate(sort_pes(num_active))
      allocate(recvcounts(num_active))
      allocate(displs(num_active))
      num_elem = 0
      displs(1) = 0
      ! Find the number of mapped elements and number of pes in this patch
      my_sort_rank = -1
      do i = 0, npes - 1
        if (temp(i) > 0) then
          num_elem = num_elem + 1
          if (num_elem > num_active) then
            call endrun(subname//": overrun of sort_pes array")
          end if
          sort_pes(num_elem) = i
          if (iam == i) then
            my_sort_rank = num_elem - 1
            my_first_elem = displs(num_elem) + 1
          end if
          recvcounts(num_elem) = temp(i)
          if (num_elem < num_active) then
            displs(num_elem + 1) = displs(num_elem) + recvcounts(num_elem)
          end if
        end if
      end do
      if (num_elem < num_active) then
        call endrun(subname//": underrun of sort_pes array")
      end if
      if (my_sort_rank >= 0) then
        num_elem = SUM(temp) ! Total number of elements to sort
      else
        num_elem = 0
      end if
      deallocate(temp)  ! Cleanup
      ! Make a group with the active PEs
      call MPI_comm_group(mpicom, mpi_group_world, ierr)
      call shr_mpi_chkerr(ierr, subname//': MPI_comm_group mpi_group_world')
      call MPI_group_incl(mpi_group_world, num_active, sort_pes,              &
           mpi_sort_group, ierr)
      call shr_mpi_chkerr(ierr, subname//': MPI_group_incl sort_pes')
      ! Make a new communicator with the active PEs
      call MPI_comm_create(mpicom, mpi_sort_group, mpi_sort_comm, ierr)
      call shr_mpi_chkerr(ierr, subname//': MPI_comm_create mpi_sort_comm')
      ! Collect all the elements for sorting (only active tasks now)
      allocate(elements(num_elem))
      if (mycnt > 0) then
        call MPI_allgatherv(local_elem, mycnt, MPI_integer8,                  &
             elements, recvcounts, displs, MPI_integer8, mpi_sort_comm, ierr)
        call shr_mpi_chkerr(ierr, subname//': MPI_allgatherv')
        ! Clean up for active PEs only
        call MPI_comm_free(mpi_sort_comm, ierr)
      end if
      ! General clean up
      call MPI_group_free(mpi_sort_group, ierr)
      deallocate(recvcounts)
      deallocate(displs)
    else if (mycnt > 0) then
      ! We are the only PE with patch info
      num_elem = mycnt
      allocate(elements(size(local_elem)))
      elements = local_elem
      my_first_elem = 1
    end if
    ! At this point, num_elem should always be the local # of items to sort
    if (num_elem > 0) then
      ! Sanity check
      if (size(elements) < num_elem) then
        call endrun(trim(subname)//": size(elements) must be >= num_elem")
      end if
      !! Do the sort, recvcounts will be the temporary index array
      allocate(recvcounts(size(elements)))
      allocate(displs(size(recvcounts)))
      call IndexSet(num_elem, recvcounts)
      call IndexSort(num_elem, recvcounts, elements, descend=.false.)
      ! Compress recvcounts (repeat data values)
      displs = 0
      do i = 1, num_elem - 1
        if (elements(recvcounts(i)) == elements(recvcounts(i + 1))) then
          displs(i + 1) = displs(i) + 1
        else
          displs(i + 1) = displs(i)
        end if
      end do
      ! Unload recvcounts into indices. Assume indices is initialized w/ default
      do i = 1, num_elem
        if ( (recvcounts(i) >= my_first_elem) .and.                           &
             (recvcounts(i) < (my_first_elem + mycnt))) then
          if (.not. dups_ok) then
            ! Use our indirect access to set the correct indices location
            ! ind array already has lb offset included
            ! Eliminate duplicate values
            if ((i > 1) .and. (displs(i) > displs(MAX((i - 1),1)))) then
              indices(ind(recvcounts(i) - my_first_elem + 1)) = compressval
            else
              indices(ind(recvcounts(i) - my_first_elem + 1)) = i - displs(i)
            end if
          else if (allocated(ind)) then
            indices(ind(recvcounts(i) - my_first_elem + 1)) = i - displs(i)
          else
            ! recvcounts points directly at a local location
            ! NB: repeat data values all get same index
            indices(recvcounts(i) - my_first_elem + lb) = i - displs(i)
          end if
        end if
      end do
      deallocate(recvcounts)
      deallocate(displs)
    end if

    if (allocated(ind)) then
      deallocate(ind)
    end if
    if (allocated(elements)) then
      deallocate(elements)
    end if
    deallocate(local_elem)

  end subroutine index_sort_vector

!!#######################################################################
!!
!! CAM grid mapping functions
!!
!!#######################################################################

  integer function cam_filemap_getIndex(this)
    ! Dummy variable
    class(cam_filemap_t)                 :: this

    cam_filemap_getIndex = this%index

  end function cam_filemap_getIndex

  subroutine cam_filemap_newIndex(this)
    ! Dummy variable
    class(cam_filemap_t)                 :: this

    unique_map_index = unique_map_index + 1
    this%index = unique_map_index

  end subroutine cam_filemap_newIndex

  subroutine cam_filemap_init(this, pemap, unstruct, src, dest)
    ! Dummy arguments
    class(cam_filemap_t)                 :: this
    integer(iMap),    pointer            :: pemap(:,:) ! Map elem for this PE
    logical,                  intent(in) :: unstruct
    integer,                  intent(in) :: src(:)
    integer, optional,        intent(in) :: dest(:)

    ! Local variables
    integer                              :: i ! Loop index
    integer                              :: index

    ! This shouldn't happen but maybe we will decide to reuse these
    if (associated(this%map)) then
      deallocate(this%map)
      nullify(this%map)
    end if

    ! Check in case these ever change (because algorithm then must be modified)
    if ((max_srcs /= 2) .or. (max_dests /= 2)) then
      call endrun('cam_filemap_init: max_src or max_dest modified')
    end if

    ! Some items are simply copied
    if (associated(pemap)) then
      this%map => pemap
    else
      nullify(this%map)
    end if
    this%src =  src
    if (present(dest)) then
      ! Structred grids will likely always have dest = (1, 2) but maybe . . .
      this%dest = dest
    else if (unstruct) then
      ! Unstructured grids are (so far) always spread along the first dimension
      this%dest(1) = 1
      this%dest(2) = 0
    else
      this%dest(1) = 1
      this%dest(2) = 2
    end if
    ! We may have holes in the 'block' decomposition which is specified by
    ! having src(2) < 0. 
    ! NB: This is currently a special purpose hack in that it is purely
    !     convention that the last dimension specifies the block index and
    !     that those blocks may not be filled.
    !     The proper way to generalize this functionality is to allow
    !     src(1) to also be < 0 and to look for holes there as well
    if (associated(this%map)) then
      do i = 1, max_srcs
        if (ANY(this%map(i,:) > 0)) then
          ! Min of all src(i) values
          this%limits(i, 1) = MINVAL(this%map(i,:), mask=(this%map(i,:)>0))
          ! Max of all src(i) values
          this%limits(i, 2) = MAXVAL(this%map(i,:), mask=(this%map(i,:)>0))
        else
          this%limits(i,1) = 0
          this%limits(i,2) = -1
        end if
      end do

      this%nmapped = 0
      do index = 1, this%num_elem()
        if ((this%dest(1) > 0) .and. (this%map(max_srcs+1, index) > 0)) then
          if (this%dest(2) > 0) then
            ! Can't do this test unless we know the dim 2 is large enough
            if (this%map(max_srcs+2, index) > 0) then
              this%nmapped = this%nmapped + 1
            end if
          else
            this%nmapped = this%nmapped + 1
          end if
        end if
      end do
    else
      this%limits(:,1) = 0
      this%limits(:,2) = -1
      this%nmapped = 0
    end if
    if (src(max_srcs) < 0) then
      ! This shouldn't happen but maybe we will decide to reuse these
      if (associated(this%blcksz)) then
        deallocate(this%blcksz)
        nullify(this%blcksz)
      end if
      allocate(this%blcksz(this%limits(max_srcs,1):this%limits(max_srcs,2)))
      this%blcksz = 0
      do i = 1, this%num_elem()
        index = this%map(max_srcs, i)
        if (this%is_mapped(i)) then
          this%blcksz(index) = this%blcksz(index) + 1
        end if
      end do
    end if

    call this%new_index()

  end subroutine cam_filemap_init

  subroutine cam_filemap_clear(this)
    ! Dummy arguments
    class(cam_filemap_t)               :: this

    if (associated(this%map)) then
      this%map = 0
    end if
    this%limits(:,1) = 0
    this%limits(:,2) = -1
    this%nmapped = 0

    ! Update the index in case a decomp was made with the old values
    call this%new_index()

  end subroutine cam_filemap_clear

  subroutine cam_filemap_copy(this, map)
    ! Dummy arguments
    class(cam_filemap_t)               :: this
    type(cam_filemap_t),  intent(in)   :: map

    ! This shouldn't happen but maybe we will decide to reuse these
    if (associated(this%map)) then
      deallocate(this%map)
      nullify(this%map)
    end if

    if (associated(map%map)) then
      allocate(this%map(size(map%map, 1), size(map%map, 2)))
      if (map%num_elem() > 0) then
        this%map = map%map
      end if
    else
      nullify(this%map)
    end if
    this%src = map%src
    this%dest = map%dest
    this%limits = map%limits
    this%nmapped = map%nmapped

    ! This shouldn't happen but maybe we will decide to reuse these
    if (associated(this%blcksz)) then
      deallocate(this%blcksz)
      nullify(this%blcksz)
    end if
    if (associated(map%blcksz)) then
      allocate(this%blcksz(size(map%blcksz)))
      this%blcksz = map%blcksz
    end if

    ! Even a copy has to have a unique index
    call this%new_index()

  end subroutine cam_filemap_copy

  subroutine cam_filemap_copyElem(this, map, index)
    ! Dummy arguments
    class(cam_filemap_t)               :: this
    type(cam_filemap_t), intent(in)    :: map
    integer,             intent(in)    :: index

    if (this%is_mapped(index)) then
      this%nmapped = this%nmapped - 1
    end if
    this%map(:, index) = map%map(:, index)
    if (this%is_mapped(index)) then
      this%nmapped = this%nmapped + 1
    end if

  end subroutine cam_filemap_copyElem

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_size: Total number of elements in the map
  !
  !---------------------------------------------------------------------------
  integer function cam_filemap_size(this)
    ! Dummy variable
    class(cam_filemap_t)                 :: this

    if (associated(this%map)) then
      cam_filemap_size = size(this%map,2)
    else
      cam_filemap_size = 0
    end if
  end function cam_filemap_size

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_isMapped: Return .true. iff the index value is mapped
  !
  !---------------------------------------------------------------------------
  elemental logical function cam_filemap_isMapped(this, index)
    ! Dummy arguments
    class(cam_filemap_t),      intent(in)    :: this
    integer,                   intent(in)    :: index

    if (associated(this%map)) then
      cam_filemap_isMapped = (this%map(3, index) > 0)
      if ((size(this%map, 1) > 3) .and. cam_filemap_isMapped) then
        cam_filemap_isMapped = (this%map(4, index) > 0)
        ! No else needed
      end if
    else
      cam_filemap_isMapped = .false.
    end if

  end function cam_filemap_isMapped

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_numMapped: Number of elements in the map with non-zero entries
  !
  !---------------------------------------------------------------------------
  integer function cam_filemap_numMapped(this)
    ! Dummy variable
    class(cam_filemap_t)                 :: this

    if (associated(this%map)) then
      cam_filemap_numMapped = this%nmapped
    else
      cam_filemap_numMapped = 0
    end if
  end function cam_filemap_numMapped

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_mapVal: Calculate an offset value from a map
  !
  !---------------------------------------------------------------------------
  integer(iMap) function cam_filemap_mapVal(this, index, dsize, dest_in)
    ! Dummy arguments
    class(cam_filemap_t)                     :: this
    integer,                   intent(in)    :: index
    integer(iMap),             intent(in)    :: dsize(:)
    integer,       optional,   intent(in)    :: dest_in(:)

    ! Local variables
    integer                                  :: d(max_dests)

    if (associated(this%map)) then
      if (present(dest_in)) then
        d = dest_in
      else
        d = this%dest
      end if
      if (this%map(3, index) > 0) then
        cam_filemap_mapVal = ((this%map(3, index) - 1) * dsize(d(1))) + 1
        if (size(this%map, 1) > 3) then
          if (this%map(4, index) > 0) then
            cam_filemap_mapVal = ((this%map(4, index) - 1) * dsize(d(2))) +   &
                 cam_filemap_mapVal  ! No +1 because it is offset from map(3,:)
          else
            cam_filemap_mapVal = 0
          end if
        ! No else needed
        end if
      else
        cam_filemap_mapVal = 0
      end if
    else
      cam_filemap_mapVal = 0
    end if
  end function cam_filemap_mapVal

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_coordVals: Find coord indices matching map index
  !
  !---------------------------------------------------------------------------
  subroutine cam_filemap_coordVals(this, index, lonIndex, latIndex, isMapped)
    ! Dummy arguments
    class(cam_filemap_t)                     :: this
    integer,                   intent(in)    :: index
    integer,                   intent(out)   :: lonIndex
    integer,                   intent(out)   :: latIndex
    logical, optional,         intent(out)   :: isMapped

    if (associated(this%map)) then
      if (size(this%map,1) > (max_srcs + 1)) then
        lonIndex = this%map(1, index)
        latIndex = this%map(2, index)
      else
        lonIndex = index
        latIndex = index
      end if
      if (present(isMapped)) then
        isMapped = this%is_mapped(index)
      end if
    else if (present(isMapped)) then
      lonIndex = 0
      latIndex = 0
      isMapped = .false.
    else
      call endrun("cam_filemap_coordVals: must have map or pass isMapped")
    end if
  end subroutine  cam_filemap_coordVals

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_coordDests: Find coord indices matching map index
  !
  !---------------------------------------------------------------------------
  subroutine cam_filemap_coordDests(this, index, lonIndex, latIndex, isMapped)
    ! Dummy arguments
    class(cam_filemap_t)                     :: this
    integer,                   intent(in)    :: index
    integer,                   intent(out)   :: lonIndex
    integer,                   intent(out)   :: latIndex
    logical, optional,         intent(out)   :: isMapped

    if (associated(this%map)) then
      lonIndex = this%map(max_srcs + 1, index)
      if (size(this%map,1) > (max_srcs + 1)) then
        latIndex = this%map(max_srcs + 2, index)
      else
        latIndex = 0
      end if
      if (present(isMapped)) then
        isMapped = this%is_mapped(index)
      end if
    else if (present(isMapped)) then
      lonIndex = 0
      latIndex = 0
      isMapped = .false.
    else
      call endrun("cam_filemap_coordDests: must have map or pass isMapped")
    end if
  end subroutine  cam_filemap_coordDests

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_get_filemap: Create the mapping between an array and a file
  !             This is the workhorse function for creating a PIO decomp DOF
  !
  !---------------------------------------------------------------------------
  subroutine cam_filemap_get_filemap(this, fieldlens, filelens, filemap,      &
       src_in, dest_in, permutation_in)

    ! Dummy arguments
    class(cam_filemap_t)                      :: this
    integer,                    intent(in)    :: fieldlens(:)
    integer,                    intent(in)    :: filelens(:)
    integer(iMap), pointer                    :: filemap(:)
    integer,          optional, intent(in)    :: src_in(:)
    integer,          optional, intent(in)    :: dest_in(:)
    integer,          optional, intent(in)    :: permutation_in(:)

    ! Local variables
    integer                       :: srclens(7)          ! field dim lens
    integer                       :: srccnt              ! Rank of fieldlens
    integer(iMap), allocatable    :: dsize(:)
    integer                       :: mapind(max_srcs)   ! Source index for map
    integer,       allocatable    :: src_ind(:)     ! Map src to file dims
    integer                       :: fmind, j
    integer(iMap)                 :: mapSize, mapPos, pos, fileSize
    integer                       :: mapcnt         ! Dimension count
    integer                       :: locsize        ! Total local # elements
    integer                       :: tind, tlen     ! Temporarys
    integer                       :: i1, i2, i3, i4, i5, i6, i7
    integer                       :: i(7)

    ! This shouldn't happen but, who knows what evil lurks in the hearts of SEs
    if (associated(filemap)) then
      deallocate(filemap)
      nullify(filemap)
    end if

    !
    fileSize = product(filelens)
    srccnt = size(fieldlens)
    srclens(1:srccnt) = fieldlens(1:srccnt)
    if (srccnt < 7) then
      srclens(srccnt+1:7) = 1
    end if

    ! Allocate output filemap (dof)
    allocate(filemap(product(fieldlens)))
    filemap = 0

    ! Find map source dimensions in input array
    mapPos = 1                                ! To compare against map size
    mapind = 0
    if (present(src_in)) then
      mapcnt = size(src_in)  ! Just used until end of loop below
      if (mapcnt > max_srcs) then
        call endrun('cam_filemap_get_filemap: src_in too large')
      end if
    end if
    do j = 1, max_srcs
      if (present(src_in)) then
        if (mapcnt >= j) then
          if (src_in(j) < 0) then
            mapind(j) = srccnt + src_in(j) + 1
          else
            mapind(j) = src_in(j)
          end if
        end if
      else
        ! A src == 0 means that map dimension is not used
        if (this%src(j) /= 0) then
          ! src < 0 is count from last array dim
          if (this%src(j) < 0) then
            mapind(j) = srccnt + this%src(j) + 1
          else
            mapind(j) = this%src(j)
          end if
        end if
      end if
      if (mapind(j) > 0) then
        mapPos = mapPos * srclens(mapind(j))  ! To compare against map size
      end if
    end do
    mapcnt = COUNT(mapind /= 0)

    ! Check that map matches dims
    ! Since patch maps are compressed, we can't do an equal compare but
    !   it is still an error if the map has more elements than the array
    mapSize = this%num_elem()
    if (mapPos < this%num_mapped()) then
      call endrun('cam_filemap_get_filemap: Map size too large for array dims')
    end if

    ! dsize is a global offset for each dimension
    allocate(dsize(size(filelens)))
    dsize(1) = 1
    do j = 1, size(filelens) - 1
      dsize(j + 1) = dsize(j) * filelens(j)
    end do

    ! src_ind maps each source dimension to the corresponding file dimension
    allocate(src_ind(srccnt))
    if (present(permutation_in)) then
      if (size(permutation_in) /= size(src_ind)) then
        call endrun('cam_filemap_get_filemap: permutation_in must have same rank as fieldlens')
      end if
      src_ind = permutation_in
    else
      src_ind = 0
      fmind = 1      ! Here fmind is first available permutation slot
      do j = 1, srccnt
        ! We need to find the offset location for each non-mapped src dimension
        if (ANY(mapind == j)) then
          continue
        else
          ! Find the next available output dimension
          if (present(dest_in)) then
            do while (ANY(dest_in == fmind))
              fmind = fmind + 1
              if (fmind > size(dsize)) then
                call endrun('cam_filemap_get_filemap: permutation calculation dest_in error')
              end if
            end do
          else
            do while (ANY(this%dest == fmind))
              fmind = fmind + 1
              if (fmind > size(dsize)) then
                call endrun('cam_filemap_get_filemap: permutation calculation dest error')
              end if
            end do
          end if
          if (fmind > size(dsize)) then
            call endrun('cam_filemap_get_filemap: permutation calculation error')
          end if
          src_ind(j) = fmind
          fmind = fmind + 1
        end if
      end do
    end if
       
    ! Step through the map and fill in local positions for each entry
    fmind = 1
    do i7 = 1, srclens(7)
      i(7) = i7
      do i6 = 1, srclens(6)
        i(6) = i6
        do i5 = 1, srclens(5)
          i(5) = i5
          do i4 = 1, srclens(4)
            i(4) = i4
            do i3 = 1, srclens(3)
              i(3) = i3
              do i2 = 1, srclens(2)
                i(2) = i2
                do i1 = 1, srclens(1)
                  i(1) = i1
                  pos = 0  ! Offset from map pos so starts at zero
                  tind = 1 ! Offset into the map
                  tlen = 1
                  do j = 1, srccnt
                    if (ANY(mapind == j)) then
                      ! j is a distributed field dimension index
                      tind = tind + ((i(j) - 1) * tlen)
                      tlen = tlen * srclens(j)
                    else
                      ! j is a local field dimension index
                      pos = pos + ((i(j) - 1) * dsize(src_ind(j)))
                    end if
                  end do
                  if (tind > mapSize) then
                    call endrun('cam_filemap_get_filemap: internal error, tind')
                  end if
                  mapPos = this%map_val(tind, dsize, dest_in)
                  if ((mapPos > 0) .and. ((pos + mapPos) > fileSize)) then
                    call endrun('cam_filemap_get_filemap: internal error, pos')
                  end if
                  if ((pos + mapPos) < 0) then
                    call endrun('cam_filemap_get_filemap: internal error, mpos')
                  end if
                  if (mapPos > 0) then
                    filemap(fmind) = pos + mapPos
                  else
                    ! This element is not mapped
                    filemap(fmind) = 0
                  end if
                  fmind = fmind + 1
                end do
              end do
            end do
          end do
        end do
      end do
    end do
    if ((fmind - 1) /= size(filemap)) then
      call endrun('cam_filemap_get_filemap: internal error, fmind')
    end if
    deallocate(dsize)
  end subroutine cam_filemap_get_filemap

  integer function cam_filemap_get_blocksize(this, block_id)

    ! Dummy arguments
    class(cam_filemap_t)                       :: this
    integer,                    intent(in)     :: block_id

    if (.not. this%has_blocksize()) then
      call endrun('cam_filemap_get_blocksize: filemap has no blocks')
    else if ((block_id < LBOUND(this%blcksz, 1)) .or.                         &
         (block_id > UBOUND(this%blcksz, 1))) then
      call endrun('cam_filemap_get_blocksize: block_id out of range')
    else
      cam_filemap_get_blocksize = this%blcksz(block_id)
    end if
  end function cam_filemap_get_blocksize

  logical function cam_filemap_has_blocksize(this, lbnd, ubnd)

    ! Dummy arguments
    class(cam_filemap_t)                       :: this
    integer,             optional, intent(out) :: lbnd
    integer,             optional, intent(out) :: ubnd

    cam_filemap_has_blocksize = associated(this%blcksz)
    if (present(lbnd)) then
      lbnd = LBOUND(this%blcksz, 1)
    end if
    if (present(ubnd)) then
      ubnd = UBOUND(this%blcksz, 1)
    end if

  end function cam_filemap_has_blocksize

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_get_array_bounds: Sets grid bounds for the relevant array
  !            Only modifies the dimensions corresponding to the map's src
  !            dims should be sized (rank,2) with the second dimension used
  !            to store lower(1) and upper(2) bounds
  !
  !---------------------------------------------------------------------------
  subroutine cam_filemap_get_array_bounds(this, dims)

    ! Dummy arguments
    class(cam_filemap_t)                       :: this
    integer,                    intent(inout)  :: dims(:,:)

    ! Local variables
    integer                                    :: rank ! rank of target array
    integer                                    :: i    ! Loop variable

    rank = size(dims,1)
    if (size(dims,2) < 2) then
      call endrun('cam_filemap_get_array_bounds: second dim of dims must be 2')
    end if

    if (MAXVAL(this%limits(1:max_srcs, 1:2)) > HUGE(kind(dims))) then
      call endrun('cam_filemap_get_array_bounds: limits too large')
    end if
    do i = 1, max_srcs
      if (this%src(i) > 0) then
        if (this%src(i) > rank) then
          call endrun('cam_filemap_get_array_bounds: rank too small')
        else
!!XXgoldyXX: Maybe modify definition of this%limits?
!          dims(i, 1:2) = INT(this%limits(i, 1:2), kind=kind(dims))
          if (associated(this%map)) then
            if (size(this%map) > 0) then
              dims(i, 1) = MINVAL(this%map(i,:))
              dims(i, 2) = MAXVAL(this%map(i,:))
            else
              dims(i, 1) = 0
              dims(i, 2) = -1
            end if
          else
            dims(i, 1) = 0
            dims(i, 2) = -1
          end if
        end if
      else if (this%src(i) < 0) then
!!XXgoldyXX: Maybe modify definition of this%limits?
!        dims(rank + this%src(i) + 1, 1:2) = INT(this%limits(i, 1:2), kind=kind(dims))
        if (associated(this%map)) then
          if (size(this%map) > 0) then
            dims(rank + this%src(i) + 1, 1) = MINVAL(this%map(i,:))
            dims(rank + this%src(i) + 1, 2) = MAXVAL(this%map(i,:))
          else
            dims(rank + this%src(i) + 1, 1) = 0
            dims(rank + this%src(i) + 1, 2) = -1
          end if
        else
          dims(rank + this%src(i) + 1, 1) = 0
          dims(rank + this%src(i) + 1, 2) = -1
        end if
       ! No else (zero means unused position)
      end if
    end do
  end subroutine cam_filemap_get_array_bounds

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_get_active_cols: Find which columns are active in a dimension
  !        Because we normally decompose columns (blocks or chunks) in the
  !        last dimension, we default to that dimension
  !
  !---------------------------------------------------------------------------
  subroutine cam_filemap_get_active_cols(this, colnum, active, srcdim_in)

    ! Dummy arguments
    class(cam_filemap_t)                       :: this
    integer,                    intent(in)     :: colnum
    logical,                    intent(out)    :: active(:)
    integer, optional,          intent(in)     :: srcdim_in

    ! Local variables
    integer                                    :: srcdim

    if (present(srcdim_in)) then
      srcdim = srcdim_in
    else
      srcdim = max_srcs
    end if

    ! Sanity checks
    if ((srcdim < 1) .or. (srcdim > max_srcs)) then
      call endrun('cam_filemap_get_active_cols: srcdim out of range')
    else if (this%src(srcdim) >= 0) then
      call endrun('cam_filemap_get_active_cols: Invalid srcdim')
    else if (size(active) < size(this%map, (3 - srcdim))) then
      call endrun('cam_filemap_get_active_cols: active too small')
    else if (colnum < LBOUND(this%map, srcdim)) then
      call endrun('cam_filemap_get_active_cols: colnum too small')
    else if (colnum > UBOUND(this%map, srcdim)) then
      call endrun('cam_filemap_get_active_cols: colnum too large')
    ! else OK
    end if

    active = .false.
!!XXgoldyXX: This is probably completely wrong. What we want is column info
    select case(srcdim)
      case (1)
        active(1:size(this%map, 2)) = (this%map(colnum,:) > 0)
      case (2)
        active(1:size(this%map, 1)) = (this%map(:,colnum) > 0)
      case default
        call endrun('cam_filemap_get_active_cols: Invalid srcdim?!?')
      end select
  end subroutine cam_filemap_get_active_cols

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_columnize: Convert lon/lat map to ncol
  !
  !---------------------------------------------------------------------------
  subroutine cam_filemap_columnize(this)
    use spmd_utils,  only: mpi_sum, mpi_integer, mpicom
    use shr_mpi_mod, only: shr_mpi_chkerr

    ! Dummy argument
    class(cam_filemap_t)                      :: this

    ! Local variables
    integer                                   :: i, j
    integer                                   :: lmax(2)
    integer                                   :: maxind(2)
    integer                                   :: offset(2)
    integer                                   :: ierr
    integer(iMap), pointer                    :: newmap(:,:) => NULL()
    character(len=*),              parameter  :: subname = 'CAM_FILEMAP_COLUMNIZE'
    ! Create a new map with same size and ordering
    ! We need the max lon/lat for calculating global offsets
    lmax = 0
    if (associated(this%map)) then
      if (size(this%map, 1) == max_srcs) then
        call endrun(trim(subname)//': must have at least 1 destination coord')
      else if (size(this%map, 1) > (max_srcs + 2)) then
        call endrun(trim(subname)//': has more than 2 destination coords')
      end if
      if (size(this%map, 2) > 0) then
        lmax(1) = MAXVAL(this%map(max_srcs + 1, :))
        if (size(this%map, 1) == (max_srcs + 2)) then
          lmax(2) = MAXVAL(this%map(max_srcs + 2, :))
        end if
      end if
    end if
    call MPI_allreduce(lmax(1), maxind(1), 1, MPI_integer, mpi_sum, mpicom, ierr)
    call shr_mpi_chkerr(ierr, subname//': MPI_allreduce maxlon')
    call MPI_allreduce(lmax(2), maxind(2), 1, MPI_integer, mpi_sum, mpicom, ierr)
    call shr_mpi_chkerr(ierr, subname//': MPI_allreduce maxlat')
    if (associated(this%map)) then
      if (size(this%map, 1) == (max_srcs + 2))then
        ! Create the new map
        allocate(newmap(max_srcs + 1, size(this%map, 2)))
        ! Who's on first?
        if (ANY(this%dest(1:2) <= 0)) then
          call endrun(trim(subname)//': can only handle positive dest indices')
        else if (this%dest(1) < this%dest(2)) then
          offset(1) = 1
          offset(2) = maxind(1)
        else if (this%dest(1) > this%dest(2)) then
          offset(1) = maxind(2)
          offset(2) = 1
        else
          call endrun(trim(subname)//': dest indices cannot be equal')
        end if
        do i = 1, size(newmap, 2)
          newmap(1:max_srcs, i) = this%map(1:max_srcs, i)
          j = ((this%map(max_srcs+1, i) * offset(1)) +                        &
               (this%map(max_srcs+2, i) * offset(2)))
          newmap(max_srcs+1, i) = j
        end do
        ! Replace our map with the new one
        deallocate(this%map)
        this%map => newmap
        nullify(newmap)
        ! Fixup dest
        this%dest(1) = 1
        this%dest(2:) = 0
        ! no else (do nothing if already ncol)
      end if ! End if lon/lat map
    end if

  end subroutine cam_filemap_columnize

  !---------------------------------------------------------------------------
  !
  !  cam_filemap_compact: Pack all active elements into a 1-based file order
  !                       Also pack latitude and longitude maps
  !
  !---------------------------------------------------------------------------
  subroutine cam_filemap_compact(this, lonmap, latmap,                        &
       num_lons, num_lats, num_mapped, columnize, dups_ok_in)
    use spmd_utils,  only: mpi_sum, mpi_integer, mpicom
    use shr_mpi_mod, only: shr_mpi_chkerr

    ! Dummy arguments
    class(cam_filemap_t)                     :: this
    integer(iMap),  pointer                  :: lonmap(:)
    integer(iMap),  pointer                  :: latmap(:)
    integer,        optional, intent(out)    :: num_lons
    integer,        optional, intent(out)    :: num_lats
    integer,        optional, intent(out)    :: num_mapped
    logical,        optional, intent(in)     :: columnize  ! Convert to ncol
    logical,        optional, intent(in)     :: dups_ok_in ! Dup coords OK

    ! Local variables
    integer                                  :: i, j
    integer                                  :: ierr
    integer(iMap), pointer                   :: data(:) => NULL()
    integer,       pointer                   :: indices(:) => NULL()
    integer                                  :: tmp_size
    logical                                  :: dok
    character(len=*),              parameter :: subname = 'CAM_FILEMAP_COMPACT'

    !! Possibly convert lon/lat map to ncol
    if (present(columnize)) then
      if (columnize) then
        call this%columnize()
      end if
    end if
    !! Are duplicate coordinate indices (lat/lon) OK?
    if (present(dups_ok_in)) then
      dok = dups_ok_in
    else
      dok = .false.
    end if
    ! Get a global index sort of mapped elements.
    do i = 1, max_dests
      if (this%dest(i) > 0) then
        if (associated(this%map)) then
          if (size(this%map, 1) >= max_srcs + i) then
            data => this%map(max_srcs + i, :)
          else
            nullify(data)
          end if
        else
          nullify(data)
        end if
        ! Allocate indices if necessary
        if (associated(indices)) then
          deallocate(indices)
          nullify(indices)
        end if
        if (associated(data)) then
          allocate(indices(LBOUND(data, 1):UBOUND(data, 1)))
          indices = 0
        else
          allocate(indices(0))
        end if
      end if
      call index_sort_vector(data, indices, compressval=0_iMap)
      if (associated(data) .and. associated(indices)) then
        data = indices
      end if
    end do
    if (associated(indices)) then
      deallocate(indices)
      nullify(indices)
    end if
    ! Get a global index sort of lat and lon maps 
    !! Compress latmap
    if (associated(latmap)) then
      ! Allocate indices
      allocate(indices(LBOUND(latmap, 1):UBOUND(latmap, 1)))
      indices = 0
    end if
    call index_sort_vector(latmap, indices, compressval=0_iMap, dups_ok_in=dok)
    if (associated(latmap)) then
      latmap = indices
      deallocate(indices)
      nullify(indices)
    end if
    !! Compress lonmap
    if (associated(lonmap)) then
      allocate(indices(LBOUND(lonmap, 1):UBOUND(lonmap, 1)))
      indices = 0
    end if
    call index_sort_vector(lonmap, indices, compressval=0_iMap, dups_ok_in=dok)
    if (associated(lonmap)) then
      lonmap = indices
      deallocate(indices)
      nullify(indices)
    end if

    if (present(num_mapped)) then
      if (this%num_elem() > 0) then
        ! Total number of mapped elements
        allocate(indices(this%num_elem()))
        indices = [ (i, i = 1, size(indices)) ]
        tmp_size = COUNT(this%is_mapped(indices))
        deallocate(indices)
        nullify(indices)
      else
        tmp_size = 0
      end if
      call MPI_allreduce(tmp_size, num_mapped, 1, MPI_integer,              &
           mpi_sum, mpicom, ierr)
      call shr_mpi_chkerr(ierr, subname//': MPI_allreduce num_mapped')
      if (num_mapped <= 0) then
        call endrun(trim(subname)//': num_mapped <= 0')
      end if
    end if
    if (present(num_lons)) then
      if (associated(lonmap)) then
        tmp_size = COUNT(lonmap /= 0)
      else
        tmp_size = 0
      end if
      call MPI_allreduce(tmp_size, num_lons, 1, MPI_integer,                  &
           mpi_sum, mpicom, ierr)
      call shr_mpi_chkerr(ierr, subname//': MPI_allreduce num_lons')
      if (num_lons <= 0) then
        call endrun(trim(subname)//': numlons <= 0')
      end if
    end if
    if (present(num_lats)) then
      if (associated(latmap)) then
        tmp_size = COUNT(latmap /= 0)
      else
        tmp_size = 0
      end if
      call MPI_allreduce(tmp_size, num_lats, 1, MPI_integer,                  &
           mpi_sum, mpicom, ierr)
      call shr_mpi_chkerr(ierr, subname//': MPI_allreduce num_lats')
      if (num_lats <= 0) then
        call endrun(trim(subname)//': numlats <= 0')
      end if
    end if

  end subroutine cam_filemap_compact

end module cam_map_utils
