!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
module cubedag_tuple_trans
  use gkernel_interfaces
  use cubetools_parameters
  use cubedag_messaging
  !
  public :: cubedag_trans_t
  private
  !
  ! Type describing each object in a tuple. It can lie in memory and/or disk
  type :: cubedag_trans_t
    character(len=maxdim) :: iorder = ''         ! Intrinsic order (empty: unused)
    integer(kind=code_k)  :: morder = code_null  ! Mapped order (null: unused)
    integer(kind=8)       :: mtstamp = 0         ! Memory time stamp (0: unused)
    integer(kind=8)       :: dtstamp = 0         ! Disk time stamp (0: unused)
    character(len=file_l) :: file = ''           ! File on disk
    integer(kind=4)       :: hdu = 0             ! HDU in file on disk
  contains
    procedure, public :: list       => cubedag_trans_list
    procedure, public :: read       => cubedag_trans_read
    procedure, public :: write      => cubedag_trans_write
    procedure, public :: upsert     => cubedag_trans_upsert
    procedure, public :: diskupdate => cubedag_trans_diskupdate
    procedure, public :: access     => cubedag_trans_access
    procedure, public :: filename   => cubedag_trans_filename
    procedure, public :: location   => cubedag_trans_location
    procedure, public :: disksize   => cubedag_trans_disksize
  end type cubedag_trans_t
  !
  character(len=*), parameter :: form_tup='(A,T26,A,1X,I11,I20,1X,A,1X,I0)'  ! For version 0.5
  !
contains
  subroutine cubedag_trans_list(trans,error)
    use cubetools_format
    !---------------------------------------------------------------------
    ! Read tuple from disk for latest versions
    !---------------------------------------------------------------------
    class(cubedag_trans_t), intent(in)   :: trans
    logical,                intent(inout) :: error
    !
    character(len=6) :: set
    character(len=mess_l) :: mess
    character(len=50) :: shortname
    character(len=*), parameter :: rname='TRANS>LIST'
    !
    select case (trans%morder)
    case (code_cube_imaset)
      set = 'ImaSet'
    case (code_cube_speset)
      set = 'SpeSet'
    case default
      set = 'UnkSet'
    end select
    !
    ! Memory
    if (trans%mtstamp.gt.0) then
      mess = cubetools_format_stdkey_boldval(set,'none',60)
      mess = trim(mess)//'  '//cubetools_format_stdkey_boldval('Status','---',18)
      call cubedag_message(seve%r,rname,mess)
    endif
    !
    ! Disk
    if (trans%dtstamp.gt.0) then
      call trans%location(code_buffer_disk,shortname)
      mess = cubetools_format_stdkey_boldval(set,shortname,60)
      mess = trim(mess)//'  '//cubetools_format_stdkey_boldval('Status','---',18)
      call cubedag_message(seve%r,rname,mess)
    endif
  end subroutine cubedag_trans_list
  !
  subroutine cubedag_trans_read(trans,lun,dagdir,curdir,error)
    use cubetools_realpath
    !---------------------------------------------------------------------
    ! Read tuple from disk for latest versions
    !---------------------------------------------------------------------
    class(cubedag_trans_t),     intent(out)   :: trans
    integer(kind=4),            intent(in)    :: lun
    character(len=*),           intent(in)    :: dagdir
    type(cubetools_realpath_t), intent(inout) :: curdir
    logical,                    intent(inout) :: error
    !
    integer(kind=4) :: ier,hdu
    character(len=12) :: key
    character(len=maxdim) :: iorder
    integer(kind=code_k) :: morder
    integer(kind=8) :: tstamp
    character(len=file_l) :: file
    type(cubetools_realpath_t) :: path
    !
    ! List-directed read allows for flexible file length
    read(lun,*,iostat=ier)  key,iorder,morder,tstamp,file,hdu
    if (ier.lt.0) return  ! EOF
    if (ier.gt.0) then
      call putios('E-TUPLE>READ,  ',ier)
      error = .true.
      return
    endif
    trans%iorder  = iorder
    trans%morder  = morder
    trans%dtstamp = tstamp
    trans%hdu     = hdu
    !
    if (file.ne.'') then
      ! File path was saved absolute, or relative to the directory of
      ! the DAG file
      call path%absolutefrom(file,dagdir)
      ! And now resolve file path relative to current directory
      trans%file = path%relativeto(curdir)
    endif
  end subroutine cubedag_trans_read
  !
  subroutine cubedag_trans_write(trans,lun,dagpath,error)
    use cubetools_realpath
    !-------------------------------------------------------------------
    !
    !-------------------------------------------------------------------
    class(cubedag_trans_t),     intent(in)    :: trans
    integer(kind=4),            intent(in)    :: lun
    type(cubetools_realpath_t), intent(inout) :: dagpath
    logical,                    intent(inout) :: error
    !
    character(len=file_l) :: relpath
    type(cubetools_realpath_t) :: path
    character(len=*), parameter :: rname='TRANS>WRITE'
    !
    if (trans%dtstamp.le.0) then
      ! Error... or skip?
      call cubedag_message(seve%e,rname,  &
        'Internal error: can not snapshot a cube with no disk information')
      error = .true.
      return
    endif
    !
    ! Compute path relative to the DAG file (cube.dag)
    call path%resolve(trans%file)
    relpath = path%relativeto(dagpath)
    !
    write(lun,form_tup)  'TUPLE',  &
                         trans%iorder,  &
                         trans%morder,  &
                         trans%dtstamp,  &
                         '"'//trim(relpath)//'"', &  ! Quotes to support partially or fully blank strings
                         trans%hdu
  end subroutine cubedag_trans_write
  !
  subroutine cubedag_trans_upsert(trans,iorder,morder,location,onefile,hdu,  &
    updated,error)
    !---------------------------------------------------------------------
    ! Upsert one entry
    !---------------------------------------------------------------------
    class(cubedag_trans_t), intent(inout) :: trans
    character(len=*),       intent(in)    :: iorder
    integer(kind=code_k),   intent(in)    :: morder    ! imaset, speset, unkset
    integer(kind=code_k),   intent(in)    :: location  ! Memory/disk
    character(len=*),       intent(in)    :: onefile
    integer(kind=4),        intent(in)    :: hdu
    logical,                intent(inout) :: updated
    logical,                intent(inout) :: error
    ! Local
    integer(kind=4) :: ier
    integer(kind=8) :: mtime
    logical :: update
    !
    if (location.eq.code_buffer_disk) then
      if (gag_inquire(onefile,len_trim(onefile)).eq.0) then
        ! File exists or it disappeared
        ier = gag_mtime(onefile,mtime)
        ! ZZZ should trap error
        update = mtime.gt.trans%dtstamp
      else
        mtime = 0
        update = .true.
      endif
    else
      mtime = 1
      update = .true.
    endif
    !
    if (update) then
      ! ZZZ Should we complain if file name has changed?
      trans%iorder = iorder
      trans%morder = morder
      if (location.eq.code_buffer_disk) then
        trans%dtstamp = mtime
        trans%file = onefile
        trans%hdu = hdu
      else
        trans%mtstamp = mtime
      endif
      updated = .true.
    endif
  end subroutine cubedag_trans_upsert
  !
  subroutine cubedag_trans_diskupdate(trans,updated,error)
    !-------------------------------------------------------------------
    ! Update the timestamp of one file ONLY IF IT HAS CHANGED.
    !-------------------------------------------------------------------
    class(cubedag_trans_t), intent(inout) :: trans
    logical,                intent(inout) :: updated
    logical,                intent(inout) :: error
    !
    character(len=*), parameter :: rname='TUPLE>DISKUPDATE>ONE'
    !
    if (trans%dtstamp.eq.0)  return  ! Disk time stamp unused: nothing to update
    !
    if (gag_inquire(trans%file,len_trim(trans%file)).ne.0) then
      ! File is referenced but it disappeared: discard it
      call cubedag_message(seve%w,rname,  &
        'File '//trim(trans%file)//' does not exist anymore')
      trans%dtstamp = 0
      trans%file = ''
      trans%hdu = 0
      ! For safety also disable the consistency flag: NOT REIMPLEMENTED
      ! pos_cons = cubedag_tuple_position(code_null,code_null)
      ! tup%tstamp(pos_cons) = 0
      ! tup%file(pos_cons) = ''
      ! tup%hdu(pos_cons) = 0
      ! updated = .true.
      return
    endif
    !
    ! File exists: update if needed
    call trans%upsert(trans%iorder,trans%morder,code_buffer_disk,trans%file,  &
      trans%hdu,updated,error)
    if (error)  return
  end subroutine cubedag_trans_diskupdate
  !
  function cubedag_trans_access(trans)
    !-------------------------------------------------------------------
    ! Return the access code (mapped order)
    !-------------------------------------------------------------------
    integer(kind=code_k) :: cubedag_trans_access
    class(cubedag_trans_t), intent(in) :: trans
    !
    cubedag_trans_access = trans%morder
  end function cubedag_trans_access
  !
  function cubedag_trans_filename(trans,location)
    !-------------------------------------------------------------------
    ! Return the file name for access+location cube
    !-------------------------------------------------------------------
    character(len=file_l) :: cubedag_trans_filename
    class(cubedag_trans_t), intent(in) :: trans
    integer(kind=code_k),   intent(in) :: location
    !
    if (location.eq.code_buffer_memory) then
      if (trans%mtstamp.le.0) then
        cubedag_trans_filename = '<not-yet-defined>'
      else
        cubedag_trans_filename = '<memory>'
      endif
    else
      if (trans%dtstamp.le.0) then
        cubedag_trans_filename = '<not-yet-defined>'
      else
        cubedag_trans_filename = trans%file
      endif
    endif
  end function cubedag_trans_filename
  !
  subroutine cubedag_trans_location(trans,location,shortloc)
    !-------------------------------------------------------------------
    ! Return the location (path) of the cube referenced by the tuple,
    ! in a string suited for LIST (i.e. properly truncating it)
    !-------------------------------------------------------------------
    class(cubedag_trans_t), intent(in)  :: trans
    integer(kind=code_k),   intent(in)  :: location  ! Memory/Disk
    character(len=*),       intent(out) :: shortloc
    ! Local
    character(len=file_l) :: cubename
    integer(kind=4) :: nc,lmax
    !
    cubename = trans%filename(location)
    nc = len_trim(cubename)
    !
    ! Append HDU number if relevant
    if (location.eq.code_buffer_disk .and. trans%hdu.gt.1) then
      write(cubename(nc+1:),'(a1,i0,a1)') '[',trans%hdu,']'
      nc = len_trim(cubename)
    endif
    !
    ! Truncate if needed
    lmax = len(shortloc)
    if (nc.gt.lmax) then
      shortloc = '...'//cubename(nc-(lmax-4):nc)
    else
      shortloc = cubename(1:nc)
    endif
  end subroutine cubedag_trans_location
  !
  function cubedag_trans_disksize(trans,location)
    !-------------------------------------------------------------------
    ! Return the disk footprint of one file (one order) in the tuple, if
    ! relevant
    !-------------------------------------------------------------------
    integer(kind=size_length) :: cubedag_trans_disksize  ! [bytes]
    class(cubedag_trans_t), intent(in) :: trans
    integer(kind=code_k),   intent(in) :: location
    ! Local
    integer(kind=4) :: ier,nc
    integer(kind=8) :: onesize
    !
    cubedag_trans_disksize = 0
    if (location.eq.code_buffer_memory)  return  ! Memory has no disk footprint
    !
    if (trans%dtstamp.le.0)  return  ! No file on disk referenced
    !
    nc = len_trim(trans%file)
    if (gag_inquire(trans%file,nc).ne.0)  return
    !
    ! This is not what we want in case of several HDUs:
    ier = gag_filsize(trans%file(1:nc),onesize)
    if (ier.ne.0)  return
    !
    cubedag_trans_disksize = onesize
  end function cubedag_trans_disksize
  !
end module cubedag_tuple_trans
!
module cubedag_tuple
  use gkernel_interfaces
  use cubetools_parameters
  use cubedag_messaging
  use cubedag_tuple_trans
  !
  public :: cubedag_tuple_t
  public :: cubedag_tuple_upsert,cubedag_tuple_setconsistent,  &
            cubedag_tuple_hasuptodatefile,cubedag_tuple_reset,  &
            cubedag_tuple_diskupdate,cubedag_tuple_rmmemo,cubedag_tuple_rmfiles
  private
  !
  ! The tuple is a collection of transpositions of the same data object
  integer(kind=4), parameter :: tuple_mtrans=4  ! Static allocation (should be made dynamic)
  type :: cubedag_tuple_t
   !consistency description: not reimplemented
    integer(kind=4),       private :: n = 0                ! Number of transpositions currently in use
    type(cubedag_trans_t), private :: trans(tuple_mtrans)  ! Transpositions
    integer(kind=4),       private :: itrans = 0           ! Iterator running index
  contains
    procedure, private :: position  => cubedag_tuple_position
    procedure          :: list      => cubedag_tuple_list
    procedure          :: hascube   => cubedag_tuple_hascube
    procedure, public  :: access    => cubedag_tuple_access
    procedure          :: filename  => cubedag_tuple_filename
    procedure          :: location  => cubedag_tuple_location
   !procedure          :: debug     => cubedag_tuple_debug
    procedure          :: disksizes => cubedag_tuple_disksizes
    procedure          :: disksize  => cubedag_tuple_disksize
    procedure          :: contains  => cubedag_tuple_contains
    procedure, public  :: write     => cubedag_tuple_write
    procedure, public  :: read02    => cubedag_tuple_read02
    procedure, public  :: read03    => cubedag_tuple_read03
    procedure, public  :: read04    => cubedag_tuple_read04
    procedure, public  :: read      => cubedag_tuple_read
    procedure, public  :: to_struct => cubedag_tuple_to_struct
    ! Iterator
    procedure, public :: iterate_init => cubedag_tuple_iterate_init
    procedure, public :: iterate      => cubedag_tuple_iterate
  end type cubedag_tuple_t
  !
  ! Formats in DAG file
  integer(kind=4),  parameter :: tuple_mdisk=4                            ! For version 0.4 and below
  character(len=*), parameter :: form_i4   ='(A,T26,I11,20(I11))'         ! Scalar or array I*4
  character(len=*), parameter :: form_tup02='(A,T26,I11,I20,3X,A)'        ! For version 0.2 and below
  character(len=*), parameter :: form_tup  ='(A,T26,I11,I20,1X,A,1X,I0)'  ! For version 0.3 and 0.4
  !
contains
  !
  subroutine cubedag_tuple_reset(tuple)
    !---------------------------------------------------------------------
    ! Fully reset a tuple
    !---------------------------------------------------------------------
    type(cubedag_tuple_t), intent(out) :: tuple
    return
  end subroutine cubedag_tuple_reset
  !
  subroutine cubedag_tuple_read02(tup,lun,error)
    !---------------------------------------------------------------------
    ! Read tuple from disk for version 0.2 and earlier
    !---------------------------------------------------------------------
    class(cubedag_tuple_t), intent(out)   :: tup
    integer(kind=4),        intent(in)    :: lun
    logical,                intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TUPLE>READ02'
    integer(kind=4) :: ier,access,i
    integer(kind=8) :: tstamp
    character(len=file_l) :: file
    character(len=12) :: key
    !
    tup%n = 0
    do i=1,tuple_mdisk
      read(lun,form_tup02,iostat=ier)  key,access,tstamp,file
      if (ier.lt.0)                exit  ! EOF
      if (ier.gt.0) then
        call putios('E-TUPLE>READ,  ',ier)
        error = .true.
        return
      endif
      if (tstamp.le.0)  cycle  ! No information available => nothing to store in latest format
      if (i.eq.1)       cycle  ! Consistency flag not reimplemented
      !
      tup%n = tup%n+1
      if (access.eq.code_cube_speset) then
        tup%trans(tup%n)%iorder = 'CAB'
      else
        tup%trans(tup%n)%iorder = 'ABC'
      endif
      tup%trans(tup%n)%morder = access
      tup%trans(tup%n)%mtstamp = 0
      tup%trans(tup%n)%dtstamp = tstamp
      tup%trans(tup%n)%file = file
      tup%trans(tup%n)%hdu = 1  ! Default in version 0.2
    enddo
  end subroutine cubedag_tuple_read02
  !
  subroutine cubedag_tuple_read03(tup,lun,error)
    !---------------------------------------------------------------------
    ! Read tuple version 0.3
    ! In this version, file paths are relative to current working
    ! directory
    !---------------------------------------------------------------------
    class(cubedag_tuple_t), intent(out)   :: tup
    integer(kind=4),        intent(in)    :: lun
    logical,                intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TUPLE>READ'
    integer(kind=4) :: ier,access,i,hdu
    integer(kind=8) :: tstamp
    character(len=file_l) :: file
    character(len=12) :: key
    !
    tup%n = 0
    do i=1,tuple_mdisk
      ! List-directed read allows for flexible file length
      read(lun,*,iostat=ier)  key,access,tstamp,file,hdu
      if (ier.lt.0)                exit  ! EOF
      if (ier.gt.0) then
        call putios('E-TUPLE>READ,  ',ier)
        error = .true.
        return
      endif
      if (tstamp.le.0)  cycle  ! No information available => nothing to store in latest format
      if (i.eq.1)       cycle  ! Consistency flag not reimplemented
      !
      tup%n = tup%n+1
      if (access.eq.code_cube_speset) then
        tup%trans(tup%n)%iorder = 'CAB'
      else
        tup%trans(tup%n)%iorder = 'ABC'
      endif
      tup%trans(tup%n)%morder = access
      tup%trans(tup%n)%mtstamp = 0
      tup%trans(tup%n)%dtstamp = tstamp
      tup%trans(tup%n)%file = file
      tup%trans(tup%n)%hdu = hdu
    enddo
  end subroutine cubedag_tuple_read03
  !
  subroutine cubedag_tuple_read04(tup,lun,dagdir,curdir,error)
    use cubetools_realpath
    !---------------------------------------------------------------------
    ! Read tuple version 0.4
    ! In this version, there is no intrinsic order
    !---------------------------------------------------------------------
    class(cubedag_tuple_t),     intent(out)   :: tup
    integer(kind=4),            intent(in)    :: lun
    character(len=*),           intent(in)    :: dagdir
    type(cubetools_realpath_t), intent(inout) :: curdir
    logical,                    intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TUPLE>READ04'
    integer(kind=4) :: ier,access,i,hdu
    integer(kind=8) :: tstamp
    character(len=file_l) :: file
    character(len=12) :: key
    type(cubetools_realpath_t) :: path
    !
    tup%n = 0
    do i=1,tuple_mdisk
      ! List-directed read allows for flexible file length
      read(lun,*,iostat=ier)  key,access,tstamp,file,hdu
      if (ier.lt.0)                exit  ! EOF
      if (ier.gt.0) then
        call putios('E-TUPLE>READ,  ',ier)
        error = .true.
        return
      endif
      if (tstamp.le.0)  cycle  ! No information available => nothing to store in latest format
      if (i.eq.1)       cycle  ! Consistency flag not reimplemented
      !
      tup%n = tup%n+1
      if (access.eq.code_cube_speset) then
        tup%trans(tup%n)%iorder = 'CAB'
      else
        tup%trans(tup%n)%iorder = 'ABC'
      endif
      tup%trans(tup%n)%morder = access
      tup%trans(tup%n)%mtstamp = 0
      tup%trans(tup%n)%dtstamp = tstamp
      tup%trans(tup%n)%hdu = hdu
      !
      if (file.ne.'') then
        ! File path was saved absolute, or relative to the directory of
        ! the DAG file
        call path%absolutefrom(file,dagdir)
        ! And now resolve file path relative to current directory
        tup%trans(tup%n)%file = path%relativeto(curdir)
      endif
    enddo
  end subroutine cubedag_tuple_read04
  !
  subroutine cubedag_tuple_read(tup,lun,dagdir,curdir,error)
    use cubetools_realpath
    !---------------------------------------------------------------------
    ! Read tuple from disk for latest versions
    !---------------------------------------------------------------------
    class(cubedag_tuple_t),     intent(out)   :: tup
    integer(kind=4),            intent(in)    :: lun
    character(len=*),           intent(in)    :: dagdir
    type(cubetools_realpath_t), intent(inout) :: curdir
    logical,                    intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TUPLE>READ'
    integer(kind=4) :: ier,i
    character(len=12) :: key
    !
    read(lun,form_i4,iostat=ier) key,tup%n
    if (ier.gt.0) then
      call putios('E-TUPLE>READ,  ',ier)
      error = .true.
      return
    endif
    if (key.ne.'TUPLE_N') then
      call cubedag_message(seve%e,rname,'Expected key TUPLE_N, got '//key)
      error = .true.
      return
    endif
    !
    do i=1,tup%n
      call tup%trans(i)%read(lun,dagdir,curdir,error)
      if (error)  return
    enddo
  end subroutine cubedag_tuple_read
  !
  subroutine cubedag_tuple_write(tup,lun,dagpath,error)
    use cubetools_realpath
    !---------------------------------------------------------------------
    ! Write tuple to disk
    !---------------------------------------------------------------------
    class(cubedag_tuple_t),     intent(in)    :: tup
    integer(kind=4),            intent(in)    :: lun
    type(cubetools_realpath_t), intent(inout) :: dagpath
    logical,                    intent(inout) :: error
    !
    integer(kind=4) :: i,n
    character(len=*), parameter :: rname='TUPLE>WRITE'
    !
    ! Snapshot only the transpositions with a disk file. Need to count them.
    n = 0
    do i=1,tup%n
      if (tup%trans(i)%dtstamp.gt.0)  n = n+1
    enddo
    !
    write(lun,form_i4)  'TUPLE_N',n
    !
    do i=1,tup%n
      if (tup%trans(i)%dtstamp.gt.0) then
        call tup%trans(i)%write(lun,dagpath,error)
        if (error)  return
      endif
    enddo
  end subroutine cubedag_tuple_write
  !
  subroutine cubedag_tuple_upsert(tup,iorder,morder,location,onefile,hdu,error)
    !---------------------------------------------------------------------
    ! Insert (if the file is new) or update (if the file has changed) one
    ! file in the tuple file. Nothing done if file is already up-to-date.
    ! If file does not exist, its timestamp is set to 0.
    !---------------------------------------------------------------------
    type(cubedag_tuple_t), intent(inout) :: tup
    character(len=*),      intent(in)    :: iorder
    integer(kind=code_k),  intent(in)    :: morder
    integer(kind=code_k),  intent(in)    :: location
    character(len=*),      intent(in)    :: onefile
    integer(kind=4),       intent(in)    :: hdu
    logical,               intent(inout) :: error
    !
    integer(kind=4) :: pos
    logical :: updated
    character(len=*), parameter :: rname='TUPLE>UPSERT'
    !
    if (iorder.eq.'') then
      call cubedag_message(seve%e,rname,'Intrinsic order is not set')
      error = .true.
      return
    endif
    !
    pos = tup%position(iorder)
    if (pos.eq.0) then
      ! Allocate a new slot
      if (tup%n.ge.tuple_mtrans) then
        ! We should go for dynamic reallocation of the tuple
        call cubedag_message(seve%e,rname,'Not implemented: too many transpositions')
        error = .true.
        return
      endif
      tup%n = tup%n+1
      pos = tup%n
    endif
    !
    updated = .false.
    call tup%trans(pos)%upsert(iorder,morder,location,onefile,hdu,updated,error)
    if (error)  return
  end subroutine cubedag_tuple_upsert
  !
  subroutine cubedag_tuple_diskupdate(tup,error)
    !-------------------------------------------------------------------
    ! Update the disk file timestamps of all the files ONLY IF THEY HAVE
    ! CHANGED. The consistency timestamp is left unchanged on purpose.
    !-------------------------------------------------------------------
    type(cubedag_tuple_t), intent(inout) :: tup
    logical,               intent(inout) :: error
    !
    logical :: updated
    integer(kind=4) :: iorder
    !
    updated = .false.
    do iorder=1,tup%n
      call tup%trans(iorder)%diskupdate(updated,error)
      if (error)  return
    enddo
  end subroutine cubedag_tuple_diskupdate
  !
  subroutine cubedag_tuple_setconsistent(tup,error)
    !---------------------------------------------------------------------
    ! Insert or update the consistency flag. It is the responsibility of
    ! the caller to ensure the files are consistent at the time this
    ! subroutine is called.
    !---------------------------------------------------------------------
    type(cubedag_tuple_t), intent(inout) :: tup
    logical,               intent(inout) :: error
    ! Local
    character(len=*), parameter :: rname='TUPLE>SETCONSISTENCY'
    !integer(kind=4) :: ier,pos
    !integer(kind=8) :: time
    !
    !call cubedag_message(seve%e,rname,'Consistency flag is not reimplemented')
    !error = .true.
    !return
    !
    !if (tup%tstamp(cubedag_tuple_position(code_cube_imaset,code_buffer_disk)).le.0 .or.  &
    !    tup%tstamp(cubedag_tuple_position(code_cube_speset,code_buffer_disk)).le.0) then
    !  call cubedag_message(seve%e,rname,  &
    !    'Can not set consistency while the 2 cubes are not present in tuple')
    !  error = .true.
    !  return
    !endif
    !
    !ier = gag_time(time)
    !pos = cubedag_tuple_position(code_null,code_null)
    !tup%code(pos) = code_null
    !tup%tstamp(pos) = time
    !tup%file(pos) = 'consistency'
    !tup%hdu(pos) = 0
  end subroutine cubedag_tuple_setconsistent
  !
  function cubedag_tuple_hasuptodatefile(tup,morder,location,iorder,onefile,hdu)
    use cubetools_access_types
    !---------------------------------------------------------------------
    ! Get the file for the given mapped order. The file is assumed "found"
    ! if:
    ! 1) it is present in the tuple and
    ! 2) the tuple is consistent, or the tuple is inconsistent but the
    !    requested file is the newest.
    !---------------------------------------------------------------------
    logical :: cubedag_tuple_hasuptodatefile
    type(cubedag_tuple_t), intent(in)  :: tup
    integer(kind=code_k),  intent(in)  :: morder    ! imaset, speset
    integer(kind=code_k),  intent(in)  :: location  ! Could be memory or disk
    character(len=*),      intent(out) :: iorder    ! Returned intrinsic order
    character(len=*),      intent(out) :: onefile   ! Returned file name
    integer(kind=4),       intent(out) :: hdu       ! Returned HDU in file
    !
    integer(kind=4) :: jorder
    !
    cubedag_tuple_hasuptodatefile = .false.
    iorder = ''
    onefile = ''
    hdu = 0
    !
    do jorder=1,tup%n
      if (tup%trans(jorder)%morder.ne.morder)  cycle
      if (location.eq.code_buffer_memory .and. tup%trans(jorder)%mtstamp.gt.0) then
        cubedag_tuple_hasuptodatefile = .true.
        iorder = tup%trans(jorder)%iorder
        return
      endif
      if (location.eq.code_buffer_disk .and. tup%trans(jorder)%dtstamp.gt.0) then
        cubedag_tuple_hasuptodatefile = .true.
        iorder  = tup%trans(jorder)%iorder
        onefile = tup%trans(jorder)%file
        hdu     = tup%trans(jorder)%hdu
        return
      endif
    enddo
    !
    ! CONSISTENCY checks not reimplemented
    !
    !if (access.eq.code_cube_unkset) then
    !  ! Consistency has no much meaning
    !  cubedag_tuple_hasuptodatefile = tstamp.gt.0
    !else
    !  ! Check for consistent file between image set and spectrum set
    !  ttstamp = tup%tstamp(cubedag_tuple_position(cubetools_transpose_access(access),location))
    !  ctstamp = tup%tstamp(cubedag_tuple_position(code_null,code_null))
    !  if (tstamp.le.0) then
    !    ! File does not exist
    !    cubedag_tuple_hasuptodatefile = .false.
    !  elseif (ttstamp.le.0) then
    !    ! Transposed file does not exist: no consistency issue
    !    cubedag_tuple_hasuptodatefile = .true.
    !  elseif (tstamp.le.ctstamp .and. ttstamp.le.ctstamp) then
    !    ! Files are consistent
    !    cubedag_tuple_hasuptodatefile = .true.
    !  elseif (ttstamp.le.tstamp) then
    !    ! Files are not consistent, and the requested one is the newest
    !    cubedag_tuple_hasuptodatefile = .true.
    !  else
    !    ! Files are not consistent, and the transposed one is the newest
    !    cubedag_tuple_hasuptodatefile = .false.
    !  endif
    !endif
  end function cubedag_tuple_hasuptodatefile
  !
  subroutine cubedag_tuple_list(tup,id,family,flag,error)
    use cubetools_format
    use cubedag_parameters
    use cubetools_flag_types
    !---------------------------------------------------------------------
    ! List the tuple contents
    !---------------------------------------------------------------------
    class(cubedag_tuple_t), intent(inout) :: tup
    integer(kind=iden_l),   intent(in)    :: id
    character(len=*),       intent(in)    :: family
    type(flag_list_t),      intent(in)    :: flag
    logical,                intent(inout) :: error
    ! Local
    logical :: consistent
    ! integer(kind=4) :: cpos,ipos,spos
    integer(kind=4) :: lstrflag,iorder
    character(len=128) :: strflag
    !integer(kind=8) :: ctstamp,itstamp,ststamp
    character(len=file_l) :: name
    character(len=32) :: status
    character(len=mess_l) :: mess
    character(len=*), parameter :: rname='TUPLE>LIST'
    !
    ! *** JP Why a listing needs to update the content of the tuple? To be
    ! *** sure JP sur to be uptodate? If yes, this should be stated in the
    ! *** header of the subroutine.
    call cubedag_tuple_diskupdate(tup,error)
    if (error)  return
    !
    ! CONSISTENCY NOT REIMPLEMENTED
    !cpos = cubedag_tuple_position(code_null,code_null)
    !spos = cubedag_tuple_position(code_cube_speset,code_buffer_disk)
    !ipos = cubedag_tuple_position(code_cube_imaset,code_buffer_disk)
    !ctstamp = tup%tstamp(cpos)
    !ststamp = tup%tstamp(spos)
    !itstamp = tup%tstamp(ipos)
    !
    ! Consistency
    !consistent = .false.
    !if (ststamp.le.0 .or. itstamp.le.0) then
    !  status = 'INCOMPLETE'
    !elseif (ststamp.le.ctstamp .and. itstamp.le.ctstamp) then
    !  status = 'CONSISTENT'
    !  consistent = .true.
    !else
    !  status = 'INCONSISTENT'
    !endif
    status = 'CONSISTENT'
    consistent = .true.
    !
    call flag%repr(strflag=strflag,lstrflag=lstrflag,error=error)
    if (error)  return
    write(name,'(i0,2x,3a)') id,trim(family),":",strflag(1:lstrflag)
    !
    mess = cubetools_format_stdkey_boldval('Identifier',name,60)
    mess = trim(mess)//'  '//cubetools_format_stdkey_boldval('Status',status,18)
    call cubedag_message(seve%r,rname,mess)
    !
    do iorder=1,tup%n
      call tup%trans(iorder)%list(error)
      if (error) return ! ?
    enddo
    !
    call cubedag_message(seve%r,rname,'')
  end subroutine cubedag_tuple_list
  !
  function cubedag_tuple_position(tup,iorder)
    !-------------------------------------------------------------------
    ! Find in the tuple the named transposition according to its order.
    !-------------------------------------------------------------------
    integer(kind=4) :: cubedag_tuple_position  ! Function value on return
    class(cubedag_tuple_t), intent(in) :: tup
    character(len=maxdim),  intent(in) :: iorder
    !
    integer(kind=4) :: jorder
    character(len=*), parameter :: rname='TUPLE>POSITION'
    !
    cubedag_tuple_position = 0
    do jorder=1,tup%n  ! This loop is unefficient, but 99% of the time we
                       ! have only 1 or 2 transpositions in the tuple!
      if (tup%trans(jorder)%iorder.eq.iorder) then
        cubedag_tuple_position = jorder
        return
      endif
    enddo
  end function cubedag_tuple_position
  !
  function cubedag_tuple_hascube(tup,order,location)
    !-------------------------------------------------------------------
    ! Return true if the tuple provides the order+location cube
    !-------------------------------------------------------------------
    logical :: cubedag_tuple_hascube
    class(cubedag_tuple_t), intent(in) :: tup
    character(len=*),       intent(in) :: order
    integer(kind=code_k),   intent(in) :: location
    !
    integer(kind=4) :: pos
    !
    cubedag_tuple_hascube = .false.
    !
    pos = tup%position(order)
    if (pos.eq.0)  return
    !
    if (location.eq.code_buffer_memory) then
      cubedag_tuple_hascube = tup%trans(pos)%mtstamp.gt.0
    else
      cubedag_tuple_hascube = tup%trans(pos)%dtstamp.gt.0
    endif
  end function cubedag_tuple_hascube
  !
  function cubedag_tuple_access(tup,order)
    !-------------------------------------------------------------------
    ! Return the access code of the cube referenced by the tuple
    !-------------------------------------------------------------------
    integer(kind=code_k) :: cubedag_tuple_access
    class(cubedag_tuple_t), intent(in)  :: tup
    character(len=*),       intent(in)  :: order
    !
    integer(kind=4) :: pos
    !
    pos = tup%position(order)
    if (pos.eq.0) then
      cubedag_tuple_access = code_cube_unkset
    else
      cubedag_tuple_access = tup%trans(pos)%access()
    endif
  end function cubedag_tuple_access
  !
  function cubedag_tuple_filename(tup,order,location)
    !-------------------------------------------------------------------
    ! Return the file name for access+location cube
    !-------------------------------------------------------------------
    character(len=file_l) :: cubedag_tuple_filename
    class(cubedag_tuple_t), intent(in) :: tup
    character(len=*),       intent(in) :: order
    integer(kind=code_k),   intent(in) :: location
    !
    integer(kind=4) :: pos
    !
    pos = tup%position(order)
    if (pos.eq.0) then
      cubedag_tuple_filename = '<not-yet-defined>'
    else
      cubedag_tuple_filename = tup%trans(pos)%filename(location)
    endif
  end function cubedag_tuple_filename
  !
  subroutine cubedag_tuple_location(tup,order,location,shortloc)
    !-------------------------------------------------------------------
    ! Return the location of the cube referenced by the tuple,
    ! in a string suited for LIST (i.e. properly truncating it)
    !-------------------------------------------------------------------
    class(cubedag_tuple_t), intent(in)  :: tup
    character(len=*),       intent(in)  :: order
    integer(kind=code_k),   intent(in)  :: location
    character(len=*),       intent(out) :: shortloc
    !
    integer(kind=4) :: pos
    !
    pos = tup%position(order)
    if (pos.eq.0) then
      shortloc = 'none'
    else
      call tup%trans(pos)%location(location,shortloc)
    endif
  end subroutine cubedag_tuple_location

  function cubedag_tuple_disksize(tup,order,location)
    !-------------------------------------------------------------------
    ! Return the file name for access+location cube
    !-------------------------------------------------------------------
    integer(kind=size_length) :: cubedag_tuple_disksize
    class(cubedag_tuple_t), intent(in) :: tup
    character(len=*),       intent(in) :: order
    integer(kind=code_k),   intent(in) :: location
    !
    integer(kind=4) :: pos
    !
    pos = tup%position(order)
    if (pos.eq.0) then
      cubedag_tuple_disksize = 0
    else
      cubedag_tuple_disksize = tup%trans(pos)%disksize(location)
    endif
  end function cubedag_tuple_disksize

  subroutine cubedag_tuple_rmmemo(tup,error)
    !-------------------------------------------------------------------
    ! Unreference the memory buffers for all orders
    !-------------------------------------------------------------------
    type(cubedag_tuple_t), intent(inout) :: tup
    logical,               intent(inout) :: error
    !
    integer(kind=4) :: iorder
    !
    do iorder=1,tup%n
      if (tup%trans(iorder)%mtstamp.le.0)  cycle  ! Not used
      !
      tup%trans(iorder)%mtstamp = 0
    enddo
  end subroutine cubedag_tuple_rmmemo

  subroutine cubedag_tuple_rmfiles(tup,error)
    !-------------------------------------------------------------------
    ! Remove all files on disk for the given tuple
    !-------------------------------------------------------------------
    type(cubedag_tuple_t), intent(inout) :: tup
    logical,               intent(inout) :: error
    !
    integer(kind=4) :: iorder
    !
    do iorder=1,tup%n
      if (tup%trans(iorder)%dtstamp.le.0)  cycle  ! Not used
      !
      call gag_filrm(tup%trans(iorder)%file)
      tup%trans(iorder)%dtstamp = 0
      tup%trans(iorder)%file    = ''
      tup%trans(iorder)%hdu     = 0
    enddo
  end subroutine cubedag_tuple_rmfiles
  !
!   subroutine cubedag_tuple_debug(tup)
!     class(cubedag_tuple_t), intent(in) :: tup
!     ! Local
!     integer(kind=4) :: i
!     do i=1,tuple_mmemo
!       write(stdout,form_tup)  'TUPLE',tup%code(i),tup%tstamp(i),trim(tup%file(i)),tup%hdu(i)
!     enddo
!   end subroutine cubedag_tuple_debug
  !
  function cubedag_tuple_disksizes(tup)
    !-------------------------------------------------------------------
    ! Return the disk footprint of ALL the files referenced by the tuple
    !-------------------------------------------------------------------
    integer(kind=size_length) :: cubedag_tuple_disksizes  ! [bytes]
    class(cubedag_tuple_t), intent(in) :: tup
    !
    integer(kind=4) :: iorder
    !
    cubedag_tuple_disksizes = 0
    do iorder=1,tup%n
      cubedag_tuple_disksizes = cubedag_tuple_disksizes + &
        tup%trans(iorder)%disksize(code_buffer_disk)
    enddo
  end function cubedag_tuple_disksizes

  function cubedag_tuple_contains(tup,file,hdu)
    !-------------------------------------------------------------------
    ! Return .true. is the tuple references the named file and hdu
    !-------------------------------------------------------------------
    logical :: cubedag_tuple_contains
    class(cubedag_tuple_t), intent(in) :: tup
    character(len=*),       intent(in) :: file
    integer(kind=4),        intent(in) :: hdu
    !
    integer(kind=4) :: iorder
    logical :: samefile,samehdu
    !
    cubedag_tuple_contains = .false.
    do iorder=1,tup%n
      if (tup%trans(iorder)%dtstamp.le.0)  cycle
      !
      samefile = gag_filsame(tup%trans(iorder)%file,file)
      samehdu  = tup%trans(iorder)%hdu.eq.hdu
      if (samefile.and.samehdu) then
        cubedag_tuple_contains = .true.
        return
      endif
    enddo
  end function cubedag_tuple_contains

  subroutine cubedag_tuple_to_struct(tup,userspace,error)
    use cubetools_userspace
    use cubetools_userstruct
    !-------------------------------------------------------------------
    !
    !-------------------------------------------------------------------
    class(cubedag_tuple_t), intent(in)    :: tup
    class(userspace_t),     intent(inout) :: userspace
    logical,                intent(inout) :: error
    !
    integer(kind=4) :: jorder
    logical :: imamemo,spememo
    character(len=file_l) :: imafile,spefile
    integer(kind=4) :: imahdu,spehdu
    type(userstruct_t), pointer :: struct
    type(userstruct_t) :: imastruct,spestruct
    character(len=*), parameter :: rname='TUPLE>TOSTRUCT'
    !
    call cubedag_message(seve%t,rname,'Welcome')
    !
    ! Search for imaset and speset values
    imamemo = .false.
    imafile = ''
    imahdu = 0
    spememo = .false.
    spefile = ''
    spehdu = 0
    do jorder=1,tup%n
      if (tup%trans(jorder)%morder.eq.code_cube_imaset) then
        if (tup%trans(jorder)%mtstamp.gt.0) then
          imamemo = .true.
        endif
        if (tup%trans(jorder)%dtstamp.gt.0) then
          imafile = tup%trans(jorder)%file
          imahdu = tup%trans(jorder)%hdu
        endif
      endif
      if (tup%trans(jorder)%morder.eq.code_cube_speset) then
        if (tup%trans(jorder)%mtstamp.gt.0) then
          spememo = .true.
        endif
        if (tup%trans(jorder)%dtstamp.gt.0) then
          spefile = tup%trans(jorder)%file
          spehdu = tup%trans(jorder)%hdu
        endif
      endif
    enddo
    !
    ! Main structure
    struct => cubetools_userstruct_ptr(userspace,error)
    if (error)  return
    call struct%def(error)
    if (error) return
    !
    ! Image set
    call struct%def_substruct('imaset',imastruct,error)
    if (error) return
    call imastruct%set_member('memory',imamemo,error)
    if (error) return
    call imastruct%set_member('file',imafile,error)
    if (error) return
    call imastruct%set_member('hdu',imahdu,error)
    if (error) return
    !
    ! Spectrum set
    call struct%def_substruct('speset',spestruct,error)
    if (error) return
    call spestruct%set_member('memory',spememo,error)
    if (error) return
    call spestruct%set_member('file',spefile,error)
    if (error) return
    call spestruct%set_member('hdu',spehdu,error)
    if (error) return
  end subroutine cubedag_tuple_to_struct

  subroutine cubedag_tuple_iterate_init(tup)
    !-------------------------------------------------------------------
    ! (Re)initialize the tuple iterator
    !-------------------------------------------------------------------
    class(cubedag_tuple_t), intent(inout) :: tup
    !
    tup%itrans = 0
  end subroutine cubedag_tuple_iterate_init
  !
  function cubedag_tuple_iterate(tup,iorder)
    !-------------------------------------------------------------------
    ! Iterate the tuple iterator
    !-------------------------------------------------------------------
    logical :: cubedag_tuple_iterate
    class(cubedag_tuple_t), intent(inout) :: tup
    character(len=*),       intent(out)   :: iorder
    !
    if (tup%itrans.ge.tup%n) then
      cubedag_tuple_iterate = .false.
      iorder = ''
    else
      cubedag_tuple_iterate = .true.
      tup%itrans = tup%itrans+1
      iorder = tup%trans(tup%itrans)%iorder
    endif
  end function cubedag_tuple_iterate
end module cubedag_tuple
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
