!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
function eclass_offset_eq(x1,x2,y1,y2,t1,t2)
  use mrtcal_buffers
  !---------------------------------------------------------------------
  ! @ private
  ! Equality routine between two pairs of offset (real*8) with a
  ! tolerance ruled by MSET CALIBRATION MATCH
  !---------------------------------------------------------------------
  logical :: eclass_offset_eq
  real(kind=8),     intent(in) :: x1,x2  ! [rad]
  real(kind=8),     intent(in) :: y1,y2  ! [rad]
  character(len=*), intent(in) :: t1,t2  ! Offset type (track, onTheFly, ...)
  !
  eclass_offset_eq = abs(x1-x2).le.rsetup%cal%match .and.  &
                     abs(y1-y2).le.rsetup%cal%match .and.  &
                     t1.eq.t2
  !
end function eclass_offset_eq
!
subroutine mrtcal_subscan_list_build(switchmode,sublist,imbf,error)
  use mrtcal_messaging
  use phys_const
  use gkernel_types
  use imbfits_types
  use mrtcal_dependencies_interfaces
  use mrtcal_interfaces, except_this=>mrtcal_subscan_list_build
  use mrtcal_buffers
  use mrtcal_calib_types
  use mrtcal_bookkeeping_types
  !---------------------------------------------------------------------
  ! @ private
  ! 1. Class the subscans of the current scan in TRACKED or OTF, ON or OFF
  ! 2. Associate the median offset, median MJD, and the integration time
  !---------------------------------------------------------------------
  integer(kind=4),      intent(in)    :: switchmode
  type(subscan_list_t), intent(inout) :: sublist
  type(imbfits_t),      intent(inout) :: imbf
  logical,              intent(inout) :: error
  ! Local
  integer(kind=4), parameter :: type_length = 80
  real(kind=8), allocatable :: r8buf(:)
  integer(kind=4), allocatable :: i4buf(:),key(:)
  character(len=type_length), allocatable :: chbuf(:)
  character(len=24), allocatable :: ch24buf(:)
  logical, allocatable :: lbuf(:)
  integer(kind=4) :: ier,isub,nsub,iequ
  type(range_t) :: time
  type(eclass_2dble1char_t) :: offset_eclass
  type(imbfits_subscan_t) :: subscan
  character(len=message_length) :: mess
  character(len=*), parameter :: rname='SUBSCAN>LIST>BUILD'
  !
  call mrtcal_message(seve%t,rname,'Welcome')
  !
  ! Sanity check + allocation
  nsub = imbf%primary%n_obsp%val
  if (nsub.eq.0) then
     call mrtcal_message(seve%e,rname,'No available subscan')
     error = .true.
     return
  endif
  call reallocate_subscan_list(nsub,sublist,error)
  if (error) return
  !
  ! Check that only one offset position is associated to each subscan and
  ! make the list of these subscan position
  do isub=1,nsub
     subscan%isub = isub
     ! Read IMBF-antslow hdu and get on-track time range
     call imbfits_read_header_antslow(imbf,isub,rsetup%cal%antslowmjdshift,subscan%antslow,error)
     if (error)  goto 100
     ! Read IMBF-backendXXX for subscan%backdata%head%desc%naxis2%val
     ! Compress bad time dumps, so that 100% bad tables are detected as empty
     call imbfits_read_header_backdata(imbf,isub,.true.,subscan%backdata,error)
     if (error)  goto 100
     call mrtcal_get_time_range_for_antslow(subscan,time,error)
     if (error)  goto 100
     !
     sublist%isub(isub) = isub
     sublist%mjd(isub) = sum(subscan%antslow%table%mjd%val(time%first:time%last))/time%n ! *** JP maybe a median would be better here
     sublist%time(isub) = subscan%antslow%head%substime%val
     sublist%empty(isub) = subscan%backdata%head%desc%naxis2%val.le.0
     sublist%scanning%val(isub) = subscan%antslow%head%substype%val
     !
     if (subscan%antslow%head%substype%val.eq.'track') then
        ! Search for the equivalent classes of offset positions for each subscan
        call reallocate_eclass_2dble1char(offset_eclass,time%n,error)
        if (error) goto 100
        offset_eclass%val1(1:time%n) = subscan%antslow%table%longoff%val(time%first:time%last)
        offset_eclass%val2(1:time%n) = subscan%antslow%table%latoff%val(time%first:time%last)
        offset_eclass%val3(1:time%n) = subscan%antslow%head%substype%val
        offset_eclass%cnt(1:time%n) = 1
        call eclass_2dble1char(eclass_offset_eq,offset_eclass)
        ! Only 1 position expected per subscan in tracked psw
        if (offset_eclass%nequ.ne.1) then
           write(mess,'(A,I0)')  'Subscan #',isub
           call mrtcal_subscan_list_error(trim(mess),offset_eclass,time%n,'traces',"1")
           error = .true.
           goto 100
        endif
        ! List the subscan positions
        sublist%onoff%val1(isub) = offset_eclass%val1(1)
        sublist%onoff%val2(isub) = offset_eclass%val2(1)
        sublist%onoff%val3(isub) = offset_eclass%val3(1)
     else if (subscan%antslow%head%substype%val.eq.'onTheFly') then
        ! Set arbitrary (but identical from 1 onTheFly subscan to another)
        ! values. The comparison function will distinguish track from onTheFly
        ! by comparing the subscan type.
        sublist%onoff%val1(isub) = 0.0
        sublist%onoff%val2(isub) = 0.0
        sublist%onoff%val3(isub) = subscan%antslow%head%substype%val
     else if (subscan%antslow%head%substype%val.eq.'onFocus') then
        ! Code is duplicated from the onTheFly section because it's unclear
        ! whether some modifications will be needed
        sublist%onoff%val1(isub) = 0.0
        sublist%onoff%val2(isub) = 0.0
        sublist%onoff%val3(isub) = subscan%antslow%head%substype%val        
     else 
        call mrtcal_message(seve%e,rname,'Unknown substype: '//subscan%antslow%head%substype%val)
        error = .true.
        goto 100
     endif
  enddo ! isub
  !
  ! Sort by increasing time (just a precaution...)
  allocate(key(nsub),r8buf(nsub),i4buf(nsub),chbuf(nsub),ch24buf(nsub),lbuf(nsub),stat=ier)
  if (failed_allocate(rname,'Sorting key and buffers',ier,error)) return
  call gr8_trie(sublist%mjd,key,nsub,error)
  if (error) then
     deallocate(key,r8buf,i4buf,chbuf,ch24buf,lbuf)
     return
  endif
  call gr8_sort(sublist%mjd,r8buf,key,nsub)
  call gr8_sort(sublist%time,r8buf,key,nsub)
  call gi4_sort(sublist%isub,i4buf,key,nsub)
  call gl_sort (sublist%empty,lbuf,key,nsub)
  call gr8_sort(sublist%onoff%val1,r8buf,key,nsub)
  call gr8_sort(sublist%onoff%val2,r8buf,key,nsub)
  call gch_sort(sublist%onoff%val3,ch24buf,key,24,nsub)
  call gch_sort(sublist%scanning%val,chbuf,key,type_length,nsub)
  deallocate(key,r8buf,i4buf,chbuf,ch24buf,lbuf)
  !
  ! Search for the equivalent classes of the subscan types
  call eclass_char(eclass_char_eq,sublist%scanning)
  if (sublist%scanning%nequ.eq.1 .and. sublist%scanning%val(1).eq.'onTheFly') then
    sublist%tracked = 0
    sublist%otf = 1
    sublist%focus = 0
  else if (sublist%scanning%nequ.eq.1 .and. sublist%scanning%val(1).eq.'onFocus') then
    sublist%tracked = 0
    sublist%otf = 0
    sublist%focus = 1
  else if (sublist%scanning%nequ.ge.1 .and. sublist%scanning%val(1).eq.'track') then
    sublist%tracked = 1
    sublist%otf = 2
    sublist%focus = 0
  else if (sublist%scanning%nequ.ge.2 .and. sublist%scanning%val(2).eq.'track') then
    sublist%tracked = 2
    sublist%otf = 1
    sublist%focus = 0
  else
    call mrtcal_message(seve%e,rname,'Could not figure out TRACKED vs OTF classes')
    write(mess,'(I0,A)') sublist%scanning%nequ,' subscan type(s) found:'
    call mrtcal_message(seve%e,rname,mess)
    do iequ=1,sublist%scanning%nequ
      write(mess,'(A,I0,A,A,A,I0,A)')  '  #',iequ,': ',  &
        trim(sublist%scanning%val(iequ)),' (',sublist%scanning%cnt(iequ),' subscans)'
      call mrtcal_message(seve%e,rname,mess)
    enddo
    error = .true.
    goto 100
  endif
  !
  ! Search for the equivalent classes of the subscan positions
  call eclass_2dble1char(eclass_offset_eq,sublist%onoff)
  select case (switchmode)
  case (switchmode_pos)
     if (sublist%onoff%nequ.ne.2) then
        call mrtcal_subscan_list_error("Scan",sublist%onoff,nsub,'subscans',"2: ON+OFF")
        error = .true.
        goto 100
     endif
     ! Identifies classes as on and off classes. The first one is always
     ! assumed to be an OFF in IMBFITS v2...
     sublist%off = 1
     sublist%on  = 2
  case (switchmode_wob)
     if (sublist%onoff%nequ.ne.1 .and. sublist%onoff%nequ.ne.2) then
        call mrtcal_subscan_list_error("Scan",sublist%onoff,nsub,'subscans',"1 or 2")
        error = .true.
        goto 100
     endif
  case (switchmode_fre,switchmode_bea)
     if (sublist%onoff%nequ.ne.1) then
        call mrtcal_subscan_list_error("Scan",sublist%onoff,nsub,'subscans',"1")
        error = .true.
        goto 100
     endif
     ! Only ON subscans in frequency or beam switching!
     sublist%on  = 1
     sublist%off = 0
  case default
     call mrtcal_message(seve%e,rname,trim(imbf%scan%head%swtchmod%val)//' not supported')
     error = .true.
     goto 100
  end select
  !
100 continue
  call imbfits_free_header_antslow(subscan%antslow,error)
  call imbfits_free_header_backdata(subscan%backdata,error)
  call free_eclass_2dble1char(offset_eclass,error)
  !
contains
  subroutine mrtcal_subscan_list_error(what,eclass,nsource,source,expected)
    use mrtcal_messaging
    character(len=*),          intent(in) :: what
    type(eclass_2dble1char_t), intent(in) :: eclass
    integer(kind=4),           intent(in) :: nsource
    character(len=*),          intent(in) :: source
    character(len=*),          intent(in) :: expected
    ! Local
    character(len=message_length) :: mess
    !
    write(mess,'(2A,I0,A,I0,1X,4A)')  what,' has ',eclass%nequ,  &
      ' positions through ',nsource,source,' (expected ',expected,'):'
    call mrtcal_message(seve%e,rname,mess)
    do iequ=1,eclass%nequ
      write(mess,'(3X,A,I0,A,f0.8,2x,f0.8)')  &
        'Position #',iequ,': ',eclass%val1(iequ)*sec_per_rad,eclass%val2(iequ)*sec_per_rad
      call mrtcal_message(seve%e,rname,mess)
    enddo ! iequ
  end subroutine mrtcal_subscan_list_error
  !
end subroutine mrtcal_subscan_list_build
!
subroutine mrtcal_subscan_list_print(switchmode,sublist,error)
  use mrtcal_messaging
  use phys_const
  use mrtcal_calib_types
  use mrtcal_interfaces, except_this=>mrtcal_subscan_list_print
  !---------------------------------------------------------------------
  ! @ private
  ! List useful information about the subscans of the current scan
  !---------------------------------------------------------------------
  integer(kind=4),      intent(in)    :: switchmode
  type(subscan_list_t), intent(in)    :: sublist
  logical,              intent(inout) :: error
  ! Local
  integer(kind=4) :: isub
  character(len=7) :: equclass
  character(len=4) :: subscan_kind
  character(len=message_length) :: mess
  character(len=*), parameter :: rname='SUBSCAN>LIST>PRINT'
  !
  call mrtcal_message(seve%t,rname,'Welcome')
  !
  if (sublist%nsub.eq.0) then
     call mrtcal_message(seve%r,rname,'No available subscan')
  else
     write(mess,'(A)') '#     ObsType           Offsets          MJD         IntTime'
     call mrtcal_message(seve%r,rname,mess)
     do isub=1,sublist%nsub
        if (sublist%scanning%bak(isub).eq.sublist%tracked) then
           equclass = 'TRACKED'
        else if (sublist%scanning%bak(isub).eq.sublist%otf) then
           equclass = 'OTF    '
        else if (sublist%scanning%bak(isub).eq.sublist%focus) then
           equclass = 'Focus  '
        else
           call mrtcal_message(seve%e,rname,'Unknown equivalent class')
           error = .true.
           return
        endif
        select case (switchmode)
        case (switchmode_pos)
           if (sublist%onoff%bak(isub).eq.sublist%on) then
              subscan_kind = 'ON '
           else if (sublist%onoff%bak(isub).eq.sublist%off) then
              subscan_kind = 'OFF'
           else
              call mrtcal_message(seve%e,rname,'Unknown equivalent class')
              error = .true.
              return
           endif
        case (switchmode_wob)
           write(subscan_kind,'(A,I1)') 'WSW',sublist%onoff%bak(isub)
        case (switchmode_fre)
           write(subscan_kind,'(A)') 'FSW'
        case (switchmode_bea)
           write(subscan_kind,'(A)') 'BSW'
        case default
           call mrtcal_message(seve%e,rname,'Unsupported switched mode') ! *** JP Having the name of the switched mode would be good...
           error = .true.
           return
        end select
        write(mess,'(I2,2x,A,1x,A,2x,2F8.1,2x,F0.8,2x,F0.6)')  &
             isub,equclass,subscan_kind,&
             sublist%onoff%val1(isub)*sec_per_rad,&
             sublist%onoff%val2(isub)*sec_per_rad,&
             sublist%mjd(isub),&
             sublist%time(isub)
        call mrtcal_message(seve%r,rname,mess)
     enddo ! isub
  endif
  !
end subroutine mrtcal_subscan_list_print
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
subroutine mrtcal_switch_cycle_init(front,backdata,antslow,sublist,cycle,error)
  use phys_const
  use mrtcal_messaging
  use mrtcal_interfaces, except_this=>mrtcal_switch_cycle_init
  use imbfits_types
  use mrtcal_calib_types
  use mrtcal_bookkeeping_types
  !---------------------------------------------------------------------
  ! @ private
  ! Note that the frequency offset in frequency switching may depend on the
  ! frontend. This imposes that the off pointer in the switch_desc_t has 
  ! noffset x nfront dimensions, and not just noffset.
  !
  ! In wobbler switching, check that offsets are given in horizontalTrue.
  ! If true, then use the sign of the longitude offset to determine the ON and OFF
  ! phases. Indeed, the backdata%head%phaseone%val value is only indicating
  ! whether the phase corresponds to the wobbler LEFT or RIGHT, not whether
  ! it is ON or OFF (even though the value names still are ON or OFF). Yes,
  ! I know: This is confusing...
  !
  ! In frequency switching, try to use backdata%head%phaseone%val to 
  ! decide who is ON and who is OFF
  !---------------------------------------------------------------------
  type(imbfits_front_t),    intent(in)    :: front
  type(imbfits_backdata_t), intent(in)    :: backdata
  type(imbfits_antslow_t),  intent(in)    :: antslow
  type(subscan_list_t),     intent(in)    :: sublist
  type(switch_cycle_t),     intent(inout) :: cycle
  logical,                  intent(inout) :: error
  !
  integer(kind=4), parameter :: ilam = 1
  integer(kind=4), parameter :: ibet = 2
  integer(kind=4) :: ifront,nfront,npha,ndump,non,noff
  real(kind=4) :: dummy
  character(len=*), parameter :: rname='PHASE>SWITCH>CYCLE>INIT'
  !
  call mrtcal_message(seve%t,rname,'Welcome')
  !
  ndump = backdata%head%nphases%val
  nfront = front%table%frqoff1%n ! *** JP some test needed about the consistency with frqoff2?
  select case (cycle%mode)
  case (switchmode_pos)
     if (ndump.ne.1) then
        call mrtcal_message(seve%e,rname,'Position switching should only have one dump per dumpcycle')
        error = .true.
        return
     endif
     non  = 1
     noff = 1
     npha = 2 ! Convention: One ON and one OFF (they can be the average of several subscans)
     call reallocate_switch_cycle(ndump,npha,non,noff,nfront,cycle,error)
     if (error) return
     cycle%on%i(1)  = 1 ! Convention
     cycle%off%i(1) = 2 ! Convention
     cycle%desc(cycle%on%i(1))%wei(:)  = +1
     cycle%desc(cycle%off%i(1))%wei(:) = -1
     cycle%desc(cycle%on%i(1))%off(ilam,:) = sublist%onoff%val1(sublist%on)
     cycle%desc(cycle%on%i(1))%off(ibet,:) = sublist%onoff%val2(sublist%on)
     cycle%desc(cycle%off%i(1))%off(ilam,:) = sublist%onoff%val1(sublist%off)
     cycle%desc(cycle%off%i(1))%off(ibet,:) = sublist%onoff%val2(sublist%off)
  case (switchmode_wob)
     if (antslow%head%systemof%val.ne.'horizontalTrue') then
        call mrtcal_message(seve%e,rname,'Wobbler switching offsets are not given in the True Horizontal frame')
        error = .true.
        return
     endif
     non  = ndump/2
     noff = ndump/2
     npha = ndump
     call reallocate_switch_cycle(ndump,npha,non,noff,nfront,cycle,error)
     if (error) return
     call fill_switch_on_and_off_indexes(sublist%onoff%val1(sublist%on).lt.0,cycle)
     cycle%desc(cycle%on%i(1))%wei(:)  = +1
     cycle%desc(cycle%off%i(1))%wei(:) = -1
     cycle%desc(cycle%on%i(1))%off(:,:) = 0 ! It's a convention that the ON offsets are 0 valued.
     cycle%desc(cycle%off%i(1))%off(ilam,:) = sublist%onoff%val1(sublist%on)
     cycle%desc(cycle%off%i(1))%off(ibet,:) = sublist%onoff%val2(sublist%on)
  case (switchmode_fre)
     non  = ndump/2
     noff = ndump/2
     npha = ndump
     call reallocate_switch_cycle(ndump,npha,non,noff,nfront,cycle,error)
     if (error) return
     call fill_switch_on_and_off_indexes(backdata%head%phaseone%val.eq.'ON',cycle)
     do ifront=1,nfront
        ! Both weights are now +1. This comes from the fact the products
        ! which are folded are different now.
        ! In the past we used to do:
        !   F = SomeGain * (PHASE1-PHASE2)
        ! F was the usual unfolded spectrum, providing one positive and
        ! one negative shifted phases. Folding was done with:
        !   FOLDED = (+F(+shift) - F(-shift))/2
        ! which realigned the 2 phases with the shift and made them positives
        ! with the +1 and -1 weights.
        !
        ! Now we compute 2 unfolded spectra:
        !   F1 = (PHASE1-PHASE2)/PHASE2
        !   F2 = (PHASE2-PHASE1)/PHASE1
        ! In this case, PHASE1 is positive in F1, and PHASE2 is positive in F2.
        ! There is no more need to inverse F1 or F2 at folding time:
        !   FOLDED = (F1(+shift) + F2(-shift))/2
        cycle%desc(cycle%on%i(1))%wei(ifront)  = +1
        cycle%desc(cycle%off%i(1))%wei(ifront) = +1
        ! CLASS needs the frequencies in MHz
        ! But the frequency offset is in simple precision in IMBFITS while
        ! CLASS asks for a double precision. It's simpler for the end user
        ! to see -7.10000000000000 (the human double representation of a
        ! simple 7.1) than 7.10000004619360 (the correct double binary
        ! representation of a simple 7.1). NCS is passing 7.1 in a XML
        ! ASCII field. So we don't know what NCS actually does. The
        ! following trick is is thus mostly a cosmetic one.
        dummy = front%table%frqoff1%val(ifront)*1e9
        cycle%desc(cycle%on%i(1))%off(:,ifront)  = dummy*1d-6
        dummy = front%table%frqoff2%val(ifront)*1e9
        cycle%desc(cycle%off%i(1))%off(:,ifront) = dummy*1d-6
     enddo ! ifront
  case (switchmode_bea)
     non  = ndump/2
     noff = ndump/2
     npha = ndump
     call reallocate_switch_cycle(ndump,npha,non,noff,nfront,cycle,error)
     if (error) return
     call fill_switch_on_and_off_indexes(backdata%head%phaseone%val.eq.'ON',cycle)
     cycle%desc(cycle%on%i(1))%wei(:)  = +1
     cycle%desc(cycle%off%i(1))%wei(:) = -1
     cycle%desc(cycle%on%i(1))%off(:,:) = 0 ! It's a convention that the ON offsets are 0 valued.
     cycle%desc(cycle%off%i(1))%off(ilam,:) = sublist%onoff%val1(sublist%on)  ! ZZZ These are not filled properly
     cycle%desc(cycle%off%i(1))%off(ibet,:) = sublist%onoff%val2(sublist%on)  ! ZZZ These are not filled properly
  case default
     call mrtcal_message(seve%e,rname,'Unknown switch mode')
     error = .true.
     return
  end select
  !
contains
  !
  subroutine fill_switch_on_and_off_indexes(firstison,cycle)
    !---------------------------------------------------------------------
    ! If firstison = .true., then on%i = (1,3,5,...) and off%i = (2,4,6,...)
    !                        then on%i = (2,4,6,...) and off%i = (1,3,5,...)
    !---------------------------------------------------------------------
    logical,              intent(in) :: firstison
    type(switch_cycle_t), intent(inout) :: cycle
    !
    integer(kind=4) :: i
    !
    if (firstison) then
       cycle%on%i(1:non)   = (/ (1+(i-1)*2, i=1,non)  /)
       cycle%off%i(1:noff) = (/ (2+(i-1)*2, i=1,noff) /)
    else
       cycle%on%i(1:non)   = (/ (2+(i-1)*2, i=1,noff) /)
       cycle%off%i(1:noff) = (/ (1+(i-1)*2, i=1,non)  /)
    endif
  end subroutine fill_switch_on_and_off_indexes
end subroutine mrtcal_switch_cycle_init
!
subroutine mrtcal_switch_cycle_list(cycle,error)
  use mrtcal_messaging
  use mrtcal_interfaces, except_this=>mrtcal_switch_cycle_list
  use mrtcal_calib_types
  !---------------------------------------------------------------------
  ! @ private
  ! List useful information about the switch cycle of the current subscan
  !---------------------------------------------------------------------
  type(switch_cycle_t), intent(in)    :: cycle
  logical,              intent(inout) :: error
  ! Local
  integer(kind=4) :: ifront,ipha
  character(len=message_length) :: mess
  character(len=*), parameter :: rname='SWITCH>CYCLE>LIST'
  !
  call mrtcal_message(seve%t,rname,'Welcome')
  !
  select case (cycle%mode)
  case (switchmode_pos)
     ! Print something?
  case (switchmode_wob)
     ! Print another thing?
  case (switchmode_bea)
     ! Print still another thing?
  case (switchmode_fre)
     if (cycle%npha.eq.0) then
        call mrtcal_message(seve%w,rname,'No phase found')
     else
        write(mess,'(2x,a)') 'Phase  Weight  Front  Freq. offset'
        call mrtcal_message(seve%r,rname,mess)
        do ipha=1,cycle%npha
           if (cycle%desc(ipha)%nfront.eq.0) then
              write(mess,'(2x,a)') 'No front end for this phase'
              call mrtcal_message(seve%w,rname,mess)
           else
              do ifront=1,cycle%desc(ipha)%nfront
                 write(mess,'(2x,I5,f6.1,4x,I5,2x,f12.3)') &
                      ipha,cycle%desc(ipha)%wei(ifront), &
                      ifront, cycle%desc(ipha)%off(1,ifront)
                 call mrtcal_message(seve%r,rname,mess)
              enddo ! ipha
           endif
        enddo ! ifront
     endif
  case default
     call mrtcal_message(seve%e,rname,'Unknown switch mode')
     error = .true.
     return
  end select
  !
end subroutine mrtcal_switch_cycle_list
!
subroutine mrtcal_switch_book_list(book,error)
  use mrtcal_interfaces, except_this=>mrtcal_switch_book_list
  use mrtcal_messaging
  use mrtcal_calib_types
  !---------------------------------------------------------------------
  ! @ private
  !---------------------------------------------------------------------
  type(switch_book_t), intent(in)    :: book
  logical,             intent(inout) :: error
  ! Local
  character(len=message_length) :: mess
  character(len=*), parameter :: rname='PHASE>SWITCH>BOOK>LIST'
  !
  call mrtcal_message(seve%t,rname,'Welcome')
  !
  ! book%idump is used as loop iterator => its value is the number of 
  ! actually checked dumps plus one!
  write(mess,'(2x,A,I0,A,I0,A)') &
       "Checked ",book%idump-1," dumps (on ",book%ndump," on-track dumps) " 
  call mrtcal_message(mseve%calib%book,rname,mess)
  write(mess,'(2x,A,I0,A,I0,A)') &
       "Rejected ",book%norphan," dumps (on ",book%ndump," on-track dumps) " 
  call mrtcal_message(mseve%calib%book,rname,mess)
  write(mess,'(2x,A,I0,A,I0,A)') &
       "Read ",book%ncycle," complete cycles over ",book%ndump/book%nphase," potential cycles"
  call mrtcal_message(mseve%calib%book,rname,mess)
  !
end subroutine mrtcal_switch_book_list
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
subroutine mrtcal_iterate_subscancycle(mrtset,imbf,subscanbuf,  &
  backcal,backsci,error)
  use mrtcal_messaging
  use mrtcal_interfaces, except_this=>mrtcal_iterate_subscancycle
  use mrtcal_buffer_types
  use mrtcal_setup_types
  !---------------------------------------------------------------------
  ! @ private
  !---------------------------------------------------------------------
  type(mrtcal_setup_t),    intent(in)    :: mrtset      !
  type(imbfits_t),         intent(in)    :: imbf        !
  type(subscan_buffer_t),  intent(inout) :: subscanbuf  !
  type(calib_backend_t),   intent(in)    :: backcal     !
  type(science_backend_t), intent(inout) :: backsci     !
  logical,                 intent(inout) :: error       !
  ! Local
  character(len=*), parameter :: rname='ITERATE>SUBSCANCYCLE'
  ! Cycle descriptions
  integer(kind=4), parameter :: msub=4        ! Max number of subscans per cycle
  integer(kind=4), parameter :: mcycle=3      ! Number of known cycles
  integer(kind=4), parameter :: mmode=2       ! Mode 1: detection, mode 2: calibration
  type :: cycle_t
    integer(kind=4)  :: nsub        ! Number of subscans per cycle
    integer(kind=4)  :: desc(msub)  ! Cycle description
    character(len=4) :: name        ! Human friendly cycle name
  end type cycle_t
  type(cycle_t) :: det(mcycle),cal(mcycle)  ! Detection, calibration
  ! Other
  type(chunkset_3d_t) :: cumul  ! Cumulation structure in case of SCAN accumulation
  integer(kind=4) :: isub,ifirst,jcycle,ncycle
  logical :: found,initcumul
  character(len=message_length) :: mess
  !
  ! --- Cycle descriptions ---
  ! ROOR (detection ROOR)
  det(1)%nsub    = 4
  det(1)%desc(:) = (/ backsci%list%off, backsci%list%on, backsci%list%on, backsci%list%off /)
  det(1)%name    = 'ROOR'
  ! ROOR (calibration ROOR)
  cal(1)%nsub    = 4
  cal(1)%desc(:) = (/ backsci%list%off, backsci%list%on, backsci%list%on, backsci%list%off /)
  cal(1)%name    = 'ROOR'
  ! RORO (detection RORO)
  det(2)%nsub    = 4
  det(2)%desc(:) = (/ backsci%list%off, backsci%list%on, backsci%list%off, backsci%list%on /)
  det(2)%name    = 'RORO'
  ! RORO (calibration RO)
  cal(2)%nsub    = 2
  cal(2)%desc(:) = (/ backsci%list%off, backsci%list%on, 0, 0 /)
  cal(2)%name    = 'RO'
  ! RO (fallback only, detection RO)
  det(3)%nsub    = 2
  det(3)%desc(:) = (/ backsci%list%off, backsci%list%on, 0, 0 /)
  det(3)%name    = 'RO'
  ! RO (fallback only, calibration RO)
  cal(3)%nsub    = 2
  cal(3)%desc(:) = (/ backsci%list%off, backsci%list%on, 0, 0 /)
  cal(3)%name    = 'RO'
  !
  ! --- Detection ---
  call detect_cycles(det,mcycle,jcycle,error)
  if (error)  return
  !
  ! --- Calibration ---
  !
  isub = 0
  ifirst = 1-cal(jcycle)%nsub
  ncycle = 0
  initcumul = .true.
  do while (isub.lt.backsci%list%nsub)
    !
    ! Get next cycle
    call mrtcal_find_next_subscancycle(backsci%list,cal(jcycle)%desc,  &
      cal(jcycle)%nsub,.true.,found,ifirst,isub,ncycle,error)
    if (error)  goto 10
    if (.not.found)  cycle
    !
    ! Produce ON-OFF for this cycle
    call mrtcal_calib_tracked_psw_cycle(mrtset,imbf,ifirst,cal(jcycle)%nsub,  &
      subscanbuf,backcal,backsci,error)
    if (error)  goto 10
    !
    ! If requested, accumulate this result
    if (mrtset%out%accmode.eq.accmode_scan) then
      if (initcumul) then
        call mrtcal_chunkset_3d_accumulate_init(backsci%diff,cumul,error)
        if (error) goto 10
        initcumul = .false.
      endif
      ! backsci%diff has no weight yet. Give it some:
      call mrtcal_chunkset_3d_accumulate_setweight(backsci%diff,  &
        backsci%tscale,mrtset%out%weight,error)
      if (error)  goto 10
      call mrtcal_chunkset_3d_accumulate_do(backsci%diff,cumul,error)
      if (error)  goto 10
    endif
    !
  enddo
  !
  if (mrtset%out%accmode.eq.accmode_scan) then
    call mrtcal_write_toclass(cumul,mrtset%out,backsci%nobs,error)
    if (error)  goto 10
  endif
  !
  write(mess,'(A,I0,1X,A,A)')  'Calibrated ',ncycle,cal(jcycle)%name,' cycles'
  call mrtcal_message(seve%i,rname,mess)
  !
10 continue
  ! Cleaning
  call cumul%free(error)
  if (error)  continue
  !
contains
  subroutine detect_cycles(cycl,mcycl,found,error)
    use mrtcal_messaging
    !-------------------------------------------------------------------
    ! Detect which of the known cycles if used in the scan
    !-------------------------------------------------------------------
    integer(kind=4), intent(in)    :: mcycl
    type(cycle_t),   intent(in)    :: cycl(mcycl)
    integer(kind=4), intent(out)   :: found
    logical,         intent(inout) :: error
    ! Local
    integer(kind=4) :: ncycl(mcycl),icycl
    !
    ncycl(:) = 0
    found = 0
    ! Try first ROOR and RORO
    do icycl=1,mcycl-1  ! Ignore last one for now
      call count_cycles(cycl(icycl),ncycl(icycl),error)
      if (error)  return
    enddo
    ! Which one is found?
    if (all(ncycl(1:mcycl-1).gt.0)) then
      call mrtcal_message(seve%e,rname,'Ambiguous subscan cycles')
      error = .true.
      return
    endif
    if (all(ncycl(1:mcycl-1).eq.0)) then
      ! Neither ROOR nor RORO were found. Try RO as last chance.
      call count_cycles(cycl(mcycl),ncycl(mcycl),error)  ! Try last one now
      if (error)  return
    endif
    do icycl=1,mcycl
      if (ncycl(icycl).gt.0) then
        found = icycl
        exit
      endif
    enddo
    if (found.eq.0) then
      call mrtcal_message(seve%e,rname,'No supported RO cycles found')
      error = .true.
      return
    endif
    write(mess,'(A,I0,1X,A,A)')  'Detected ',ncycl(found),cycl(found)%name,' cycles'
    call mrtcal_message(seve%i,rname,mess)
    !
  end subroutine detect_cycles
  !
  subroutine count_cycles(cycl,ncycle,error)
    !-------------------------------------------------------------------
    ! Count the number of subscan cycles
    !-------------------------------------------------------------------
    type(cycle_t),   intent(in)    :: cycl
    integer(kind=4), intent(out)   :: ncycle
    logical,         intent(inout) :: error
    ! Local
    integer(kind=4) :: isub,ifirst
    logical :: found
    !
    isub = 0
    ifirst = 1-cycl%nsub
    ncycle = 0
    do while (isub.lt.backsci%list%nsub)
      call mrtcal_find_next_subscancycle(backsci%list,cycl%desc,  &
        cycl%nsub,.false.,found,ifirst,isub,ncycle,error)
      if (error)  return
    enddo
  end subroutine count_cycles
  !
end subroutine mrtcal_iterate_subscancycle
!
subroutine mrtcal_find_next_subscancycle(sublist,swcycle,nswitch,  &
  verbose,found,ifirst,isub,ncycle,error)
  use imbfits_types
  use mrtcal_messaging
  use mrtcal_calib_types
  use mrtcal_interfaces, except_this=>mrtcal_find_next_subscancycle
  !---------------------------------------------------------------------
  ! @ private
  !---------------------------------------------------------------------
  type(subscan_list_t), intent(in)    :: sublist
  integer(kind=4),      intent(in)    :: nswitch
  integer(kind=4),      intent(in)    :: swcycle(nswitch)
  logical,              intent(in)    :: verbose
  logical,              intent(out)   :: found
  integer(kind=4),      intent(inout) :: ifirst
  integer(kind=4),      intent(inout) :: isub
  integer(kind=4),      intent(inout) :: ncycle
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='FIND>NEXT>SUBSCANCYCLE'
  logical :: notyetwarned,match
  integer(kind=4) :: iswitch
  character(len=message_length) :: mess
  !
  call mrtcal_message(seve%t,rname,'Welcome')
  !
  ! Iterate internal counters
  isub = isub+1
  ifirst = ifirst+nswitch
  ! Initialize next cycle search
  found = .false.
  iswitch = 1
  notyetwarned = .true.
  !
  do
     ! Incomplete last / exit condition
     if (isub.gt.sublist%nsub) then
        if (iswitch.gt.1) then
           ! That's just a warning, *not* an error
           if (verbose)  &
             call mrtcal_message(seve%w,rname,'Incomplete last switch cycle')
        endif
        found = .false.
        return
     endif
     !
     ! Check switch kind
     match = sublist%onoff%bak(isub).eq.swcycle(iswitch)
     !
     if (match) then
        notyetwarned = .true.
        ! Expected switch number
        if (iswitch.eq.nswitch) then
          ! Completed a full cycle
          found = .true.
          ncycle = ncycle+1
          if (verbose) then
            write(mess,'(A,I0)') 'Found next cycle starting at ',ifirst
            call mrtcal_message(seve%i,rname,mess)
          endif
          return
        else
          ! Not yet a full cycle => Increment iswitch and subscan counters
          iswitch = iswitch+1
          isub = isub+1
        endif
     else
        ! Unexpected switch kind => Warn only once while searching for a new start of a complete cycle
        if (verbose) then
          if (notyetwarned) then
            write(mess,'(A,I0,A)') 'Incomplete or inconsistent switch cycle starting at ',  &
                  ifirst,' => Searching for start of next cycle'
            call mrtcal_message(seve%w,rname,mess)
            notyetwarned = .false.
          endif
          write(mess,'(A,I0,A,I0,A,I0,A)') 'Subscan #',isub,&
              ' has wrong switch type (',&
              sublist%onoff%bak(isub),' vs ',swcycle(iswitch),')'
          call mrtcal_message(seve%w,rname,mess)
        endif
        if (iswitch.gt.1) then
          ! We were in the middle of a cycle
          !   => Reset switch number to find start of next cycle
          ! The current subscan could be the start of this cycle
          !   => Do not increase the position counter in the subscan list
          iswitch = 1
        else
          ! Already searching for start of cycle
          !   => Just increase the position counter in the subscan list
          isub = isub+1
          ! norphan = norphan+1
        endif
        ifirst = isub
     endif ! Is the switch kind the expected one?
  enddo ! Infinite loop on subscans
  !
end subroutine mrtcal_find_next_subscancycle
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
