!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
module cubeadm_taskloop
  use cubeadm_messaging
  use gkernel_interfaces
  use cube_types
  use cubedag_link_type
  use cubetopology_cuberegion_types
  use cubeadm_entryloop
  use cubeadm_taskloop_iteration
  !
  type :: cubeadm_iterator_compute_t
    logical              :: input       ! Is this an input or output cube?
    ! Access mode
    integer(kind=code_k) :: order       ! Cube order
    integer(kind=code_k) :: access      ! Iterator access mode
    ! Data values
    integer(kind=data_k) :: nd          ! Total number of data values
    integer(kind=data_k) :: ndperentry  ! Number of data values per entry
    integer(kind=data_k) :: ndperplane  ! Number of data values per plane
    integer(kind=data_k) :: ndpertask   ! Number of data values per task
    integer(kind=data_k) :: ndperblock  ! Number of data values per block
    integer(kind=data_k) :: ndginentry  ! Number of data granularity in entry (1 entry is a multiple of this)
    integer(kind=data_k) :: ndginblock  ! Number of data granularity in block (1 block is a multiple of this)
    ! Entries
    integer(kind=entr_k) :: neperblock  ! Number of entries per IO block
    integer(kind=entr_k) :: nepertask   ! Number of entries per task
    integer(kind=entr_k) :: neperplane  ! Number of entries per plane (if <1, use npperentry)
    ! Planes
    integer(kind=entr_k) :: np          ! Total number of planes
    integer(kind=entr_k) :: nppertask   ! Number of planes per task
    integer(kind=entr_k) :: npperblock  ! Number of planes per block
    integer(kind=entr_k) :: npperentry  ! Number of planes per entry (if <1, use neperplane)
    ! Tasks
    real(kind=4)         :: ntperblock  ! Number of tasks per block
    ! Blocks
    real(kind=4)         :: nb          ! Number of blocks
  end type cubeadm_iterator_compute_t
  !
  type :: cubeadm_iterator_t
    ! Entries
    integer(kind=entr_k) :: ne          ! Total number of entries
    integer(kind=entr_k) :: neperplane  ! Number of output entries per plane
    ! Tasks
    integer(kind=entr_k) :: nt          ! Total number of tasks
    ! Planes
    integer(kind=entr_k) :: nppertask   ! Number of planes per task
    integer(kind=entr_k) :: npperiblock ! Number of planes per input block
    integer(kind=entr_k) :: npperoblock ! Number of planes per output block
    integer(kind=entr_k) :: inp         ! Number of input planes
    integer(kind=entr_k) :: startplane  ! First input plane to iterate
    integer(kind=entr_k) :: stopplane   ! Last  input plane to iterate
    ! Region
    type(cuberegion_prog_t), pointer :: region => null()  !
    ! Lists for iterations ZZZ these should be a single array of taskloop_iteration_t
    integer(kind=entr_k), allocatable :: ifps(:),ilps(:)
    integer(kind=entr_k), allocatable :: ofps(:),olps(:)
    integer(kind=entr_k), allocatable :: ofes(:),oles(:)
    ! Current task iteration
    ! NB: this is a duplicate of taskloop_iteration_t. Ifort 13 and 18 have
    !     a bug which does not correctly duplicates sub-types components in
    !     FIRSTPRIVATE statements. No better way than duplicating the type
    !     here
    integer(kind=entr_k) :: num      ! This task number
    integer(kind=entr_k) :: ie       ! The output entry number iterated in this task
  ! integer(kind=data_k) :: ofd,old  ! Output first/last data to be processed
    integer(kind=entr_k) :: ifp,ilp  ! Input  first/last plane to be processed
    integer(kind=entr_k) :: ofp,olp  ! Output first/last plane to be processed
    integer(kind=entr_k) :: ofe,ole  ! Output first/last entry to be processed
  contains
    procedure, public :: iterate_entry => cubeadm_iterator_iterate_entry
  end type cubeadm_iterator_t
  !
  interface cubeadm_taskloop_iterator_reallocate
    module procedure cubeadm_taskloop_iterator_reallocate4
    module procedure cubeadm_taskloop_iterator_reallocate8
  end interface cubeadm_taskloop_iterator_reallocate
  !
  public :: cubeadm_iterator_t
  public :: cubeadm_taskloop_init,cubeadm_taskloop_iterate
  private
  !
contains
  !
  subroutine cubeadm_taskloop_init(incubes,oucubes,region,iter,error)
    use cubedag_node_type
    !----------------------------------------------------------------------
    ! Set up the iterator suited for the input and the output cubes
    !----------------------------------------------------------------------
    type(cubedag_link_t),     intent(in)    :: incubes     ! In cube list
    type(cubedag_link_t),     intent(in)    :: oucubes     ! Out cube list
    type(cuberegion_prog_t),  pointer       :: region      !
    type(cubeadm_iterator_t), intent(out)   :: iter
    logical,                  intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TASKLOOP>INIT'
    type(cubeadm_iterator_compute_t), allocatable :: myiter(:)
    integer(kind=entr_k) :: icube,ncube
    type(cube_t), pointer :: cube
    integer(kind=4) :: ier
    class(cubedag_node_object_t), pointer :: dno
    character(len=message_length) :: mess
    !
    call cubeadm_message(admseve%trace,rname,'Welcome')
    !
    ! Compute one iterator per cube
    allocate(myiter(incubes%n+oucubes%n),stat=ier)
    if (failed_allocate(rname,'iterator',ier,error)) return
    ncube = 0
    !
    ! Input cubes
    do icube=1,incubes%n
      dno => cubedag_node_ptr(incubes%list(icube)%p,error)
      if (error) return
      cube => cubetuple_cube_ptr(dno,error)
      if (error) return
      ! Skip cubes for which we won't iterate data
      if (cube%tuple%current%desc%action.eq.code_read_head)  cycle
      ! Set up an iterator suited for this cube
      ncube = ncube+1
      write(mess,'(A,I0)') '--- Computing iterator for cube #',ncube
      call cubeadm_message(admseve%others,rname,mess)
      call cubeadm_taskloop_iterator_compute(cube,.true.,myiter(ncube),error)
      if (error) return
    enddo
    !
    ! Output cubes
    do icube=1,oucubes%n
      dno => cubedag_node_ptr(oucubes%list(icube)%p,error)
      if (error) return
      cube => cubetuple_cube_ptr(dno,error)
      if (error) return
      ! Set up an iterator suited for this cube
      ncube = ncube+1
      write(mess,'(A,I0)') '--- Computing iterator for cube #',ncube
      call cubeadm_message(admseve%others,rname,mess)
      call cubeadm_taskloop_iterator_compute(cube,.false.,myiter(ncube),error)
      if (error) return
    enddo
    !
    ! Cubes consistency: number of entries must be the same or 1
    ! me = maxval(myiter(1:ncube)%ne)
    ! do icube=1,ncube
    !   if (myiter(icube)%ne.ne.1 .and. myiter(icube)%ne.ne.me) then
    !     write(mess,'(A,I0,A,I0)')  &
    !       'Iterated cubes have different number of entries: ',myiter(icube)%ne,',',me
    !     call cubeadm_message(seve%e,rname,mess)
    !     error = .true.
    !     return
    !   endif
    ! enddo
    !
    ! Compute the global iterator by merging all the individual ones
    call cubeadm_message(admseve%others,rname,'--- Computing global iterator')
    call cubeadm_taskloop_merge(myiter,ncube,region,iter,error)
    if (error) return
    call cubeadm_message(admseve%others,rname,'--- Done')
  end subroutine cubeadm_taskloop_init
  !
  subroutine cubeadm_taskloop_iterator_compute(cub,input,iter,error)
    use cubetools_format
    !----------------------------------------------------------------------
    ! Set up the iterator suited for a single cube.
    !----------------------------------------------------------------------
    type(cube_t),                     intent(in)    :: cub     !
    logical,                          intent(in)    :: input   !
    type(cubeadm_iterator_compute_t), intent(out)   :: iter    !
    logical,                          intent(inout) :: error   !
    ! Local
    character(len=*), parameter :: rname='TASKLOOP>ITERATOR'
    logical :: parallel
    integer(kind=chan_k) :: nchanperblock
    integer(kind=pixe_k) :: nyperblock
    integer(kind=data_k) :: nplaneperblock
    real(kind=4) :: totalsize,blocksize
    character(len=message_length) :: mess
    !
    call cubeadm_message(admseve%trace,rname,'Welcome')
    !
    iter%input = input
    !
    ! Block size. 2 possibilities: MEMORY mode or DISK mode.
    ! What if AUTOMATIC mode? => falls into same division as DISK mode,
    ! which is acceptable
    totalsize = cub%tuple%current%size()  ! [Bytes]
    select case (cub%tuple%current%desc%buffered)
    case (code_buffer_memory)
      ! In MEMORY mode, block size is all the file
      blocksize = totalsize  ! [Bytes]
    case (code_buffer_disk)
      ! In DISK mode, block size is ruled by SET BUFFER BLOCK
      blocksize = cub%user%buff%block  ! [Bytes]
    case default
      call cubeadm_message(seve%e,rname,'Unexpected cube buffering')
      error = .true.
      return
    end select
    !
    ! Which access?
    iter%order = cub%order()
    select case (cub%access())
    case (code_access_subset,code_access_fullset)
      iter%access = cub%access()
    case (code_access_blobset)
      call cubeadm_message(seve%e,rname,'Blob access mode is not implemented')
      error = .true.
      return
    case default
      ! Other accesses based on actual data order
      select case (cub%order())
      case (code_access_imaset)
        iter%access = code_access_imaset
      case (code_access_speset)
        iter%access = code_access_speset
      case default
        call cubeadm_message(seve%e,rname,'Unsupported access mode')
        error = .true.
        return
      end select
    end select
    !
    ! Entries
    select case (iter%access)
    case (code_access_subset)
      call cub%tuple%current%plane_per_block(blocksize,'SET\BUFFER /BLOCK',nplaneperblock,error)
      if (error) return
      iter%ndginentry = cub%tuple%current%desc%n1*cub%tuple%current%desc%n2  ! ZZZ method ndata_per_plane
      iter%ndginblock = iter%ndginentry
      iter%ndperblock = nplaneperblock * iter%ndginblock
      iter%np         = cub%tuple%current%desc%n3
      iter%npperblock = nplaneperblock
      iter%ndperplane = cub%tuple%current%desc%n1 * cub%tuple%current%desc%n2
    case (code_access_fullset)
      iter%ndginentry = cub%tuple%current%desc%n1 *  &
                        cub%tuple%current%desc%n2 *  &
                        cub%tuple%current%desc%n3
      iter%ndginblock = iter%ndginentry
      iter%ndperblock = cub%tuple%current%desc%n3 * iter%ndginblock
      iter%np         = cub%tuple%current%desc%n3
      iter%npperblock = cub%tuple%current%desc%n3  ! The trick is here: all planes => whole cube
      iter%ndperplane = cub%tuple%current%desc%n1 * cub%tuple%current%desc%n2
    case (code_access_imaset)
      call cub%tuple%current%chan_per_block(blocksize,'SET\BUFFER /BLOCK',nchanperblock,error)
      if (error) return
      iter%ndginentry = cub%tuple%current%desc%nx*cub%tuple%current%desc%ny  ! ZZZ method ndata_per_image
      iter%ndginblock = iter%ndginentry
      iter%ndperblock = nchanperblock * iter%ndginblock
      iter%np         = cub%tuple%current%desc%nc
      iter%npperblock = nchanperblock
      iter%ndperplane = cub%tuple%current%desc%nx * cub%tuple%current%desc%ny
    case (code_access_speset)
      call cub%tuple%current%y_per_block(blocksize,'SET\BUFFER /BLOCK',nyperblock,error)
      if (error) return
      iter%ndginentry = cub%tuple%current%desc%nc
      iter%ndginblock = iter%ndginentry * cub%tuple%current%desc%nx
      iter%ndperblock = nyperblock * iter%ndginblock
      iter%np         = cub%tuple%current%desc%ny
      iter%npperblock = nyperblock
      iter%ndperplane = cub%tuple%current%desc%nc * cub%tuple%current%desc%nx
    end select
    blocksize = iter%ndperblock * cub%nbytes()  ! Re-rounding for correct granularity
    iter%nb = totalsize/blocksize  ! Floating point (feedback only)
    !
    ! Number of tasks per IO block
    parallel = .false.
    !$ parallel = .true.
    if (parallel) then
      ! Parallel mode: entries are divided in tasks of size SET BUFFER PARA
      iter%ntperblock = blocksize/cub%user%buff%task  ! Floating point, rounding comes after
      if (iter%ntperblock.lt.1.0) then
        ! This can happen if:
        !  1) buff%task > buff%block (wrong user settings?)
        !  2) buff%task > totalsize
        iter%ntperblock = 1.0  ! This means no parallel threads!
      endif
    else
      ! Serial mode: one task per block
      iter%ntperblock = 1.0
    endif
    !
    ! Tasking
    iter%nd = cub%ndata()
    select case (iter%access)
    case (code_access_subset)
      ! Entries are sized to maximum, tasks deal with 1 entry each
      iter%nepertask = 1
      ! Block is divided in ntperblock, each entry must be a multiple of ndginentry
      iter%nppertask  = ceiling(iter%npperblock/iter%ntperblock)
      iter%ndperentry = iter%nppertask*iter%ndginentry
      iter%neperblock = (iter%ndperblock-1)/iter%ndperentry+1  ! Should be equal to ntperblock or so
    case (code_access_fullset)
      ! One unique entry, one unique task
      iter%nepertask = 1
      iter%nppertask  = iter%npperblock
      iter%ndperentry = iter%ndginentry
      iter%neperblock = 1
    case (code_access_imaset,code_access_speset)
      ! Entries are sized to minimum, tasks deal with several entries each
      iter%nppertask  = ceiling(iter%npperblock/iter%ntperblock)
      iter%ndperentry = iter%ndginentry  ! No extra factor = minimum allowed size
      iter%neperblock = iter%ndperblock/iter%ndperentry  ! Should be exact integer division
      iter%nepertask = ceiling(iter%neperblock/iter%ntperblock)  ! ceiling() because task size is a minimum
    end select
    iter%ndpertask = iter%ndperentry * iter%nepertask
    iter%neperplane = iter%ndperplane/iter%ndperentry
    iter%npperentry = iter%ndperentry/iter%ndperplane
    !
    write(mess,'(I0,A,I0,A,A)')  &
        iter%nppertask,' planes per task, ',  &
        iter%npperblock,' planes per block, block size ',  &
        cubetools_format_memsize(blocksize)
    call cubeadm_message(admseve%others,rname,mess)
  end subroutine cubeadm_taskloop_iterator_compute
  !
  subroutine cubeadm_taskloop_merge(iter,niter,region,merged,error)
    !-------------------------------------------------------------------
    ! Merge all the iterators to produce the final iterator
    !-------------------------------------------------------------------
    integer(kind=entr_k),             intent(in)    :: niter
    type(cubeadm_iterator_compute_t), intent(in)    :: iter(niter)
    type(cuberegion_prog_t),          pointer       :: region
    type(cubeadm_iterator_t),         intent(out)   :: merged
    logical,                          intent(inout) :: error
    !
    integer(kind=4) :: iiter,ref_input,ref_output
    integer(kind=code_k) :: order
    character(len=message_length) :: mess
    character(len=*), parameter :: rname='TASKLOOP>MERGE'
    !
    ! This subroutine is the core of the problem. It is intended to
    ! scale properly the entries and tasks, knowing all the input and
    ! output cubes. The main goal is to compute the number of planes
    ! per task, which must fit in the user limit (max task size).
    !
    ! However there are some tricky details like mixed accesses. For
    ! example HISTO3D reads three 2D images (imaset) and creates one 2D
    ! image (imaset) and one 3D cube (fullset).
    !
    ! Because of this, fullset-accessed cubes are excluded in the analysis
    ! below as they are not involved in the task sizing (100% of data
    ! treated by a single task).
    !
    ! Look for smallest nppertask, so that the task size is exceeded
    ! for none of the cubes.
    merged%nppertask = huge(0)
    do iiter=1,niter
      if (iter(iiter)%nppertask.lt.merged%nppertask)  &
        merged%nppertask = iter(iiter)%nppertask
    enddo
    !
    ! Look for an input and an output cube from the list
    ref_input = cubeadm_taskloop_get_inout(iter,niter,.true.)
    ref_output = cubeadm_taskloop_get_inout(iter,niter,.false.)
    if (ref_input.eq.0) then
      ! No input cube, i.e. the command creates a cube from scratch.
      ! Iteration is ruled by output cube only,
      ref_input = ref_output
    endif
    if (ref_output.eq.0) then
      ! No output cube, i.e. the command creates no cube. Iteration
      ! is ruled by input cube only
      ref_output = ref_input
    endif
    !
    if (ref_input.eq.0) then
      ! No input nor output cube => should not happen
      ! Put safe values
      merged%inp         = 1
      merged%npperiblock = 1
      merged%startplane  = 1
      merged%stopplane   = 1
      merged%neperplane  = 1
      merged%npperoblock = 1
    else
      ! Apply input and output cubes parameter to merged
      merged%inp         = iter(ref_input)%np
      merged%npperiblock = iter(ref_input)%npperblock
      !
      ! Apply subset to be iterated if relevant
      order = iter(ref_input)%order
      merged%region => region
      if (associated(merged%region)) then
        if (order.eq.code_cube_imaset) then
          merged%startplane = merged%region%iz%first
        elseif (order.eq.code_cube_speset) then
          merged%startplane = merged%region%iy%first
          ! ZZZ Ugly patch  fix me!
          ! merged%neperplane = (merged%region%ix%last-merged%region%ix%first+1)
        else  ! ZZZ Can this happen?
          merged%startplane = 1
        endif
      else
        merged%startplane = 1
      endif
      if (associated(merged%region)) then
        if (order.eq.code_cube_imaset) then
          merged%stopplane = merged%region%iz%last
        elseif (order.eq.code_cube_speset) then
          merged%stopplane = merged%region%iy%last
        else  ! ZZZ Can this happen?
          merged%stopplane = iter(ref_input)%np
        endif
      else
        merged%stopplane = iter(ref_input)%np
      endif
      !
      merged%neperplane  = iter(ref_output)%neperplane
      merged%npperoblock = iter(ref_output)%npperblock
    endif
    !
    write(mess,'(3(A,I0))')   &
      'Start plane ',merged%startplane,  &
      ', stop plane ',merged%stopplane,  &
      ', total planes ',merged%stopplane-merged%startplane+1
    call cubeadm_message(admseve%others,rname,mess)
    !
    ! Total number of tasks and entries. Can not guess by simple
    ! divisions. Apply iterator for exact value.
    call cubeadm_taskloop_reset(merged,error)
    if (error) return
    do while (cubeadm_taskloop_compute(merged,error))
      ! Reallocation in the loop because final size is unknown (that's the purpose here)
      call cubeadm_taskloop_iterator_reallocate(merged,merged%num,error)
      if (error) return
      merged%ifps(merged%num) = merged%ifp
      merged%ilps(merged%num) = merged%ilp
      merged%ofps(merged%num) = merged%ofp
      merged%olps(merged%num) = merged%olp
      merged%ofes(merged%num) = merged%ofe
      merged%oles(merged%num) = merged%ole
    enddo
    merged%nt = merged%num
    merged%ne = merged%ole
    !
    write(mess,'(7(A,I0))')   &
      'Split job using ',merged%nt,' tasks for ',merged%ne,' entries'
!        ceiling(iter%nb),' blocks, up to ',  &
!        iter%neperblock,' entries per block, up to ',  &
!        iter%nepertask,' entries per task, up to ',  &
!        ceiling(iter%ntperblock),' tasks per block)'
    call cubeadm_message(admseve%others,rname,mess)
    !
    ! Reset iteration before real use
    call cubeadm_taskloop_reset(merged,error)
    if (error) return
  end subroutine cubeadm_taskloop_merge
  !
  function cubeadm_taskloop_get_inout(iter,niter,what) result(ref_cube)
    !-------------------------------------------------------------------
    ! From the list of all cubes, find which one is of the 'what' type
    ! (input = .true., output = .false.) and if possible non
    ! fullset-access.
    !-------------------------------------------------------------------
    integer(kind=4) :: ref_cube  ! Function value on return
    integer(kind=entr_k),             intent(in) :: niter
    type(cubeadm_iterator_compute_t), intent(in) :: iter(niter)
    logical,                          intent(in) :: what
    ! Local
    integer(kind=4) :: iiter
    !
    ref_cube = 0  ! If no cube found
    !
    do iiter=1,niter
      ! Skip cubes cubes with wrong status:
      if (iter(iiter)%input.neqv.what)  cycle
      ! Avoid fullset-cube if mixed with other entry access:
      if (iter(iiter)%access.eq.code_access_fullset)  cycle
      ref_cube = iiter
      exit
    enddo
    !
    if (ref_cube.eq.0) then
      ! Same but allow fullset-cubes
      do iiter=1,niter
        ! Skip output cubes:
        if (iter(iiter)%input.neqv.what)  cycle
        ref_cube = iiter
        exit
      enddo
    endif
  end function cubeadm_taskloop_get_inout
  !
  subroutine cubeadm_taskloop_iterator_reallocate4(iter,rsize,error)
    use gkernel_interfaces
    !-------------------------------------------------------------------
    !
    !-------------------------------------------------------------------
    type(cubeadm_iterator_t), intent(inout) :: iter
    integer(kind=4),          intent(in)    :: rsize  ! Requested size
    logical,                  intent(inout) :: error
    call cubeadm_taskloop_iterator_reallocate8(iter,int(rsize,kind=8),error)
    if (error) return
  end subroutine cubeadm_taskloop_iterator_reallocate4
  !
  subroutine cubeadm_taskloop_iterator_reallocate8(iter,rsize,error)
    use gkernel_interfaces
    !-------------------------------------------------------------------
    !
    !-------------------------------------------------------------------
    type(cubeadm_iterator_t), intent(inout) :: iter
    integer(kind=8),          intent(in)    :: rsize  ! Requested size
    logical,                  intent(inout) :: error
    !
    character(len=*), parameter :: rname='TASKLOOP>ITERATOR>REALLOCATE'
    integer(kind=4) :: osize,nsize,ier
    logical :: olddata
    integer(kind=4), parameter :: iter_min_alloc=10
    integer(kind=entr_k), allocatable :: ifps(:),ilps(:)
    integer(kind=entr_k), allocatable :: ofps(:),olps(:)
    integer(kind=entr_k), allocatable :: ofes(:),oles(:)
    !
    olddata = allocated(iter%ifps)
    if (olddata) then
      osize = size(iter%ifps)
    else
      osize = 0
    endif
    if (osize.ge.rsize)  return
    !
    nsize = max(rsize,iter_min_alloc)
    nsize = max(nsize,2*osize)
    !
    if (olddata) then
      call move_alloc(from=iter%ifps,to=ifps)
      call move_alloc(from=iter%ilps,to=ilps)
      call move_alloc(from=iter%ofps,to=ofps)
      call move_alloc(from=iter%olps,to=olps)
      call move_alloc(from=iter%ofes,to=ofes)
      call move_alloc(from=iter%oles,to=oles)
      ! => Arrays are now deallocated
    endif
    !
    allocate(iter%ifps(nsize),  &
             iter%ilps(nsize),  &
             iter%ofps(nsize),  &
             iter%olps(nsize),  &
             iter%ofes(nsize),  &
             iter%oles(nsize),  &
             stat=ier)
    if (failed_allocate(rname,'iterator lists',ier,error))  return
    !
    if (olddata) then
      iter%ifps(1:osize) = ifps(1:osize)
      iter%ilps(1:osize) = ilps(1:osize)
      iter%ofps(1:osize) = ofps(1:osize)
      iter%olps(1:osize) = olps(1:osize)
      iter%ofes(1:osize) = ofes(1:osize)
      iter%oles(1:osize) = oles(1:osize)
      deallocate(ifps,ilps,ofps,olps,ofes,oles)
    endif
  end subroutine cubeadm_taskloop_iterator_reallocate8
  !
  subroutine cubeadm_taskloop_reset(iter,error)
    !-------------------------------------------------------------------
    ! Reset iterator before iterating with cubeadm_taskloop_iterate
    !-------------------------------------------------------------------
    type(cubeadm_iterator_t), intent(inout) :: iter
    logical,                  intent(inout) :: error
    !
    iter%num = 0
    iter%ifp = 0
    iter%ilp = iter%startplane-1  ! So that 1st iteration starts at correct position
    iter%ofp = 0
    iter%olp = 0
    iter%ofe = 0
    iter%ole = 0
  end subroutine cubeadm_taskloop_reset
  !
  function cubeadm_taskloop_compute(iter,error)
    !----------------------------------------------------------------------
    ! Iterate the iterator for next range of entries to be processed.
    ! Return .false. if no more data is to be processed.
    !----------------------------------------------------------------------
    logical :: cubeadm_taskloop_compute
    type(cubeadm_iterator_t), intent(inout) :: iter
    logical,                  intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TASKLOOP>COMPUTE'
    !
    if (.true.) then ! ZZZ select case (iter%access)
      ! Plane-iterating mode
      cubeadm_taskloop_compute = cubeadm_taskloop_compute_planes(iter,error)
      if (error) return
    else
      ! Data-iterating mode
      cubeadm_taskloop_compute = cubeadm_taskloop_compute_data(iter,error)
      if (error) return
    endif
  end function cubeadm_taskloop_compute
  !
  function cubeadm_taskloop_compute_planes(iter,error)
    !----------------------------------------------------------------------
    ! Iterate the iterator for next range of PLANES to be processed.
    ! This mode is mostly suited to iterate cubes of same 3rd dimension (2
    ! first dimensions might differ).
    !
    ! Return .false. if no more data is to be processed.
    !----------------------------------------------------------------------
    logical :: cubeadm_taskloop_compute_planes
    type(cubeadm_iterator_t), intent(inout) :: iter
    logical,                  intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TASKLOOP>COMPUTE>PLANES'
    integer(kind=4) :: iblock,oblock,inplane,onplane,nplane
    integer(kind=entr_k) :: ibfp,obfp
    character(len=message_length) :: mess
    !
    ! No welcome on purpose here!
    if (iter%ilp.ge.iter%stopplane) then
      ! Last entry iteration was processed on previous iteration: all done
      cubeadm_taskloop_compute_planes = .false.
      return
    endif
    !
    ! Current task
    iter%num = iter%num+1
    !
    ! Input planes. The plan here is to define the range of input planes to
    ! be processed by this iteration. BUT it is valid for some commands to
    ! "process" planes beyond the input cube (e.g. EXTRACT). This is allowed
    ! as long as the range is fully in or fully off the cube (no overlap
    ! allowed). This implies 2 boundary conditions to define the range.
    iter%ifp = iter%ilp+1
    iter%ilp = iter%ifp+iter%nppertask-1
    ! Define a input block number. A block contains npperblock planes. The first
    ! block starts at ibfp defined below (so that we read blocks of maximum
    ! allowed size from the first actual plane to be read -- except last block
    ! which might smaller because of non-integer divisions --). The range of
    ! input planes we are trying to compute below must lie in the same block.
    ibfp = max(iter%startplane,1)  ! Input block first plane starts at first
                                   ! actual data to read from disk.
    iblock = (iter%ifp-ibfp)/iter%npperiblock+1
    ! Input range can not extend beyond current input block:
    iter%ilp = min(iter%ilp,ibfp-1+iblock*iter%npperiblock)
    ! Last block can be smaller than the other ones, range can not extend
    ! beyond stop plane
    iter%ilp = min(iter%ilp,iter%stopplane)
    ! Left boundary condition: no overlap, next iteration range will start
    ! from 1
    if (iter%ifp.le.0)       iter%ilp = min(iter%ilp,0)
    ! Right boundary condition: no overlap, next iteration range will start
    ! from np+1
    if (iter%ifp.le.iter%inp) iter%ilp = min(iter%ilp,iter%inp)
    inplane = iter%ilp-iter%ifp+1
    !
    ! Output planes
    iter%ofp = iter%olp+1
    iter%olp = iter%ofp+iter%nppertask-1
    ! Define an output block number for the same reason (the defined range
    ! must lie in the same block)
    obfp = 1  ! By construction
    oblock = (iter%ofp-obfp)/iter%npperoblock+1
    ! Output range can not extend beyond current output block
    iter%olp = min(iter%olp,obfp-1+oblock*iter%npperoblock)
    onplane = iter%olp-iter%ofp+1
    !
    ! Nplane? Use intersection of the selected ranges to ensure the
    ! requirement that the i/o ranges lie in their blocks is fulfilled.
    nplane = min(inplane,onplane)
    iter%ilp = iter%ifp+nplane-1
    iter%olp = iter%ofp+nplane-1
    !
    ! Output entries
    iter%ofe = iter%ole + 1
    if (iter%neperplane.ge.1) then
      iter%ole = iter%ole + nplane*iter%neperplane
    else
      ! Most likely subcube access: 1 subcube for all planes
      iter%ole = iter%ole + 1
    endif
    !
    cubeadm_taskloop_compute_planes = .true.
    !
    write(mess,'(11(A,I0))')  &
      'Computing iblock ',iblock,', oblock ',oblock,  &
      ', oentries ',iter%ofe,':',iter%ole,  &
      ' (',iter%ole-iter%ofe+1,' entries)'//  &
      ', iplanes ',iter%ifp,':',iter%ilp,  &
      ', oplanes ',iter%ofp,':',iter%olp,  &
      ' (',nplane,' planes)'
    call cubeadm_message(admseve%others,rname,mess)
    !
  end function cubeadm_taskloop_compute_planes
  !
  function cubeadm_taskloop_compute_data(iter,error)
    !----------------------------------------------------------------------
    ! Iterate the iterator for next range of DATA to be processed.
    ! This mode is mostly suited to iterate cubes of same total size (same
    ! dimensions) which have a bit-to-bit symmetry.
    !
    ! Return .false. if no more data is to be processed.
    !----------------------------------------------------------------------
    logical :: cubeadm_taskloop_compute_data
    type(cubeadm_iterator_t), intent(inout) :: iter
    logical,                  intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TASKLOOP>COMPUTE>DATA'
!     integer(kind=4) :: iblock
!     integer(kind=data_k) :: ndata,ne
!     character(len=message_length) :: mess
    !
    ! No welcome on purpose here!
!     if (iter%lastdata.ge.iter%nd) then
!       ! Last data iteration was processed on previous iteration: all done
!       cubeadm_taskloop_compute_data = .false.
!       return
!     endif
!     !
!     iter%firstdata = iter%lastdata  + 1
!     iter%lastdata  = iter%firstdata + iter%ndpertask - 1
!     ! Range can not extend beyond current block
!     iblock = (iter%firstdata-1)/iter%ndperblock+1
!     iter%lastdata = min(iter%lastdata,iblock*iter%ndperblock)
!     ! Last block can be smaller than the other ones, range can not extend beyond file:
!     iter%lastdata = min(iter%lastdata,iter%nd)
!     !
!     cubeadm_taskloop_compute_data = .true.
    !
    ! Not relevant is this context:
!   ! Convert firstdata and lastdata to entry numbers
!   ndata = iter%lastdata - iter%firstdata + 1
!   ne    = (ndata-1)/iter%ndperentry + 1 ! Exact integer value, except for last block
!   iter%firstplane = iter%lastplane + 1
!   iter%lastplane  = iter%lastplane + ne
!   iter%first = iter%firstplane
!   iter%last  = iter%lastplane
    !
    ! Debugging
!     write(mess,'(7(A,I0))')  'Processing block ',iblock,  &
!       ', data ',iter%firstdata,' to ',iter%lastdata,  &
!       ', entries ',iter%first,' to ',iter%last,  &
!       ' (',ne,' entries)'
!     call cubeadm_message(seve%i,rname,mess)
    !
  end function cubeadm_taskloop_compute_data
  !
  function cubeadm_taskloop_iterate(iter,error)
    !----------------------------------------------------------------------
    ! Iterate the iterator for next range of entries to be processed.
    ! Return .false. if no more data is to be processed.
    !----------------------------------------------------------------------
    logical :: cubeadm_taskloop_iterate
    type(cubeadm_iterator_t), intent(inout) :: iter
    logical,                  intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TASKLOOP>ITERATE'
    !
    if (.true.) then
      ! Plane-iterating mode
      cubeadm_taskloop_iterate = cubeadm_taskloop_iterate_planes(iter,error)
      if (error) return
    else
      ! Data-iterating mode
      ! cubeadm_taskloop_iterate = cubeadm_taskloop_iterate_data(iter,error)
      ! if (error) return
    endif
  end function cubeadm_taskloop_iterate
  !
  function cubeadm_taskloop_iterate_planes(iter,error)
    !----------------------------------------------------------------------
    ! Iterate the iterator for next range of PLANES to be processed.
    ! This mode is mostly suited to iterate cubes of same 3rd dimension (2
    ! first dimensions might differ).
    !
    ! Return .false. if no more data is to be processed.
    !----------------------------------------------------------------------
    logical :: cubeadm_taskloop_iterate_planes
    type(cubeadm_iterator_t), intent(inout) :: iter
    logical,                  intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TASKLOOP>ITERATE>PLANES'
    character(len=mess_l) :: mess
    !
    ! No welcome on purpose here!
    if (iter%num.ge.iter%nt) then
      ! Last task was processed on previous iteration: all done
      cubeadm_taskloop_iterate_planes = .false.
      return
    endif
    !
    iter%num = iter%num+1
    iter%ifp = iter%ifps(iter%num)
    iter%ilp = iter%ilps(iter%num)
    iter%ofp = iter%ofps(iter%num)
    iter%olp = iter%olps(iter%num)
    iter%ofe = iter%ofes(iter%num)
    iter%ole = iter%oles(iter%num)
    iter%ie  = iter%ofe-1  ! Current entry, to be iterated with cubeadm_iterator_iterate_entry
    !
    cubeadm_taskloop_iterate_planes = .true.
    !
    write(mess,'(9(A,I0))')  'Iterating oentries ', &
      iter%ofe,':',iter%ole,' (',iter%ole-iter%ofe+1,' entries)'//  &
      ', iplanes ',iter%ifp,':',iter%ilp,  &
      ', oplanes ',iter%ofp,':',iter%olp,  &
      ' (',iter%ilp-iter%ifp+1,' planes)'
    call cubeadm_message(admseve%others,rname,mess)
  end function cubeadm_taskloop_iterate_planes
  !
  function cubeadm_taskloop_idata2plane(iter,cube,pos1d)
    !-------------------------------------------------------------------
    ! Convert a data position (1D) into a plane number (3rd dimension)
    ! in the given cube
    !-------------------------------------------------------------------
    integer(kind=data_k) :: cubeadm_taskloop_idata2plane
    class(cubeadm_iterator_t), intent(in) :: iter  ! ZZZ Not needed here...
    type(cube_t),              intent(in) :: cube
    integer(kind=data_k),      intent(in) :: pos1d
    !
    integer(kind=data_k) :: n1,n2
    n1 = cube%tuple%current%desc%n1
    n2 = cube%tuple%current%desc%n2
    cubeadm_taskloop_idata2plane = (pos1d-1)/(n1*n2)+1
  end function cubeadm_taskloop_idata2plane
  !
  subroutine cubeadm_taskloop_iterator_debug(iter)
    type(cubeadm_iterator_compute_t), intent(in) :: iter
    !
    print *,""
    print *,"nd         = ",iter%nd
    print *,"ndperentry = ",iter%ndperentry
    print *,"ndperplane = ",iter%ndperplane
    print *,"ndpertask  = ",iter%ndpertask
    print *,"ndperblock = ",iter%ndperblock
    print *,"ndginentry = ",iter%ndginentry
    print *,"ndginblock = ",iter%ndginblock

    print *,"neperblock = ",iter%neperblock
    print *,"nepertask  = ",iter%nepertask
    print *,"neperplane = ",iter%neperplane

    print *,"np         = ",iter%np
    print *,"nppertask  = ",iter%nppertask
    print *,"npperblock = ",iter%npperblock
    print *,"npperentry = ",iter%npperentry

    print *,"ntperblock = ",iter%ntperblock

    print *,"nb         = ",iter%nb
    !
  end subroutine cubeadm_taskloop_iterator_debug
  !
  function cubeadm_iterator_iterate_entry(itertask,error)
    !-------------------------------------------------------------------
    ! Iterate the entry known by the current task iterator
    !-------------------------------------------------------------------
    logical :: cubeadm_iterator_iterate_entry
    class(cubeadm_iterator_t), intent(inout) :: itertask
    logical,                   intent(inout) :: error
    !
    cubeadm_iterator_iterate_entry = .false.
    !
    if (itertask%ie.eq.itertask%ole)  return  ! All done
    !
    itertask%ie = itertask%ie+1
    call cubeadm_entryloop_iterate(itertask%ie,error)
    if (error)  return
    cubeadm_iterator_iterate_entry = .true.  ! New entry and no error
  end function cubeadm_iterator_iterate_entry
  !
end module cubeadm_taskloop
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
