subroutine cubeio_set_descriptor_intrinsic(cub,error)
  use cubetools_header_interface
  use cubeio_messaging
  use cubeio_types
  use cubeio_interfaces, except_this=>cubeio_set_descriptor_intrinsic
  use cubeio_header_iodesc
  use cubeio_header_hgdf
  use cubeio_header_hfits
  !---------------------------------------------------------------------
  ! @ private
  ! Fill the cube descriptor from the proper header (intrinsic part)
  !---------------------------------------------------------------------
  type(cubeio_cube_t), intent(inout) :: cub
  logical,             intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='SET>DESCRIPTOR>INTRINSIC'
  type(cube_header_interface_t) :: interf
  !
  ! Pre-set the descriptor as unrecognized: it will have some basic
  ! valid values even in case of error during export. Proper values
  ! are re-set during import.
  call cubeio_iodesc_unrecognized(cub%desc,error)
  if (error)  return
  !
  ! NB: cub%desc%filekind is set out of this subroutine
  select case(cub%desc%filekind)
  case (code_filekind_fits)
    call cubeio_hfits_export(cub%hfits,interf,error)
    if (error)  return
  case (code_filekind_gdf)
    call cubeio_hgdf_export(cub%hgdf,interf,error)
    if (error)  return
  case default
    call cubeio_message(seve%e,rname,'No associated file on disk')
    error = .true.
    return
  end select
  call cubeio_iodesc_import(interf,cub%desc,error)
  if (error)  return
end subroutine cubeio_set_descriptor_intrinsic
!
subroutine cubeio_set_descriptor_external(cubset,cubdef,old,cub,error)
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_set_descriptor_external
  use cubeio_cube_define
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ public
  ! Fill the cube descriptor from the proper header (external part)
  !---------------------------------------------------------------------
  type(cube_setup_t),  intent(in)    :: cubset
  type(cube_define_t), intent(in)    :: cubdef
  logical,             intent(in)    :: old
  type(cubeio_cube_t), intent(inout) :: cub
  logical,             intent(inout) :: error
  !
  call cubeio_set_access(cubdef,cub,error)
  if (error)  return
  !
  call cubeio_set_filekind(cubdef,cub,error)
  if (error)  return
  !
  call cubeio_set_action(cubdef,old,cub,error)
  if (error)  return
  !
  ! Must come after cubeio_set_filekind
  call cubeio_set_unblank(cubset,cubdef,cub,error)
  if (error)  return
  call cubeio_set_reblank(cubset,cubdef,cub,error)
  if (error)  return
  !
  ! Data buffering kind: auto, disk or memory?
  if (cubdef%dobuffering) then
    call cubeio_set_buffering(cubdef%buffering,cubset%buff%limit,cub,error)
  else
    if (old) then
      call cubeio_set_buffering(cubset%buff%input,cubset%buff%limit,cub,error)
    else
      call cubeio_set_buffering(cubset%buff%output,cubset%buff%limit,cub,error)
    endif
  endif
  if (error)  return
  !
end subroutine cubeio_set_descriptor_external
!
subroutine cubeio_set_access(cubdef,cub,error)
  use cubetools_access
  use cubeio_messaging
  use cubeio_interfaces, except_this=>cubeio_set_access
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Set the cub%desc%access flag according to input code
  !---------------------------------------------------------------------
  type(cube_define_t), intent(in)    :: cubdef  ! Access mode description
  type(cubeio_cube_t), intent(inout) :: cub     !
  logical,             intent(inout) :: error   !
  ! Local
  character(len=*), parameter :: rname='SET>ACCESS'
  !
  if (cubdef%doaccess) then
    select case (cubdef%access)
    case (code_access_imaset_or_speset,code_access_any)
      ! Use access for native order
      cub%desc%access = cubetools_order2access(cub%desc%order)
    case (code_access_imaset,code_access_speset,code_access_subset,code_access_fullset)
      cub%desc%access = cubdef%access
    case default
      call cubeio_message(seve%e,rname,'Access mode not supported')
      error = .true.
      return
    end select
  else
    ! Use access for native order
    cub%desc%access = cubetools_order2access(cub%desc%order)
  endif
  !
end subroutine cubeio_set_access
!
subroutine cubeio_set_filekind(cubdef,cub,error)
  use cubeio_messaging
  use cubeio_interfaces, except_this=>cubeio_set_filekind
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Set the cub%desc%filekind flag according to input code
  !---------------------------------------------------------------------
  type(cube_define_t), intent(in)    :: cubdef  ! Action mode description
  type(cubeio_cube_t), intent(inout) :: cub     !
  logical,             intent(inout) :: error   !
  ! Local
  character(len=*), parameter :: rname='SET>FILEKIND'
  !
  if (cubdef%dofilekind) then
    cub%desc%filekind = cubdef%filekind
  else
    ! Leave unchanged
    continue
  endif
  !
end subroutine cubeio_set_filekind
!
subroutine cubeio_set_buffering(buffered,limit,cub,error)
  use gkernel_interfaces
  use cubeio_interfaces, except_this=>cubeio_set_buffering
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Set the cube%desc%buffered flag according to input code
  !---------------------------------------------------------------------
  integer(kind=4),     intent(in)    :: buffered  ! Buffering code
  real(kind=4),        intent(in)    :: limit     ! Limit in case of mode AUTO
  type(cubeio_cube_t), intent(inout) :: cub       !
  logical,             intent(inout) :: error     !
  !
  if (buffered.eq.code_buffer_auto) then
    if (cub%size().gt.limit) then
      cub%desc%buffered = code_buffer_disk
    else
      cub%desc%buffered = code_buffer_memory
    endif
  else
    cub%desc%buffered = buffered
  endif
  !
end subroutine cubeio_set_buffering
!
subroutine cubeio_set_action(cubdef,old,cub,error)
  use cubeio_messaging
  use cubeio_interfaces, except_this=>cubeio_set_action
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Set the cub%desc%action flag according to input code
  !---------------------------------------------------------------------
  type(cube_define_t), intent(in)    :: cubdef  ! Action mode description
  logical,             intent(in)    :: old     ! Action on old or new file?
  type(cubeio_cube_t), intent(inout) :: cub     !
  logical,             intent(inout) :: error   !
  ! Local
  character(len=*), parameter :: rname='SET>ACTION'
  logical :: oldok,newok
  !
  if (cubdef%doaction) then
    oldok =      old .and. (cubdef%action.eq.code_read_head .or.  &
                            cubdef%action.eq.code_read .or.  &
                            cubdef%action.eq.code_update)
    newok = .not.old .and.  cubdef%action.eq.code_write
    if (oldok.or.newok) then
      cub%desc%action = cubdef%action
    else
      call cubeio_message(seve%e,rname,'Invalid cube action mode')
      error = .true.
      return
    endif
  else
    if (old) then
      cub%desc%action = code_read  ! Default action is readonly
    else
      cub%desc%action = code_write  ! Action is write
    endif
  endif
  !
end subroutine cubeio_set_action
!
subroutine cubeio_set_unblank(cubset,cubdef,cub,error)
  use ieee_arithmetic
  use cubeio_messaging
  use cubeio_interfaces, except_this=>cubeio_set_unblank
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Set the cub%desc%unblank flag according to input code
  !---------------------------------------------------------------------
  type(cube_setup_t),  intent(in)    :: cubset  ! General setup
  type(cube_define_t), intent(in)    :: cubdef  ! Override?
  type(cubeio_cube_t), intent(inout) :: cub     !
  logical,             intent(inout) :: error   !
  ! Local
  character(len=*), parameter :: rname='SET>UNBLANK'
  !
  select case (cub%desc%filekind)
  case (code_filekind_fits)
    ! Cube is FITS: nothing to patch if already NaN
    ! No blanking needed, as data is already patched by cubefitsio_image_dataread
    cub%desc%unblank = code_null
    return
  case (code_filekind_gdf)
    ! Cube is GDF: nothing to patch if no blanking defined
    if (cub%hgdf%gil%blan_words.le.0 .or. cub%hgdf%gil%eval.lt.0.) then
      cub%desc%unblank = code_null
      return
    endif
  case default
    ! Not relevant if no associated file on disk
    continue
  end select
  !
  if (cubdef%dounblank) then
    ! General setup overriden
    cub%desc%unblank = cubdef%unblank
  else
    ! Use general setup
    cub%desc%unblank = cubset%blanking%rmode
  endif
  !
  if (cub%desc%unblank.eq.code_patchblank_err) then
    ! NB: the other modes are implemented when data are read
    call cubeio_message(seve%e,rname,'Cube has Bval/Eval values')
    call cubeio_message(seve%e,rname,  &
      'Use NAN command to patch them, or change SET\BLANKING mode to ONTHEFLY')
    error = .true.
    return
  endif
  !
end subroutine cubeio_set_unblank
!
subroutine cubeio_set_reblank(cubset,cubdef,cub,error)
  use cubeio_messaging
  use cubeio_interfaces, except_this=>cubeio_set_reblank
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Set the cub%desc%reblank flag
  !---------------------------------------------------------------------
  type(cube_setup_t),  intent(in)    :: cubset  ! General setup
  type(cube_define_t), intent(in)    :: cubdef  ! Override?
  type(cubeio_cube_t), intent(inout) :: cub     !
  logical,             intent(inout) :: error   !
  ! Local
  character(len=*), parameter :: rname='SET>REBLANK'
  !
  select case (cub%desc%filekind)
  case (code_filekind_fits)
    ! Cube is FITS: re-blanking is not supported/implemented
    cub%desc%reblank = .false.
    return
  case (code_filekind_gdf)
    ! Cube is GDF
    cub%desc%reblank = cubdef%doreblank
    cub%desc%bval = cubdef%bval
    cub%desc%eval = cubdef%eval
  case default
    ! Not relevant if no associated file on disk
    cub%desc%reblank = .false.
    return
  end select
end subroutine cubeio_set_reblank
!
subroutine cubeio_max_chan_block(cubset,cub,blocksize,blockname,nchan,error)
  use cubeio_messaging
  use gkernel_interfaces
  use cubetools_format
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_max_chan_block
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ public
  ! Return the maximum number of channels which can be used in a
  ! cube_block_t for cube given as argument
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cubeio_cube_t),  intent(in)    :: cub
  real(kind=4),         intent(in)    :: blocksize  ! [Bytes]
  character(len=*),     intent(in)    :: blockname
  integer(kind=chan_k), intent(out)   :: nchan
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='MAX>CHAN>BLOCK'
  real(kind=4) :: onechan
  integer(kind=chan_k) :: ndiv
  character(len=message_length) :: mess
  !
  ! The size of the block should fit in the following constraint:
  !  - must fit at least 1 entry (otherwise we can't do anything)
  !  - if memory is below the dedicated maximum size, increase the buffer
  !    to the maximum possible
  onechan = cub%chansize() ! [Bytes] Weight of one channel
  nchan = min(floor(blocksize/onechan),cub%desc%nc)
  if (nchan.le.0) then
    nchan = 1
    write(mess,'(5A)')  'Buffer (',cubetools_format_memsize(blocksize),  &
      ') is not large enough to store one channel (',  &
      cubetools_format_memsize(onechan),')'
    call cubeio_message(seve%w,rname,mess)
    call cubeio_message(seve%w,rname,blockname//' should be increased for better efficiency')
  endif
  !
  ! Try to avoid very odd split, e.g. dividing 5=4+1 or 9=4+4+1. We prefer
  ! 5=3+2 or 9=3+3+3, i.e. same number of divisions but evenly distributed.
  ndiv = (cub%desc%nc-1)/nchan+1
  nchan = (cub%desc%nc-1)/ndiv+1
  !
  write(mess,'(A,I0,A)')  'Buffer will store up to ',nchan,' channels'
  call cubeio_message(ioseve%others,rname,mess)
  !
end subroutine cubeio_max_chan_block
!
subroutine cubeio_max_y_block(cubset,cub,blocksize,blockname,ny,error)
  use cubeio_messaging
  use gkernel_interfaces
  use cubetools_format
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_max_y_block
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ public
  ! Return the maximum number of Y rows which can be used in a
  ! cube_block_t for cube given as argument
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cubeio_cube_t),  intent(in)    :: cub
  real(kind=4),         intent(in)    :: blocksize  ! [Bytes]
  character(len=*),     intent(in)    :: blockname
  integer(kind=pixe_k), intent(out)   :: ny
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='MAX>Y>BLOCK'
  real(kind=4) :: oney
  integer(kind=pixe_k) :: ndiv
  character(len=message_length) :: mess
  !
  ! The size of the block should fit in the following constraint:
  !  - must fit at least 1 entry (otherwise we can't do anything)
  !  - if memory is below the dedicated maximum size, increase the buffer
  !    to the maximum possible
  oney = cub%ysize()  ! [Bytes] Weight of one Y row
  ny = min(floor(blocksize/oney),cub%desc%ny)
  if (ny.le.0) then
    ny = 1
    write(mess,'(5A)')  'Buffer (',cubetools_format_memsize(blocksize),  &
      ') is not large enough to store one Y row (',  &
      cubetools_format_memsize(oney),')'
    call cubeio_message(seve%w,rname,mess)
    call cubeio_message(seve%w,rname,blockname//' should be increased for better efficiency')
  endif
  !
  ! Try to avoid very odd split, e.g. dividing 5=4+1 or 9=4+4+1. We prefer
  ! 5=3+2 or 9=3+3+3, i.e. same number of divisions but evenly distributed.
  ndiv = (cub%desc%ny-1)/ny+1
  ny = (cub%desc%ny-1)/ndiv+1
  !
  write(mess,'(A,I0,A)')  'Buffer will store up to ',ny,' Y rows'
  call cubeio_message(ioseve%others,rname,mess)
  !
end subroutine cubeio_max_y_block
!
subroutine cubeio_max_any_block(cubset,cub,blocksize,blockname,nplane,error)
  use cubeio_messaging
  use gkernel_interfaces
  use cubetools_format
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_max_any_block
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ public
  ! Return the maximum number of planes which can be used in a
  ! cube_block_t for cube given as argument
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cubeio_cube_t),  intent(in)    :: cub
  real(kind=4),         intent(in)    :: blocksize  ! [Bytes]
  character(len=*),     intent(in)    :: blockname
  integer(kind=data_k), intent(out)   :: nplane
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='MAX>ANY>BLOCK'
  real(kind=4) :: oneplane
  integer(kind=data_k) :: ndiv
  character(len=message_length) :: mess
  !
  ! The size of the block should fit in the following constraint:
  !  - must fit at least 1 entry (otherwise we can't do anything)
  !  - if memory is below the dedicated maximum size, increase the buffer
  !    to the maximum possible
  oneplane = cub%planesize() ! [Bytes] Weight of one plane
  nplane = min(floor(blocksize/oneplane),cub%desc%n3)
  if (nplane.le.0) then
    nplane = 1
    write(mess,'(5A)')  'Buffer (',cubetools_format_memsize(blocksize),  &
      ') is not large enough to store one plane (',  &
      cubetools_format_memsize(oneplane),')'
    call cubeio_message(seve%w,rname,mess)
    call cubeio_message(seve%w,rname,blockname//' should be increased for better efficiency')
  endif
  !
  ! Try to avoid very odd split, e.g. dividing 5=4+1 or 9=4+4+1. We prefer
  ! 5=3+2 or 9=3+3+3, i.e. same number of divisions but evenly distributed.
  ndiv = (cub%desc%n3-1)/nplane+1
  nplane = (cub%desc%n3-1)/ndiv+1
  !
  write(mess,'(A,I0,A)')  'Buffer will store up to ',nplane,' planes'
  call cubeio_message(ioseve%others,rname,mess)
  !
end subroutine cubeio_max_any_block
!
subroutine cubeio_finalize_cube(cubset,head,cub,error)
  use gkernel_interfaces
  use cubetools_setup_types
  use cubefitsio_header_read
  use cubeio_interfaces, except_this=>cubeio_finalize_cube
  use cubeio_types
  use cubeio_timing
  !---------------------------------------------------------------------
  ! @ public
  ! This subroutine is to be invoked once the cube is fully read or
  ! created or modified, to leave it in a 'better' state.
  !---------------------------------------------------------------------
  type(cube_setup_t),  intent(in)    :: cubset
  type(cube_header_t), intent(in)    :: head
  type(cubeio_cube_t), intent(inout) :: cub
  logical,             intent(inout) :: error
  !
  ! Might need flushing of last block in case of disk mode
  call cubeio_flush_block(cubset,head,cub,cub%block,error)
  if (error)  continue
  !
  ! Free temporary buffers as they are not needed anymore
  call cubeio_block_free(cub%block,error)
  if (error)  continue
  !
  if (cub%desc%action.eq.code_write .or.  &
      cub%desc%action.eq.code_update) then
    if (cub%desc%filekind.eq.code_filekind_fits) then
      ! We have written a FITS file. Reload its header in our
      ! dictionary. This is the best way to retrieve what is actually
      ! on disk, as CFITSIO adds its owns comments and keywords beyond
      ! our choice.
      call cubefitsio_header_fill(cub%hfits,error)
      if (error)  continue
    endif
  endif
  !
  ! Timing feedback?
  call gag_cputime_get(cub%time%total)  ! Get total time on this cube
  call cubeio_timing_feedback(cubset,cub)
  !
end subroutine cubeio_finalize_cube
