!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
module uv_cal_mod
  use gbl_message
  use image_def
  use gkernel_interfaces
  !
  public :: uv_cal_command
  private
  !
  type uv_daps_t
     integer(kind=4) :: iori = 0 ! Origin indice before sorting
     !
     real(kind=4)    :: u = 0
     real(kind=4)    :: v = 0
     integer(kind=4) :: iant = 0
     integer(kind=4) :: jant = 0
     integer(kind=4) :: antmin = 0
     integer(kind=4) :: antmax = 0
     integer(kind=4) :: date = 0.0
     real(kind=4)    :: time = 0.0
     !
     integer(kind=4), allocatable :: idx(:)
     integer(kind=4), allocatable :: rdx(:)
     real(kind=8),    allocatable :: key(:)
   contains
     procedure :: allocate => daps_allocate
     procedure :: get      => daps_get
  end type uv_daps_t
  !
  type uv_data_t
     integer(kind=4)           :: ncol,nchan,nvisi   ! Size of the UV buffer
     real(kind=4), pointer     :: uv(:,:) => null()  ! [ncol,nvisi] Pointer to UV buffer
     logical                   :: usevisifreq        ! Which frequency array to use?
     ! Columns which are simple pointers to UV buffer
     ! TBD
     ! Columns which can not be pointers (modified information)
     real(kind=8), allocatable :: chanfreq(:)        ! [nchan] Frequency at each channel
     real(kind=8), allocatable :: visifreq(:)        ! [nvisi] Ref. frequency for each visibility
  contains
     procedure :: get  => uv_data_get
     procedure :: copy => uv_data_copy
     procedure :: put  => uv_data_put
     procedure :: freq => uv_data_freq
  end type uv_data_t
  !
  type uv_atom_t
     complex(kind=4) :: val
     real(kind=4)    :: wei
   contains
     procedure, public :: getfrom    => uv_atom_getfrom
     procedure, public :: putinto    => uv_atom_putinto
     procedure, public :: apply_gain => uv_atom_apply_gain
  end type uv_atom_t
  !
contains
  !
  !------------------------------------------------------------------------
  !
  subroutine daps_allocate(daps,nvisi,error)
    !----------------------------------------------------------------------
    ! ***JP: Should be CUBE array types to avoid writing specific code here
    !----------------------------------------------------------------------
    class(uv_daps_t), intent(inout) :: daps
    integer(kind=4),  intent(in)    :: nvisi
    logical,          intent(inout) :: error
    !
    integer(kind=4) :: ier
    character(len=*), parameter :: rname='DAPS>ALLOCATE'
    !
    if (nvisi.le.0) then
       call map_message(seve%e,rname,'Zero or negative number of visibilities')
       error = .true.
       return
    endif
    allocate(daps%idx(nvisi),daps%rdx(nvisi),daps%key(nvisi),stat=ier)
    if (failed_allocate(rname,'daps',ier,error)) return
  end subroutine daps_allocate
  !
  subroutine daps_get(daps,uvt,icur)
    !----------------------------------------------------------------------
    ! 
    !----------------------------------------------------------------------
    class(uv_daps_t), intent(inout) :: daps
    type(uv_data_t),  intent(in)    :: uvt
    integer(kind=4),  intent(in)    :: icur
    !
    integer(kind=4), parameter :: ucol    = 1  ! hx%gil%column_pointer(code_uvt_u)
    integer(kind=4), parameter :: vcol    = 2  ! hx%gil%column_pointer(code_uvt_v)
    integer(kind=4), parameter :: datecol = 4  ! hx%gil%column_pointer(code_uvt_date)
    integer(kind=4), parameter :: timecol = 5  ! hx%gil%column_pointer(code_uvt_time)
    integer(kind=4), parameter :: anticol = 6  ! hx%gil%column_pointer(code_uvt_anti)
    integer(kind=4), parameter :: antjcol = 7  ! hx%gil%column_pointer(code_uvt_antj)
    !
    daps%iori = daps%idx(icur)
    daps%u = uvt%uv(ucol,daps%iori)
    daps%v = uvt%uv(vcol,daps%iori)
    daps%iant = nint(uvt%uv(anticol,daps%iori))
    daps%jant = nint(uvt%uv(antjcol,daps%iori))
    if (daps%iant.lt.daps%jant) then
       daps%antmin = daps%iant
       daps%antmax = daps%jant
    else
       daps%antmin = daps%jant
       daps%antmax = daps%iant
    endif
    daps%date = nint(uvt%uv(datecol,daps%iori))
    daps%time = uvt%uv(timecol,daps%iori)
  end subroutine daps_get
  !
  !------------------------------------------------------------------------
  !
  subroutine uv_data_get(uvd,uvt,nvisi,init,error)
    !----------------------------------------------------------------------
    ! Get a r2d UV buffer into a uv_data_t and compute the 'freq'
    ! column
    ! Note 1: the uvt%r2d buffer may contain only a subset of all the
    !         visibilities.
    ! Note 2: the uvt%r2d buffer may be larger than the actual nvisi to
    !         be used.
    !----------------------------------------------------------------------
    class(uv_data_t), intent(inout) :: uvd
    type(gildas),     intent(in)    :: uvt
    integer(kind=8),  intent(in)    :: nvisi
    logical,          intent(in)    :: init  ! Getting old or new data which needs init?
    logical,          intent(inout) :: error
    !
    integer(kind=4) :: ier,cfreq
    integer(kind=8) :: ichan,iv
    logical :: offfreqs
    character(len=*), parameter :: rname='UV>DATA>GET'
    !
    ! Pointer to UV data buffer
    uvd%ncol  = uvt%gil%dim(1)
    uvd%nchan = uvt%gil%nchan
    uvd%nvisi = nvisi
    uvd%uv => uvt%r2d(1:uvd%ncol,1:uvd%nvisi)
    if (init) then
      ! uvd%uv(:,:) = 0.  ! Useless, will be overwritten when filling visibilities
    endif
    !
    ! Compute the array of spectral channel frequencies
    if (allocated(uvd%chanfreq))  deallocate(uvd%chanfreq)
    allocate(uvd%chanfreq(uvd%nchan),stat=ier)
    if (failed_allocate(rname,'chan freq',ier,error)) return
    do ichan=1,uvd%nchan
       uvd%chanfreq(ichan) = gdf_uv_frequency(uvt,real(ichan,kind=8))
    enddo ! ichan
    !
    ! Create the reference frequency column (1 per visibility), either from
    ! the offset frequency DAPS column or from scratch.
    if (allocated(uvd%visifreq))  deallocate(uvd%visifreq)
    allocate(uvd%visifreq(uvd%nvisi),stat=ier)
    if (failed_allocate(rname,'freq array',ier,error)) return
    cfreq = uvt%gil%column_pointer(code_uvt_freq)
    offfreqs = .false.  ! Offset frequencies are not relevant
    if (init .or. cfreq.eq.0) then
      ! Fallback: header frequency is used for all visibilities
      uvd%visifreq(:) = uvt%gil%val(uvt%gil%faxi)
    else
      ! Use offset frequencies (but relevant only if non-zero)
      do iv=1,uvd%nvisi
        if (uvd%uv(cfreq,iv).ne.0.0)  offfreqs = .true.
        uvd%visifreq(iv) = uvt%gil%val(uvt%gil%faxi) + uvd%uv(cfreq,iv)  ! From offset freq to absolute freq.
      enddo
    endif
    !
    ! Guess which of the 'chanfreq' or 'visifreq' is to be used (this
    ! basically means guessing if facing a continuum or line table).
    if (offfreqs .and. uvd%nchan.gt.1) then
      call map_message(seve%e,rname,  &
        'Not implemented: line table with non-zero frequency column in DAPS')
      error = .true.
      return
    endif
    ! Note that line tables can have only one channel. The right test is
    ! the following one since line tables are never expected to provide
    ! a non-zero frequency column in DAPS.
    uvd%usevisifreq = offfreqs
  end subroutine uv_data_get
  !
  function uv_data_freq(uvd,ichan,ivisi)
    !-------------------------------------------------------------------
    ! Return the frequency associated to the given channel and given
    ! visibility.
    !-------------------------------------------------------------------
    real(kind=8) :: uv_data_freq
    class(uv_data_t), intent(in) :: uvd
    integer(kind=4),  intent(in) :: ichan
    integer(kind=4),  intent(in) :: ivisi
    if (uvd%usevisifreq) then
      uv_data_freq = uvd%visifreq(ivisi)
    else
      uv_data_freq = uvd%chanfreq(ichan)
    endif
  end function uv_data_freq
  !
  subroutine uv_data_copy(in,ou,error)
    !----------------------------------------------------------------------
    !
    !----------------------------------------------------------------------
    class(uv_data_t), intent(in)    :: in
    type(uv_data_t),  intent(inout) :: ou
    logical,          intent(inout) :: error
    !
    character(len=*), parameter :: rname='UV>DATA>COPY'
    !
    ! Sanity check
    if (in%nvisi.ne.ou%nvisi) then
      call map_message(seve%e,rname,'Incompatible number of visibilities')
      error = .true.
      return
    endif
    if (in%nchan.ne.ou%nchan) then
      call map_message(seve%e,rname,'Incompatible number of visibilities')
      error = .true.
      return
    endif
    ! DAPS:
    !  - the leading DAPS are assumed to be compatible
    !  - the input trailing DAPS are assumed to be present and
    !    compatible with the output first trailing DAPS. But there can
    !    be more trailing DAPS as output (e.g. the FREQ column which could
    !    have been added in prepare_uvt_as)
    if (ou%ncol.lt.in%ncol) then
      call map_message(seve%e,rname,'Incompatible number of columns')
      error = .true.
      return
    endif
    !
    ! Now copy:
    ou%uv(1:in%ncol,:) = in%uv(1:in%ncol,:)
    ou%visifreq(:)     = in%visifreq(:)
  end subroutine uv_data_copy
  !
  subroutine uv_data_put(uvd,uvt,error)
    !----------------------------------------------------------------------
    ! Put a uv_data_t to a r2d UV buffer and compute the 'freq'
    ! column
    ! Note 1: the uvt%r2d buffer may contain only a subset of all the
    !         visibilities.
    ! Note 2: the uvt%r2d buffer may be larger than the actual nvisi to
    !         be used.
    !----------------------------------------------------------------------
    class(uv_data_t), intent(inout) :: uvd
    type(gildas),     intent(in)    :: uvt
    logical,          intent(inout) :: error
    !
    integer(kind=4) :: cfreq
    integer(kind=8) :: iv
    character(len=*), parameter :: rname='UV>DATA>PUT'
    !
    ! I) From uvd%uv to uvt%r2d:
    !  => nothing to be done thanks to one pointing to the other
    !
    ! II) Frequency column
    cfreq = uvt%gil%column_pointer(code_uvt_freq)
    if (cfreq.le.0) then
      call map_message(seve%e,rname,'Internal error: no frequency DAPS column')
      error = .true.
      return
    endif
    !
    do iv=1,uvd%nvisi
      uvt%r2d(cfreq,iv) = uvd%visifreq(iv)-uvt%gil%val(uvt%gil%faxi)
    enddo
  end subroutine uv_data_put
  !
  !------------------------------------------------------------------------
  !
  subroutine uv_atom_getfrom(atom,data,ivis,ichan)
    !-----------------------------------------------------------------
    !
    !-----------------------------------------------------------------
    class(uv_atom_t), intent(inout) :: atom
    type(uv_data_t),  intent(in)    :: data
    integer(kind=4),  intent(in)    :: ivis
    integer(kind=4),  intent(in)    :: ichan
    !
    integer(kind=4) :: icol
    !
    icol = 3*(ichan-1)+8
    atom%val = cmplx(data%uv(icol,ivis),data%uv(icol+1,ivis))
    atom%wei = data%uv(icol+2,ivis)
  end subroutine uv_atom_getfrom
  !
  subroutine uv_atom_putinto(atom,data,ivis,ichan)
    !-----------------------------------------------------------------
    !
    !-----------------------------------------------------------------
    class(uv_atom_t), intent(in)    :: atom
    type(uv_data_t),  intent(inout) :: data
    integer(kind=4),  intent(in)    :: ivis
    integer(kind=4),  intent(in)    :: ichan
    !
    integer(kind=4) :: icol
    !
    icol = 3*(ichan-1)+8
    data%uv(icol,  ivis) = real(atom%val)
    data%uv(icol+1,ivis) = aimag(atom%val)
    data%uv(icol+2,ivis) = atom%wei
  end subroutine uv_atom_putinto
  !
  subroutine uv_atom_apply_gain(cal,raw,zgain)
    !-----------------------------------------------------------------
    !
    !-----------------------------------------------------------------
    class(uv_atom_t), intent(inout) :: cal
    type(uv_atom_t),  intent(in)    :: raw
    complex(kind=4),  intent(in)    :: zgain
    !
    cal%val = raw%val/zgain
    cal%wei = raw%wei*abs(zgain)**2
  end subroutine uv_atom_apply_gain
  !
  !------------------------------------------------------------------------
  !
  subroutine uv_cal_command(error)
    !----------------------------------------------------------------------
    ! Apply gains from a table to another table.
    !----------------------------------------------------------------------
    logical, intent(inout) :: error
    !
    type(gildas) :: raw,cal,gain
    logical :: doflag
    real(kind=8) :: phafactor,ampfactor
    integer(kind=4) :: ier,ncol,ivisi,mvisi,lvisi
    character(len=filename_length) :: rawname,gainname,calname
    character(len=message_length) :: mess
    type(uv_data_t) :: uvg,uvd,uvc
    character(len=*), parameter :: rname='UV>CAL>COMMAND'
    !
    ! Parse input
    call gildas_open
    call gildas_char('UVDATA$',rawname)
    call gildas_char('UVGAIN$',gainname)
    call gildas_char('UVCAL$',calname)
    call gildas_dble('AMPLI_GAIN$',ampfactor,1)
    call gildas_dble('PHASE_GAIN$',phafactor,1)
    call gildas_logi('FLAG$',doflag,1)
    call gildas_close
    !
    ! Prepare input and output
    call get_uvt(raw,'input',rawname,error)
    if (error) goto 10
    call get_uvt(gain,'gain',gainname,error,uvd=uvg)
    if (error) goto 10
    call prepare_uvt_as(cal,'calibrated',calname,raw,error)
    if (error) goto 10
    !
    ncol = raw%gil%dim(1)
    !
    ! Define the number of visibilities (mvisi) to be treated by block on
    ! the input data assuming that it is the largest one
    call gdf_nitems('SPACE_GILDAS',mvisi,ncol)
    mvisi = min(mvisi,raw%gil%nvisi)
    ! Allocate the associated space
    allocate(raw%r2d(raw%gil%dim(1),mvisi),cal%r2d(cal%gil%dim(1),mvisi),stat=ier)
    if (failed_allocate(rname,'uv table',ier,error)) goto 10
    !
    ! Loop over block of visibilities of the raw/cal table
    raw%blc = 0
    raw%trc = 0
    cal%blc = 0
    cal%trc = 0
    do ivisi=1,raw%gil%nvisi,mvisi
       write(mess,*) ivisi,' / ',raw%gil%nvisi,mvisi
       call map_message(seve%d,rname,mess)
       !
       ! Read data
       raw%blc(2) = ivisi
       raw%trc(2) = min(raw%gil%nvisi,ivisi-1+mvisi)
       cal%blc = raw%blc
       cal%trc = raw%trc
       call gdf_read_data(raw,raw%r2d,error)
       if (error) goto 10
       lvisi = raw%trc(2)-raw%blc(2)+1
       call uvd%get(raw,int(lvisi,kind=8),.false.,error)
       if (error) goto 10
       call uvc%get(cal,int(lvisi,kind=8),.true.,error)
       if (error) goto 10
       !
       ! Calibrate
       call uv_cal_main(uvd,uvc,uvg,ampfactor,phafactor,doflag,error)
       if (error) goto 10
       !
       ! Write data
       call uvc%put(cal,error)
       if (error) goto 10
       call gdf_write_data(cal,cal%r2d,error)
       if (error) goto 10
    enddo ! ivisi
    !
    ! Clean
10  call gdf_close_image(cal,error)
    call gdf_close_image(raw,error)
    if (associated(raw%r2d))  deallocate(raw%r2d)
    if (associated(cal%r2d))  deallocate(cal%r2d)
    if (associated(gain%r2d)) deallocate(gain%r2d)
    !
  contains
    !
    subroutine get_uvt(uvt,kind,name,error,uvd)
      type(gildas),              intent(inout) :: uvt
      character(len=*),          intent(in)    :: kind
      character(len=*),          intent(in)    :: name
      logical,                   intent(inout) :: error
      type(uv_data_t), optional, intent(inout) :: uvd
      !
      integer(kind=4) :: n
      character(len=filename_length) :: shortname
      !
      n = len_trim(name)
      if (n.le.0) return
      shortname = name(1:n)
      call gildas_null(uvt,type='UVT')
      call gdf_read_gildas(uvt,shortname,'.uvt',error,data=present(uvd))
      if (error) then
         call map_message(seve%e,rname,'Cannot read '//trim(kind)//'uv table')
         return
      endif
      !
      if (.not.present(uvd))  return
      call uvd%get(uvt,uvt%gil%nvisi,.false.,error)
      if (error)  return
    end subroutine get_uvt
    !
    subroutine prepare_uvt_as(ou,kind,name,in,error)
      type(gildas),     intent(inout) :: ou
      character(len=*), intent(in)    :: kind
      character(len=*), intent(in)    :: name
      type(gildas),     intent(in)    :: in
      logical,          intent(inout) :: error 
      !
      integer(kind=4) :: n
      character(len=filename_length) :: shortname
      !
      ! Set up header
      call gildas_null(ou,type='UVT')
      call gdf_copy_header(in,ou,error)
      if (error) return
      if (ou%gil%column_pointer(code_uvt_freq).gt.0) then
        ! Frequency column already present => nothing to do
      else
        call map_message(seve%i,rname,'Adding frequency offset column in DAPS')
        ! hcuv%gil%ntrail = huv%gil%ntrail+1  ! Not needed (automatic?)
        ou%gil%dim(1) = ou%gil%dim(1)+1
        ou%gil%column_pointer(code_uvt_freq) = ou%gil%dim(1)
        ou%gil%column_size(code_uvt_freq) = 1
      endif
      !
      ! Set up file
      n = len_trim(name)
      if (n.eq.0) return
      shortname = name(1:n)
      call sic_parsef(shortname,ou%file,' ','.uvt')
      call map_message(seve%i,rname,'Creating '//trim(kind)//' uv table '//trim(ou%file))
      call gdf_create_image(ou,error)
      if (error) then
         call map_message(seve%e,rname,'Cannot create '//trim(kind)//' uv table')
         return
      endif
    end subroutine prepare_uvt_as
  end subroutine uv_cal_command
  !
  subroutine uv_cal_main(raw,cal,gain,ampfactor,phafactor,doflag,error)
    use gkernel_interfaces
    use uv_sort, only: uv_findtb
    use uv_sort_codes
    !---------------------------------------------------------------------
    ! Apply phase and/or amplitude calibration to the raw uv table.  Phase
    ! and amplitudes are stored in the gain uv table, and usually come from
    ! a previous use of task UV_GAIN. This subroutine allows to apply the
    ! corrections to a spectral line table, whatever the way the gains were
    ! computed before.
    !
    ! The antenna numbers must match those of the gain table.  There is no
    ! specific verification about this.
    !
    ! ***JP: For the moment, we are sorting the gain table several times
    ! ***JP: when the problem does not fit in memory!
    !---------------------------------------------------------------------
    type(uv_data_t), intent(in)    :: raw        ! Raw visibilities
    type(uv_data_t), intent(inout) :: cal        ! Calibrated visibilities
    type(uv_data_t), intent(in)    :: gain       ! Gain array
    real(kind=8),    intent(in)    :: ampfactor  ! Amplitude factor
    real(kind=8),    intent(in)    :: phafactor  ! Phase factor
    logical,         intent(in)    :: doflag     ! Flag visibilities without good gain solution?
    logical,         intent(inout) :: error
    !
    logical :: sorted,matched,outofrange,found
    logical :: flagged(cal%nvisi)
    integer(kind=4) :: cnt
    integer(kind=4) :: igainfirst,igainlast
    integer(kind=4) :: iraw,ical
    type(uv_daps_t) :: onerawdaps,onegaindaps
    character(len=message_length) :: mess
    character(len=*), parameter :: rname='UV>CAL>MAIN'
    !
    call onerawdaps%allocate(raw%nvisi,error)
    if (error) return
    call onegaindaps%allocate(gain%nvisi,error)
    if (error) return
    !
    ! Get the chronological order, for both input tables. Tables are
    ! ordered by UNIQUE-BASE then TIME (because time is a fuzzy argument,
    ! with a tolerance when matching), then FREQ (because continuum table
    ! might have been split into several visibilities for each BASE+TIME).
    ! This ensures that matches will be contiguous, and they can also be
    ! optimized.
    call uv_findtb(code_sort_ubtf,&
                   raw%uv,raw%nvisi,raw%ncol,&
                   onerawdaps%key,onerawdaps%idx,onerawdaps%rdx,sorted,&
                   error,&
                   freq=raw%visifreq)
    if (error) return
    call uv_findtb(code_sort_ubtf,&
                   gain%uv,gain%nvisi,gain%ncol,&
                   onegaindaps%key,onegaindaps%idx,onegaindaps%rdx,sorted,&
                   error,&
                   freq=gain%visifreq)
    if (error) return
    ! NB: oneraw%key and onegain%key can not be used hereafter because they can
    !     use different relative dates.
    !
    ! First setup CAL = RAW by default
    ! Leading DAPS + channels + trailing DAPS. Uncalibrated data will be kept
    ! or flagged as desired
    call raw%copy(cal,error)
    if (error)  return
    !
    ! The loop is done on calibration times, so there is no guarantee that
    ! a given data visibility is treated.  To handle this, we set a counter
    ! for each visibility, to check whether it has indeed been affected...
    flagged(1:cal%nvisi) = .true.
    igainfirst = 0
    igainlast  = 0
    !
    ! Loop on visibilities
    do iraw=1,raw%nvisi
       ! Get visibility daps
       call onerawdaps%get(raw,iraw)
       ! Check whether the current gain range matches base and datetime
       call does_current_gain_range_match(igainlast,gain,onerawdaps,onegaindaps,outofrange,matched)
       if (outofrange) then
          call map_message(seve%e,rname,'Programming error => Try to access gain range out of its range')
          error = .true.
          return
       endif
       if (.not.matched) then
          ! Find the next gain range that matches base and datetime
          call find_next_matching_gain_range(gain,onerawdaps,onegaindaps,igainfirst,igainlast,found)
          if (.not.found) then
             call map_message(seve%e,rname,'Did not succeed to match uv and gain tables')
             error = .true.
             return
          endif
       endif
       ! Now we need to apply the correct gain for each channel frequency
       call apply_gain_for_each_frequency_within_gain_range(onerawdaps,onegaindaps,gain,igainfirst,igainlast,&
            iraw,raw,cal,flagged)
    enddo ! iraw
    !
    ! Finish by checking what has not been calibrated
    if (any(flagged)) then
       cnt = 0
       if (doflag) then
          ! Uncalibrated data are nullified
          do ical=1,cal%nvisi
             if (flagged(ical)) then
                ! print *,ical  ! Too verbose...
                cnt = cnt+1
                cal%uv(8:3*raw%nchan+7,ical) = 0.0
             endif
          enddo ! ical
          write(mess,'(3(A,I0))') &
               'Flagged ',cnt,' uncalibrated visibilities (over ',cal%nvisi,')'
       else
          ! Uncalibrated data is left unmodified
          do ical=1,cal%nvisi
             if (flagged(ical)) then
                cnt = cnt+1
                ! print *,ical  ! Too verbose...
             endif
          enddo ! ical
          write(mess,'(3(A,I0))') &
               'Remaining ',cnt,' uncalibrated visibilities (over ',cal%nvisi,')'
       endif
       call map_message(seve%w,rname,mess)
    endif
    !
  contains
    !
    subroutine does_current_gain_range_match(ilast,gain,onerawdaps,onegaindaps,outofrange,matched)
      !-------------------------------------------------------------------
      ! 
      !-------------------------------------------------------------------
      integer(kind=4), intent(in)    :: ilast
      type(uv_data_t), intent(in)    :: gain
      type(uv_daps_t), intent(in)    :: onerawdaps
      type(uv_daps_t), intent(inout) :: onegaindaps
      logical,         intent(out)   :: outofrange
      logical,         intent(out)   :: matched
      !
      integer(kind=4) :: diff
      !
      if ((ilast.lt.0).or.(ilast.gt.gain%nvisi)) then
         outofrange = .true.
         matched = .false.
         return
      endif
      outofrange = .false.
      if (ilast.eq.0) then
         matched = .false.
      else
         ! Compare visibility and gain base and datetime of current range
         call onegaindaps%get(gain,ilast)
         call compare_base_datetime(onerawdaps,onegaindaps,diff)
         matched = (diff.eq.0)
      endif
    end subroutine does_current_gain_range_match
    !
    subroutine find_next_matching_gain_range(gain,onerawdaps,onegaindaps,ifirst,ilast,found)
      !-------------------------------------------------------------------
      ! 
      !-------------------------------------------------------------------
      type(uv_data_t), intent(in)    :: gain
      type(uv_daps_t), intent(in)    :: onerawdaps
      type(uv_daps_t), intent(inout) :: onegaindaps
      integer(kind=4), intent(inout) :: ifirst
      integer(kind=4), intent(inout) :: ilast
      logical,         intent(out)   :: found
      !
      integer(kind=4) :: diff
      !
      ! Initialize the search
      found = .false.
      !
      ! Search for first matching gains starting with the last gain in
      ! previous range
      ifirst = ilast
      ! Loop as long as possible
      do while (ifirst.lt.gain%nvisi)
         ! Get next gain daps
         ifirst = ifirst+1
         call onegaindaps%get(gain,ifirst)
         ! Compare visibility and gain base and datetime
         call compare_base_datetime(onerawdaps,onegaindaps,diff)
         if (diff.lt.0) then
            ! Gain is before current visibility => Cycle to restart from
            ! this ifirst next time
            cycle
         elseif (diff.gt.0) then
            ! Gain is after current visibility. No need to go further we
            ! won't find anything useful after this
            return
         else
            ! diff.eq.0
            !
            ! We found the first gain that matches exactly in terms of
            ! baseline and datetime!
            exit
         endif
      enddo ! (ifirst.lt.gain%nvisi)
      !
      ! Search for last matching gains starting with the previously found first gain
      ilast = ifirst
      ! Loop as long as possible
      do while (ilast.lt.gain%nvisi)
         ! Get next gain daps
         ilast = ilast+1
         call onegaindaps%get(gain,ilast)
         ! Compare visibility and gain base and datetime
         call compare_base_datetime(onerawdaps,onegaindaps,diff)
         if (diff.lt.0) then
            ! Gain is before current visibility => This should not happen
            return
         elseif (diff.gt.0) then
            ! Gain is after current visibility => last gain in current
            ! current range was the previous gain!
            ilast = ilast-1
            exit
         else
            ! diff.eq.0
            !
            ! We found another gain that matches exactly in terms of
            ! baseline and datetime => just cycle
            cycle
         endif
      enddo ! (ilast.lt.gain%nvisi)
      ! Found first and last gains in current range!
      found = .true.
    end subroutine find_next_matching_gain_range
    !
    subroutine apply_gain_for_each_frequency_within_gain_range(onerawdaps,onegaindaps,gain,ifirst,ilast,&
         iraw,raw,cal,flagged)
      !-------------------------------------------------------------------
      ! 
      !-------------------------------------------------------------------
      type(uv_daps_t), intent(in)    :: onerawdaps
      type(uv_daps_t), intent(inout) :: onegaindaps
      type(uv_data_t), intent(in)    :: gain
      integer(kind=4), intent(in)    :: ifirst
      integer(kind=4), intent(in)    :: ilast
      integer(kind=4), intent(in)    :: iraw
      type(uv_data_t), intent(in)    :: raw
      type(uv_data_t), intent(inout) :: cal
      logical,         intent(inout) :: flagged(:)
      !
      integer(kind=4) :: ichan,igain,bestvalid
      real(kind=8) :: dist,mindist
      complex(kind=4) :: zgain
      type(uv_atom_t) :: rawatom,calatom
      real(kind=8), parameter :: not_yet_defined=0
      ! For each channel, apply:
      ! - the nearest (in frequency) non-zero gain OR
      ! - the nearest (in frequency) gain, can be zero i.e. no gain applied
      ! Select here:
      logical, parameter :: allow_zero_gain=.true.
      !
      igain = ifirst
      !
      ! Loop over channels to search for the best valid gain in frequency
      do ichan=1,raw%nchan
         ! Reinitialize frequency distance for next channel with gain selected
         ! for previous channel
         bestvalid = not_yet_defined
         !
         ! Iterate gain table when possible
         do while (igain.le.ilast)
            ! Get this gain
            call onegaindaps%get(gain,igain)
            zgain = compute_gain(gain,onegaindaps%iori)
            if (allow_zero_gain .or. zgain.ne.0.) then
              ! Compute frequency distance and analyze it
              dist = abs(raw%freq(ichan,onerawdaps%iori)-gain%freq(1,onegaindaps%iori))
              if (bestvalid.eq.not_yet_defined) then
                bestvalid = igain
                mindist = dist
              elseif (dist.gt.mindist) then
                ! The distance in frequency is worse. Since the raw
                ! visibilities and gain arrays are also sorted by increasing
                ! frequency, this means we won't find nothing better after
                ! this => exit the "do while (igain.lt.ilast)" loop
                igain = bestvalid  ! Not always the previous one if there were zero-gains before
                call onegaindaps%get(gain,igain)
                zgain = compute_gain(gain,onegaindaps%iori)
                exit
              else
                ! Current gain is closer in frequency => Store it and
                ! iterate to see whether we find a gain at a closer
                ! frequency distance
                bestvalid = igain
                mindist = dist
              endif
            endif  ! zgain.ne.0
            igain = igain+1  ! Try next gain
         enddo ! (igain.le.gain%nvisi)
         ! A match has been found => it's onegaindaps%iori, with its zgain
         ! already computed
         !
         ! Debug
         if (.false.) then
            write(1,*) ">>> Matched raw(i,j,date,time,freq,ichan) = ",  &
                       onerawdaps%iant,onerawdaps%jant,  &
                       onerawdaps%date,onerawdaps%time,  &
                       raw%freq(ichan,onerawdaps%iori),ichan
            write(1,*) "       with gain(i,j,date,time,freq)      = ",  &
                       onegaindaps%iant,onegaindaps%jant,  &
                       onegaindaps%date,onegaindaps%time,  &
                       gain%freq(1,onegaindaps%iori),zgain
         endif
         !
         if (zgain.eq.0.0)  cycle  ! Gain can be 0 if all gains are 0 for this
                                   ! range. No gain to apply =>
                                   ! flagged(onerawdaps%iori) remains .true.
         !
         ! Apply the gain
         flagged(onerawdaps%iori) = .false.
         !***JP: This is the way it should be done
         !call rawatom%pointto(raw,onerawdaps%iori,ichan)
         !call calatom%pointto(raw,onerawdaps%iori,ichan)
         !if (onerawdaps%iant.eq.onegaindaps%jant) then
         !   call calatom%apply_gain(rawatom,conjg(zgain))
         !else
         !   call calatom%apply_gain(rawatom,zgain)
         !endif
         ! ***JP: But this is not easily possible. So copy!
         call rawatom%getfrom(raw,onerawdaps%iori,ichan)
         if (onerawdaps%iant.eq.onegaindaps%jant) then
           call calatom%apply_gain(rawatom,conjg(zgain))
         else
           call calatom%apply_gain(rawatom,zgain)
         endif
         call calatom%putinto(cal,onerawdaps%iori,ichan)
      enddo ! ichan
    end subroutine apply_gain_for_each_frequency_within_gain_range
    !
    function compute_gain(gain,ivis) result(zgain)
      !-----------------------------------------------------------------
      ! Compute the gain for the given visibility. Assume it is found
      ! in the first (unique) channel.
      !-----------------------------------------------------------------
      complex(kind=4) :: zgain
      type(uv_data_t), intent(in) :: gain  ! Gain array
      integer(kind=4), intent(in) :: ivis  !
      !
      real(kind=8) :: ampli,phase
      complex(kind=4) :: zg
      !
      ampli = sqrt(gain%uv(8,ivis)**2+gain%uv(9,ivis)**2)
      phase = atan2(gain%uv(9,ivis),gain%uv(8,ivis))
      if (gain%uv(10,ivis).le.0) ampli = 0.d0
      if (ampli.ne.0.d0) then
        ! Correct (a fraction of) the phase
        phase = phafactor*phase
        zg = cmplx(cos(phase),sin(phase))
        ampli = 1d0-ampfactor+ampli*ampfactor
        zgain = ampli*zg
      else
        ! Reset Zgain to 0
        zgain = 0.0
      endif
    end function compute_gain
    !
    subroutine compare_base_datetime(a,b,result)
      !-------------------------------------------------------------------
      ! Compare A and B visibilities according to their
      !  - first antenna, then
      !  - second antenna, then
      !  - date, then
      !  - time within tolerance
      ! Return -1 if A>B, 0 if A=B, +1 if A<B
      !-------------------------------------------------------------------
      type(uv_daps_t), intent(in)  :: a
      type(uv_daps_t), intent(in)  :: b
      integer(kind=4), intent(out) :: result
      !
      real(kind=8), parameter :: time_tol=1.0d0 ! [s] Tolerance on time
      real(kind=4) :: diff
      !
      ! Compare first antennas
      if (a%iant.gt.b%iant) then
         result = -1
         return
      elseif (a%iant.lt.b%iant) then
         result = +1
         return
      endif
      ! Compare second antennas
      if (a%jant.gt.b%jant) then
         result = -1
         return
      elseif (a%jant.lt.b%jant) then
         result = +1
         return
      endif
      ! Compare dates
      if (a%date.gt.b%date) then
         result = -1
         return
      elseif (a%date.lt.b%date) then
         result = +1
         return
      endif
      ! Compare times within tolerance
      diff = a%time-b%time
      if (diff.gt.time_tol) then
         result = -1
         return
      elseif (diff.lt.-time_tol) then
         result = +1
         return
      endif
      ! That's a match!
      result = 0
    end subroutine compare_base_datetime
  end subroutine uv_cal_main
end module uv_cal_mod
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
program uv_cal
  use gbl_message
  use gkernel_interfaces
  use uv_cal_mod
  !
  logical :: error=.false.
  character(len=*), parameter :: rname='UV>CAL'
  !
  call uv_cal_command(error)
  if (error) call sysexi(fatale)
  call map_message(seve%i,rname,'Successful completion')
end program uv_cal
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
