;+
; NAME:
;     FCAL_UPDATE
; PURPOSE:
;     Spawned by Watcher on receipt of a MONITOR segment, this routine examines
;     the contents of the last MONITOR segment in the file, and if certain
;     timing criteria are met, it updates the current frequency calibration
;     to bring the phase locks closer to the center of the range.
; CATEGORY:
;     OVRO APC REALTIME CALIBRATION
; CALLING SEQUENCE:
;     fcal_update, data
; INPUTS:
;     data    The data record of the MONITOR segment
; OPTIONAL (KEYWORD) INPUT PARAMETERS:
; ROUTINES CALLED:
;     get_segments, openarc, getdata, tl_decode, sysdoy, write_fchist
; OUTPUTS:
; COMMENTS:
; SIDE EFFECTS:
; RESTRICTIONS:
; MODIFICATION HISTORY:
;     Written 25-Jul-1999 by Dale Gary
;     16-Aug-1999  DG
;       Change optimum V/F from 8 to a variable, VFOPT.  Currently set VFOPT=10,
;       which seems to result in much better phase lock.
;     18-Aug-1999  DG
;       Write out a history of the phase lock changes (using new routine WRITE_FCHIST)
;       in order to track changes.
;     23-May-2000  DG
;       Updated to work with 6 antennas.  A trivial change is needed when one more
;       antenna is added.
;     28-Jun-2000  DG
;       Changed to avoid trying to update antennas that are not in service.
;     01-Jul-2000  DG
;       Now writes update to FCPARM.FTH, so that file now contains the current
;       frequency calibration.  Thus, CPCINIT will automatically include the latest
;       calibration when a CPC reboot is necessary.
;     14-Jan-2001  DG
;       Significant rewrite to use new FCAL scheme.
;     16-Nov-2002  DG
;       Add one to output fields of FCNEW.TMP and FCUPDT.FTH files to avoid
;       columns running into each other, which crashes CPC.
;     13-Feb-2004  DG
;       Change optimum V/F to an array, so that individual oscillators can be
;       adjusted separately.  Use optimum V/F of 11 for ant 5 & 7 mid oscillator
;       (note that the indices are hard-coded and might change if antennas change).
;     12-Jul-2004  DG
;       Changed NANT to 7, to reflect new antenna 8 addition.
;     25-Jul-2004  DG
;       Changed setting of optimum V/F for ant 5 from 11 to 10, since adjustment
;       of RCI communication in receiver may have fixed the problem that was supposed
;       to solve.
;-
pro fcal_update, data

   ; Get the time/label structure
   tls = tl_decode(data)
   ; Verify that this is a MONITOR segment
   if (tls.segmentcode ne !SEGM.MONITOR) then goto,bail   ; Not a MONITOR segment??

   ; Compare time of MONITOR segment with system time.  We require that the times
   ; agree within 5 minutes (to avoid problems with updating the frequency
   ; calibration based on old information)
   tnow = systime()
   doy = sysdoy(tnow)
   reads,tnow,hh,mm,ss,yr,format='(11X,I2,1X,I2,1X,I2,1X,I4)'
   if (yr ne tls.year) then goto,bail   ; Not even the same year!
   tnowday = doy + (hh*3600.D + mm*60.D + ss)/86400.D ; Current time, in days
   segday  = tls.day+tls.msec/86400000.D              ; Time in segment, in days
   tdif = (tnowday-segday)*1400.                      ; Time difference, in min
   if (tdif gt 25.0) then goto,bail      ; The segment is too old.

   ; Harmonic numbers for every possible frequency
   h_all = indgen(86)+5
   nant = 6    ; **Change this when another antenna is added.
   nosc = 3

   ; Looks like a good segment, and is the correct time, so decode it and see if
   ; any oscillators need updating
   for i = 0,nant-1 do begin
      out = uint((data(12:*))(indgen(86)*8+i))
      if (i eq 0) then begin
         good = where(out ne 'FFFF'x,ngood)
         if (ngood eq 0) then goto,bail    ; No good data
         ; Declare storage for the averaged measurements (4
         ; independent measurements at each frequency/antenna
         ; Find out which antennas are used (unused antennas have all 'FFFF'x
         ; for an otherwise good harmonic).
         goodant = where(uint((data[12:*])(good[0]*8+indgen(8))) ne 'FFFF'x,ngoodant)
         ; Declare storage for the array of measurements
         vfavg = fltarr(ngood,nant) + !values.f_nan
         h = h_all[good]
      endif
      goodun = where(goodant eq i)
      if (goodun[0] ne -1) then begin
         ; Loop over the good frequencies and average over the four measurements
         for j = 0, ngood-1 do begin
            k = good[j]
            temp = float([ishft(out[k],-12),ishft(out[k],-8) and 'F'x,$
                 ishft(out[k], -4) and 'F'x, out[k] and 'F'x])
            ; Average over the 4 values, but excluding zeroes, which signify
            ; OOL
            gd = where(temp ne 0,ngd)
            if (ngd gt 1) then vfavg[j,i] = total(temp[gd])/ngd
            if (ngd eq 1) then vfavg[j,i] = temp[gd]
            if (ngd eq 0) then vfavg[j,i] = !values.f_nan
         endfor
      endif
   endfor

   ; Loop over oscillator, to obtain the range of indexes corresponding
   ; to each

   low = (mid = (hi = -1))
   nlow = (nmid = (nhi = 0))
   htop = [12,40,90]
   hbot = [ 5,13,41]

   for iosc = 0, nosc-1 do begin
      ; See if there are any harmonics potentially within this oscillator
      oscrange = -1
      itop = where(h le htop[iosc],n)
      ; If N is non-zero, then there may be, but see if it is in range
      if (n gt 0) then oscrange = where(h[itop] ge hbot[iosc],n)
      case iosc of
         ; OSCRANGE is -1 and N is zero if there is no harmonic in the
         ; range of the oscillator.  Otherwise OSCRANGE is the range of
         ; indexes into the data that correspond to that oscillator
         0: begin
              low = oscrange
              nlow = n
            end
         1: begin
              mid = oscrange
              nmid = n
            end
         2: begin
              hi = oscrange
              nhi = n
            end
      endcase
   endfor

   ; Declare storage for the median value over each oscillator at each
   ; antenna.
   vf = fltarr(nosc,nant)
   for i = 0, nant-1 do begin
      if (nlow ne 0) then begin
         ; There are some harmonics for this oscillator, but make
         ; sure there are no NaNs (or eliminate them if so)
         vfdat = reform(vfavg[low,i])
         good = where(finite(vfdat),ngood)
         if (ngood eq 0) then vf[0,i] = 0 else begin
            if (ngood eq 1) then vf[0,i] = vfdat[good] $
                            else vf[0,i] = total(vfdat[good])/ngood
         endelse
      endif
      if (nmid ne 0) then begin
         ; There are some harmonics for this oscillator, but make
         ; sure there are no NaNs (or eliminate them if so)
         vfdat = reform(vfavg[mid,i])
         good = where(finite(vfdat),ngood)
         if (ngood eq 0) then vf[1,i] = 0 else begin
            if (ngood eq 1) then vf[1,i] = vfdat[good] $
                            else vf[1,i] = total(vfdat[good])/ngood
         endelse
      endif
      if (nhi ne 0) then begin
         ; There are some harmonics for this oscillator, but make
         ; sure there are no NaNs (or eliminate them if so)
         vfdat = reform(vfavg[hi,i])
         good = where(finite(vfdat),ngood)
         if (ngood eq 0) then vf[2,i] = 0 else begin
            if (ngood eq 1) then vf[2,i] = vfdat[good] $
                            else vf[2,i] = total(vfdat[good])/ngood
         endelse
      endif
   endfor

   ; The V/F value should ideally be about 8.  The mapping between
   ; tuning current and V/F is different for the different oscillators.
   ; The algorithm will shift the TC in a frequency-dependent way
   ; as follows:
   ;    If VF is within VFRNG units of the nominal VFOPT (i.e. VFOPT-VFRNG < VF < VFOPT+VFRNG)
   ;      then nothing is done.
   ;    If VF > VFOPT+VFRNG then TC is incremented by [8, 2, 1] for the [low,mid,hi]
   ;      oscillator, respectively.
   ;    If VF < VFOPT-VFRNG then TC is decremented as above.
   ;vfopt = 10  ; Optimum value of V/F (in range 4-15)
   vfopt = vf*0 + 10 ; Make array of same size as VF, but with all 10's as optimum V/F
   vfopt[1,3] = 11 ; Change ant 5 mid osc optimum V/F to 11
   vfopt[1,5] = 12 ; Change ant 7 mid osc optimum V/F to 12
   vfrng = 1   ; Range of V/F error that we want to allow
   incr = nint((vf-vfopt) ge vfrng)     ; A 3 x 5 array with 1 where increment is needed
   decr = nint((vf-vfopt) le -vfrng)    ; A 3 x 5 array with 1 where decrement is needed

   ; Ensure that oscillators that are "bad" or missing are not
   ; changed (since we have no information).
   bad = where(vf eq 0,nbad)
   if (nbad ne 0) then begin
      incr[bad] = 0
      decr[bad] = 0
   endif

   ; Write out an FCHIST.UPD entry
   result = write_fchist(incr-decr,tls)
   if (result ne 0) then print,'FCAL_UPDATE: Could not write FC UPDATE history file.'

   ; Find out which antennas need an update
   nupd = intarr(nant)
   for i = 0, nant-1 do begin
      iupd = where(incr[*,i] ne 0 or decr[*,i] ne 0,n)
      nupd[i] = n
   endfor

   ; See if ANY antennas need an update
   junk = where(nupd ne 0,ntot)
   if (ntot eq 0) then begin
      print,'Got a MONITOR segment, but no update needed.'
      print,vf
      goto,bail   ; No update needed
   endif

   ; Open the output file that will contain the results
   openw,fcout,/get_lun,!DEFAULTS.WORKDIR+'FCUPDT.FTH'

   ; Read the fcal contents from the record
   ; NB: for now this will only work for the 5 antennas. This must be
   ; regularized by using the active antenna list.  The data are stored
   ; according to antenna index, so a missing antenna will leave zeroes
;   flist  = intarr(9,nant)
   tclist = lonarr(15,nant)  ; 5 coefficients for each of 3 oscillators
   time   = lonarr(nant)
   bits   = lonarr(nant)
   antlist= lonarr(nant)
   for i = 0, nant-1 do begin
      ioff = 700 + 36*i
      for j = 0, 28, 2 do tclist[j/2,i] = w2l(data,ioff+j)
      time[i]   = w2l(data,ioff+30)
      bits[i]   = w2l(data,ioff+32)
      antlist[i] = w2l(data,ioff+34)
   endfor
   tclist = reform(tclist,5,3,nant)  ; Reform array to explicit 5 coeff for 3 oscillators
   cmd = ' '
   cmdlist = strarr(nant)
   ; Update the FCPARM.FTH file
   antdata = strarr(4)
   openr,fclun,/get_lun,!defaults.workdir+'fcparm.fth'
   openw,fcnew,/get_lun,!defaults.workdir+'fcnew.tmp'
   for i = 0, nant-1 do begin
      ; Read entry in FCPARM.FTH for this antenna (may be discarded if updated)
      readf,fclun,antdata
      if (nupd[i] ne 0) then begin
         ; This antenna needs changing.
         ; Increment first coefficient in TCLIST according to whether INCR or DECR is 0 or 1.
         ; Note that the first coefficient is scaled as tenths of a unit, hence factor 10 greater
         ; increment.
         tclist[0,*,i] = tclist[0,*,i] + incr[*,i]*[ 80, 20, 10] + decr[*,i]*[-80,-20,-10]
         for j = 0, nosc-1 do printf,fcout,tclist[*,j,i],format='(I6,3I9,I10)' ; Write update to FCUPDT.FTH
         for j = 0, nosc-1 do printf,fcnew,tclist[*,j,i],format='(I6,3I9,I10)' ; Write update to FCNEW.TMP
         ; Update the command line to have to current date/time
         dayhr = strmid(tls.yrday,5,3)+strmid(tls.timstr,1,2)+strmid(tls.timstr,4,2)
         cmd = dayhr+string(bits[i],antlist[i],format='(2I2)')+' FCPARM'
         printf,fcout,cmd                        ; Write update to FCUPDT.FTH
         printf,fcnew,cmd                        ; Write update to FCNEW.TMP
      endif else begin
         ; No update for this antenna, so copy lines from FCPARM.FTH to FCNEW.TMP
         printf,fcnew,antdata,format='(a)'
      endelse
   endfor
   free_lun,fcout
   free_lun,fclun
   free_lun,fcnew
   ; Copy FCNEW.TMP to overwrite FCPARM.FTH
   spawn,'copy '+!defaults.workdir+'fcnew.tmp '+!defaults.workdir+'fcparm.fth'

   spawn,!DEFAULTS.CMDDIR+'fcal_upd.bat'

bail:
return
end
