;+
; NAME:
;     GET_CYCLE
; PURPOSE:
;     Returns one cycle of data, and the time corresponding to the beginning
;     of the cycle.  This routine can be called repeatedly to return a
;     sequence of cycles of data, as long as the DATA array is not modified
;     between calls.
; CATEGORY:
;     OVRO APC DATA
; CALLING SEQUENCE:
;     cycle = get_cycle(a,data,currec,header,obseq,cfg,tcycle)
; INPUTS:
;     a        A file associated variable as returned from OPENARC.
;     data     Must be a named variable.  On input, DATA contains the data
;                that have been read during a previous call to GET_CYCLE().
;                On the first call, DATA should be undefined (i.e. either
;                not previously referenced, or use DELVAR,DATA before the
;                first call).
;     header   The HEADER structure corresponding to the data.
;     obseq    The OBSEQ structure corresponding to the data.
;     cfg      The CONFIG structure corresponding to the data.
;     tcycle   Must be a named variable.  On input, TCYCLE contains the
;                start time [msec] returned by a previous call to GET_CYCLE().
;                On the first call, TCYCLE may be anything, or undefined.
; OPTIONAL (KEYWORD) INPUT PARAMETERS:
; ROUTINES CALLED:
;     getdata, tl_decode
; OUTPUTS:
;     cycle    Contains one cycle of data as a simple, linear array of
;                samples of length HEADER.NWS*OBSEQ.NX, where HEADER.NWS
;                is the number of words in a sample and OBSEQ.NX is the
;                number of samples in an observing sequence.  If no more
;                data can be read due to a zero record (indicating that the
;                file is still being written), CYCLE = 0.  If no more data
;                can be read due to reading a non-data record, a partial cycle
;                of data may be returned.  If a non-data record is read,
;                the segmentcode of the record is returned.
;     data     Must be a named variable.  On output, DATA contains the data
;                that have been read during the call to GET_CYCLE(), but not
;                yet placed into the CYCLE output variable.  On the first
;                call, DATA should be undefined (i.e. either not previously
;                referenced, or use DELVAR,DATA before the first call).
;     tcycle   Must be a named variable.  On output, TCYCLE contains the
;                start time [msec] of the cycle returned in CYCLE.  If CYCLE = 0,
;                TCYCLE should be ignored.
; COMMENTS:
;     Do not change the contents of DATA or TCYCLE between calls to GET_CYCLE().
; SIDE EFFECTS:
; RESTRICTIONS:
;     This routine is going to break if the DATA segment is broken up by
;     non-data segments (e.g. Engineering segments)
; MODIFICATION HISTORY:
;     Written 12-Jan-1999 by Dale E. Gary
;     20-Jan-1999  DG
;       Added code to indicate that a zero record has been read (i.e., the file
;       is still being actively written to).  The calling routine can wait for
;       some time, then try again.
;     15-Mar-1999  DG
;       Added detection of EOS segment, in which case the routine returns a -1.
;     14-May-1999  DG
;       Change EOS detection to more general returning of segment code.
;       Major rewrite to solve some problems:
;         1. The cycle time was 200 ms off due to not taking KOFF into account
;         2. GET_CYCLE could not find the start of a cycle unless it was
;              called with the first record in the scan.  This has been relaxed
;              so that it can be called with any record.
;     22-Jan-2000  DG
;       First attempt to make the code work over a day change within a scan
;     28-Mar-2000  DG
;       Made change to account for newly determined timing issue--the time/label
;       field has a time that is one sample "late" since it is the time
;       corresponding to the end of acquisition of the first sample.  Variable
;       NSOFF takes care of this.  Also fixed a round-off problem introduced
;       in my code to account for a day change--amounted to 2 msec.
;     11-Mar-2001  DG
;       Fixed long-standing bug that caused "dead reckoning" in start from middle
;       of a scan to fail if the start was somewhere within the first cycle.
;     08-Feb-2002  DG
;       GET_CYCLE failed if the first record after a TRAJECTORY was not a data
;       record.  Added a check for this.  Note that first call to GET_CYCLE
;       should now be WHILE (n_elements(data) eq 0) DO get_cycle,...
;     03-Mar-2002  DG
;       FLAREMETER was getting into an infinite loop.  Added code to return an
;       EOF segment code when a file read error occurs.
;-
function get_cycle,a,data,currec,header,obseq,cfg,tcycle,tls

   ; First two samples are no good (not part of an observing sequence),
   ; so this represents a perpetual offset to the cycle.
   koff = 2

   nws = header.nws
   noff = koff*nws   ; Number of words in two samples that are no good
   nsoff = 1         ; Number of samples "late" for time in Time/Label field

   nsr = header.nsr
   rdoff = header.rdoff ; Number of words before data start
   nx = obseq.nx
   ncycle = nws*nx      ; Number of words in a cycle
   nrecord = nws*nsr    ; Number of words in a record

   t = lonarr(10)

   curpos = long(n_elements(data))
   if (curpos gt 0) then begin
      good = data  ; Put the rest of DATA into "good" array
   endif else begin
      ; The DATA array is undefined, so this is the first call.
      ; Read records until the start of a cycle is found
      sod = -1    ; SOD = Start of Data
      while (sod lt 0) do begin
         currec = currec+1
         tmpdata = getdata(currec,a)
         if (tmpdata(0) eq 0 or tmpdata(0) eq -1) then begin
            if (tmpdata[0] eq -1 and n_elements(tmpdata) eq 1) then begin
               ; If file is no longer growing, return an EOF
               filestr = fstat(a)
               if (filestr.cur_ptr le filestr.size) then return,!SEGM.EOF
            endif
            ; There is no new record waiting, so put things back as they
            ; were before the call and return a zero to indicate end of file.
            ; If the calling procedure gets a zero, it can wait and try again.
            currec = currec - 1
            return,0
         endif
         tls = tl_decode(tmpdata)
         if (tls.segmentcode ne 7) then return,0  ; Skip this record if not DATA
         ; Index of start of data (not including NOFF or RDOFF)
         sod = (nrecord - (long(nrecord)*tls.nrs mod ncycle)) ;mod nrecord
         if (sod ge nrecord) then return,0  ; Skip this record if SOD is beyond end of record
      endwhile
      good = tmpdata[sod+rdoff:rdoff+nrecord-1]
      tcycle = tls.msec + ((sod+noff)/nws - nsoff)*cfg.sampintms $
               + 86400000L*(tls.day-header.tls.day) ; Add 24h if day change
   endelse

   ; Determine the current start position of the data, for calculating
   ; the start time of the cycle.
   curpos = n_elements(good)

   ; Read records and add to "good" array until an entire cycle
   ; has been read.
   firstrec = currec
   while n_elements(good) lt (ncycle+NOFF) do begin
      currec = currec+1
      data = getdata(currec,a)
      if (data(0) eq 0 or data(0) eq -1) then begin
         if (data[0] eq -1 and n_elements(data) eq 1) then begin
            ; If file is no longer growing, return an EOF
            filestr = fstat(a)
            if (filestr.cur_ptr le filestr.size) then return,!SEGM.EOF
         endif
         ; There is no new record waiting, so put things back as they
         ; were before the call and return a zero to indicate end of file.
         ; If the calling procedure gets a zero, it can wait and try again.
         currec = currec - 1
         if (curpos gt 0) then data = good
         return,0
      endif
      tls = tl_decode(data)
      ; Read delay bits from data record
;      dla = get_delaybits(data)
; printf,2,tls.msec,dla
;      print,tls.msec,data[12]-data[13]- cfg.dlacent[0]+ cfg.dlacent[1]
      t(currec-firstrec-1) = tls.msec $
              + 86400000L*(tls.day-header.tls.day) ; Add 24h if day change
      if (tls.segmentcode ne 7) then begin
         ; This is not a data record, so return with partial contents
         cycle = good
         data = good
         ; Determine start time of cycle from previous start time, adding
         ; one cycle of time
         tcycle = tcycle + obseq.nx*cfg.sampintms
         ; Return the segmentcode, to which the calling procedure should
         ; act accordingly
         cycle = tls.segmentcode
         return,cycle
      endif
      if (n_elements(good) gt 0) then begin
         good = [good,data(rdoff:rdoff+nrecord-1)]
      endif else begin
         good = data(rdoff:rdoff+nrecord-1)
      endelse
   endwhile

   ; An entire cycle has been read, so extract it from the "good" array
   cycle = good(noff:noff+ncycle-1)

   ; If the T array is zero, the cycle is smaller than a record and we
   ; have to determine the time from dead-reckoning from the previous time.
   if (t(0) eq 0) then begin
      ; Determine start time of cycle from previous start time, adding
      ; one cycle of time
      tcycle = tcycle + obseq.nx*cfg.sampintms
   endif else begin
      ; This cycle bridges two records, so determine the start time
      ; from the start time of the next record.
      tcycle = t(0) - ((curpos-noff)/header.nws + nsoff)*cfg.sampintms
   endelse

   ; Put the rest of the good data into the "data" array (keeping KOFF
   ; samples from previous cycle so that KOFF remains equal to 2 for
   ; subsequent calls to this routine).
   data = good(ncycle:*)

   good = where(t ne 0)

return,cycle
end