///
/// @file logutils.c
/// @authors k. headley
/// @date 01 mar 2020

/// Cross-platform Carbon Waveglider log utilities

/// @sa doxygen-examples.c for more examples of Doxygen markup


/////////////////////////
// Terms of use
/////////////////////////
/*
 Copyright Information

 Copyright 2002-2020 MBARI
 Monterey Bay Aquarium Research Institute, all rights reserved.

 Terms of Use

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version. You can access the GPLv3 license at
 http://www.gnu.org/licenses/gpl-3.0.html

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details
 (http://www.gnu.org/licenses/gpl-3.0.html)

 MBARI provides the documentation and software code "as is", with no warranty,
 express or implied, as to the software, title, non-infringement of third party
 rights, merchantability, or fitness for any particular purpose, the accuracy of
 the code, or the performance or results which you may obtain from its use. You
 assume the entire risk associated with use of the code, and you agree to be
 responsible for the entire cost of repair or servicing of the program with
 which you are using the code.

 In no event shall MBARI be liable for any damages, whether general, special,
 incidental or consequential damages, arising out of your use of the software,
 including, but not limited to, the loss or corruption of your data or damages
 of any kind resulting from use of the software, any prohibited use, or your
 inability to use the software. You agree to defend, indemnify and hold harmless
 MBARI and its officers, directors, and employees against any claim, loss,
 liability or expense, including attorneys' fees, resulting from loss of or
 damage to property or the injury to or death of any person arising out of the
 use of the software.

 The MBARI software is provided without obligation on the part of the
 Monterey Bay Aquarium Research Institute to assist in its use, correction,
 modification, or enhancement.

 MBARI assumes no responsibility or liability for any third party and/or
 commercial software required for the database or applications. Licensee agrees
 to obtain and maintain valid licenses for any additional third party software
 required.
 */

/////////////////////////
// Headers
/////////////////////////
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <string.h>
#include "cwg-shared.h"
#include "licor8xx-shared.h"
#include "logutils.h"
#include "iowfile.h"
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
#include "ioserial.h"
#include "timers.h"
#endif
#if defined(__APPLE__) || defined(__unix__) || defined(__CYGWIN__)
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#endif
#if defined(__WIN32) || defined(__WIN64)
#include <Windows.h>
#endif


/////////////////////////
// Macros
/////////////////////////

// These macros should only be defined for
// application main files rather than general C files
/*
/// @def PRODUCT
/// @brief header software product name
#define PRODUCT "TBD_PRODUCT"

/// @def COPYRIGHT
/// @brief header software copyright info
#define COPYRIGHT "Copyright 2002-YYYY MBARI Monterey Bay Aquarium Research Institute, all rights reserved."
/// @def NOWARRANTY
/// @brief header software terms of use
#define NOWARRANTY  \
"This program is distributed in the hope that it will be useful,\n"\
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"\
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"\
"GNU General Public License for more details (http://www.gnu.org/licenses/gpl-3.0.html)\n"
*/

/////////////////////////
// Declarations
/////////////////////////

/////////////////////////
// Imports
/////////////////////////

/////////////////////////
// Module Global Variables
/////////////////////////
const char *linfo_rbin_names[RBIN_COUNT]={"RMID_INVAL","RMID_NONE","RMID_SAMPLE",
    "RMID_SCAL","RMID_ZCAL","RTID_INVAL","RTID_ZRECV1"};
const char *linfo_ebin_names[EBIN_COUNT]={"E_SYNC","E_TMZERO","E_TMMONO",
    "E_CTINVAL","E_SEQINVAL","E_LENINVAL","E_RTID","E_RMID"};

/////////////////////////
// Function Definitions
/////////////////////////

bool g_logutils_interrupt=false;

// find sync pattern before or after current file pointer position
// returns 0 on success w/ r_ofs set to start of sync pattern.
// if r_sync is non-NULL, contains the number of bytes skipped on success.
// returns error code otherwise:
// -1 invalid argument
// -2 EOF
// -3 beginning of file
// -4 IO/file error
static int s_find_sync(FILE_T *fp_src, bool fwd, uint32_t *r_ofs, int *r_sync)
{
    int retval=-1;
    uint32_t sync_offset =0;

    if(NULL!=fp_src && NULL!=r_ofs){
        byte cur=0x0;
        size_t br=0;
        uint32_t ofs = iow_ftell(fp_src);
        sync_offset = ofs;
        enum{ST_FIRST=0,ST_SECOND,ST_THIRD,ST_FOURTH,ST_SYNC_VALID}state;
        state=ST_FIRST;
//        OUT_ERR("sync ENTER - fp[%"FMT_U32"] fwd[%c]\r\n",iow_ftell(fp_src),BOOLCHAR(fwd));
        while(state!=ST_SYNC_VALID){
//                OUT_ERR("br[%"FMT_SZ"] cur[%02X] ferr[%d] feof[%d] fp[%"FMT_U32"]\r\n",br,cur,iow_ferror(fp_src),iow_feof(fp_src),iow_ftell(fp_src));
            state=ST_FIRST;
            cur=0;
            br=0;
            // mark offset in case this is the one
            sync_offset = iow_ftell(fp_src);
            if( (br=iow_fread(fp_src,&cur,1))==1 && cur==(fwd ? 0x02 : 0x0C) ){
                state++;
                if(!fwd){
                    ofs-=1;
                    if(ofs==0)break;
                    iow_fseek(fp_src,ofs);
                }
                if( (br=iow_fread(fp_src,&cur,1))==1 && cur==(fwd ? 0x4C : 0xEA) ){
                    state++;
                    if(!fwd){
                        ofs-=1;
                        if(ofs==0)break;
                        iow_fseek(fp_src,ofs);
                    }
                    if( (br=iow_fread(fp_src,&cur,1))==1 && cur==(fwd ? 0xEA : 0x4C) ){
                        state++;
                        if(!fwd){
                            ofs-=1;
                            iow_fseek(fp_src,ofs);
                            // save offset in case this is the one (reverse)
                            sync_offset = ofs;
                        }
                        if( (br=iow_fread(fp_src,&cur,1))==1 && cur==(fwd ? 0x0C : 0x02)){
                            state=ST_SYNC_VALID;
                        }else{
                            if(br==0 || iow_ferror(fp_src) || (iow_feof(fp_src) && fwd) || iow_ftell(fp_src)==0){
                                break;
                            }
                            if(NULL!=r_sync)(*r_sync)+=4;
                        }
                    }else{
                        if(br==0 || iow_ferror(fp_src) || (iow_feof(fp_src) && fwd) || iow_ftell(fp_src)==0){
                            break;
                        }
                        if(NULL!=r_sync)(*r_sync)+=3;
                    }
                }else{
                    if(br==0 || iow_ferror(fp_src) || (iow_feof(fp_src) && fwd) || iow_ftell(fp_src)==0){
                        break;
                    }
                    if(NULL!=r_sync)(*r_sync)+=2;
                }
            }else{
//               OUT_ERR("br[%"FMT_SZ"] cur[%02X] ferr[%d] feof[%d] fp[%"FMT_U32"]\r\n",br,cur,iow_ferror(fp_src),iow_feof(fp_src),iow_ftell(fp_src));
                if(br==0 || iow_ferror(fp_src) || (iow_feof(fp_src) && fwd) || iow_ftell(fp_src)==0){
                    break;
                }
                if(NULL!=r_sync)(*r_sync)+=1;
            }

            if(!fwd){
                if(ofs<3){
                    iow_fseek(fp_src,0);
                    break;
                }
                ofs-=1;
                iow_fseek(fp_src,ofs);
                //                DPRINT("ofs[%4"FMT_U32"] fp[%4lu] st[%d] fr[%d] br[%u] cur[%02X]\r\n",ofs,iow_ftell(fp_src),state,fr,br,cur);
            }
//            OUT_ERR("state[%d] cur[%02X] fp[%"FMT_U32"]\r\n",state,cur,iow_ftell(fp_src));
        }
//        OUT_ERR("sync status: state[%d] fwd[%c] cur[%02X] br[%"FMT_SZ"] fp(%"FMT_U32"/%"FMT_U32")\r\n",state,BOOLCHAR(fwd),cur,br,iow_ftell(fp_src),iow_fsize(fp_src));
        if(state==ST_SYNC_VALID){
            *r_ofs = sync_offset;
            retval=0;
        }else if(iow_feof(fp_src) ){
//           OUT_ERR(">>>>sync reached EOF\r\n");
            iow_clearerr(fp_src);
            retval=-2;
        }else if(iow_ftell(fp_src)== (uint32_t)0 ){
//           OUT_ERR(">>>>sync reached SOF\r\n");
            retval=-3;
        }else{
           // IO/fsys error
            if(iow_ftell(fp_src)<iow_fsize(fp_src))
            OUT_ERR("ERR - sync read state[%d] cur[%02X] iow_errno[%d] br[%"FMT_SZ"] fp(%"FMT_U32"/%"FMT_U32")\r\n",state,cur,iow_errno,br,iow_ftell(fp_src),iow_fsize(fp_src));
            retval=-4;
        }

    }// else invalid argument

//    OUT_ERR("sync EXIT  - fp[%"FMT_U32"] s_ofs[%"FMT_U32"] r_ofs[%"FMT_U32"] ret[%d]\r\n",iow_ftell(fp_src),sync_offset,*r_ofs,retval);

    return retval;
}


static int s_jump_n(FILE_T *fp_src, uint32_t count, bool dir, DWORD *r_ofs, uint32_t *r_count)
{
    int retval=0;
    uint32_t i=0;
    uint32_t test_ofs=0;
    uint32_t cur_ofs=iow_ftell(fp_src);

//    OUT_CTTY("DWORD[%"FMT_SZ"] UINT[%"FMT_SZ"] size_t[%"FMT_SZ"]\r\n",sizeof(DWORD),sizeof(UINT),sizeof(size_t));
//    OUT_CTTY("s_jump_n ENTER count[%"FMT_U32"] dir[%c] fp[%"FMT_U32"]\r\n",count,BOOLCHAR(dir),iow_ftell(fp_src));
    for(i=0;i<count;i++){
        size_t br=0;
        licor_zrecord_t zrec_s, *zrec=&zrec_s;
        ZERO_MEM(zrec,LI_ZREC_HDR_LEN);
        size_t read_len=LI_ZREC_HDR_LEN;
        if(dir){
            // read header
//            OUT_CTTY(" reading read_len[%"FMT_SZ"]\r\n",read_len);
            if( (br=iow_fread(fp_src,(void *)zrec,read_len))==read_len){
                if(zrec->hdr.sync==LI_ZREC_SYNC){
                    // update offset
                    cur_ofs+=zrec->hdr.len+LI_ZREC_HDR_LEN;
//                    OUT_CTTY("jmp fwd [%"FMT_U32"/%"FMT_U32"] ofs[%"FMT_U32"] len[%"FMT_U32"] read_len[%"FMT_SZ"]\r\n",i,count,cur_ofs,zrec->hdr.len,read_len);
                }else{
                    // file structure/read error
                    // TODO : recover sync or die
                    OUT_CTTY("ERR - possible errors in file structure e.g. extra/missing bytes\r\n");
                    OUT_CTTY("ERR - fread_iow error[%d] br[%"FMT_SZ"] cur_ofs[%"FMT_U32"] read_len[%"FMT_SZ"] sync[%04"FMT_X32"]\r\n",iow_errno,br,cur_ofs,read_len,zrec->hdr.sync);
                    retval=-5;
                }
            }else{
                // read error / EOF / SOF
                break;
            }
        }else{
            int j=0;
            // init to one measurement + header before current offset
            // assumes pointed at sync
            test_ofs=iow_ftell(fp_src)-(LI_ZREC_MDATA_LEN+LI_ZREC_HDR_LEN);
            // traverse one or more measurements
            for(j=0;j<LICOR_MAX_CYCLE_MEASUREMENTS;j++){
                iow_fseek(fp_src,test_ofs);
                if( (br=iow_fread(fp_src,(void *)zrec,read_len))==read_len){
                    if(zrec->hdr.sync==LI_ZREC_SYNC){
                        // update offset
                        cur_ofs=test_ofs;
                        //                        DPRINT("jmp rev [%d/%d] ofs[%lu]\r\n",i,count,cur_ofs);
                    break;
                    }else{
                        // back up one more measurement length
                        test_ofs -= LI_ZREC_MDATA_LEN;
                    }
                }else{
                    // read error / EOF / SOF
                    break;
                }
            }

            if(j>=LICOR_MAX_CYCLE_MEASUREMENTS){
                // file structure error
                // TODO : recover sync or die
                retval=-5;
            }
        }

        // check for error conditions
        if(iow_feof(fp_src)){
            iow_clearerr(fp_src);
            retval=-2;
        }else if(iow_ftell(fp_src)==0UL){
            retval=-3;
        }else if(iow_ferror(fp_src) || br!=read_len){
            // IO/fsys error
            iow_clearerr(fp_src);
            retval=-4;
        }

        if(retval!=0){
            break;
        }

        // continue to next offset
        iow_fseek(fp_src,cur_ofs);
    }

    if(NULL!=r_count)*r_count=i;
    if(i==count){
        *r_ofs=cur_ofs;
        retval=0;
    }

//    OUT_CTTY("s_jump_n EXIT  ret[%d] cur_ofs[%"FMT_U32"] i[%"FMT_U32"] r_count[%"FMT_U32"] r_ofs[%"FMT_U32"] fp[%"FMT_U32"]\r\n",retval,cur_ofs,
//                i,(NULL==r_count?-1:*r_count),*r_ofs,iow_ftell(fp_src));

    return retval;
}

static int s_log_fast_find_rel(FILE_T *fp_src, uint32_t n, bool dir, int *r_count, DWORD *r_ofs, int *r_sync)
{
    uint32_t retval=-1;
    uint32_t i=0;
    uint32_t test_ofs=0;
    int serr=0;
//       OUT_CTTY("s_log_fast_find_rel ENTER ret[%d] n[%d] dir[%c] ofs[%"FMT_U32"] count[%d] sync[%d] fp[%"FMT_U32"]]\r\n",retval,n,(dir?'Y':'N'),test_ofs,*r_count,*r_sync,iow_ftell(fp_src));

    if(NULL!=fp_src && NULL!=r_ofs && n>=0){

        for(i=0; i<n ;i++){
            test_ofs=0;
            // find the first sync (if not already there)

            if( (serr=s_find_sync(fp_src,dir,&test_ofs,r_sync))==0){

                // use fast routine for remaining
                // (requires start at valid)
                i++;
//                OUT_CTTY("sync[%d/%d] slow ofs[%"FMT_U32"]\r\n",i,n,test_ofs);
                if(i<n){
                    uint32_t r_found=0;
                    iow_fseek(fp_src,test_ofs);
                    if( (serr=s_jump_n(fp_src,n-i,dir,&test_ofs,&r_found))==0 ){
                        // success
                        // DPRINT("sync[%d/%d] fast found[%d] ofs[%"FMT_U32"]\r\n",i,n,r_found,test_ofs);
                    }else{
//                        TRACE();
//                        OUT_CTTY("i[%"FMT_U32"] rfound[%"FMT_U32"] serr[%d]\r\n",i,r_found,serr);
                        retval=serr;
                        break;
                    }
//                    OUT_CTTY("i[%"FMT_U32"] rfound[%"FMT_U32"]\r\n",i,r_found);
                    // update counter - should be finished
                    i=r_found;
                }

                if(i>=n)break;

            }else{
                retval=serr;
                break;
            }
        }

        if(NULL!=r_count)*r_count=i;
        if(i==n){
            *r_ofs=test_ofs;
            retval=0;
        }
    }// else invalid argument
//    OUT_CTTY("s_log_fast_find_rel EXIT  ret[%d] test_ofs[%"FMT_U32"] rcount[%"FMT_U32"] sync[%d] fp[%"FMT_U32"]\r\n",retval,test_ofs,
//            (NULL==r_count?0:*r_count),(NULL==r_sync?0:*r_sync),iow_ftell(fp_src));
    return retval;
}

static int s_log_find_abs(FILE_T *fp_src, uint32_t rec_idx, DWORD *r_ofs, uint32_t *r_count)
{
    int retval=-1;

//       OUT_CTTY("s_log_find_abs ENTER rec_idx[%"FMT_U32"] fp[%"FMT_U32"]\r\n",rec_idx,iow_ftell(fp_src));

    if(NULL!=fp_src && NULL!=r_ofs ){
        uint32_t test_ofs=0;
        int serr=0;

        iow_fseek(fp_src,0);
        uint32_t test_count=0;
        if( (serr=s_jump_n(fp_src, rec_idx, true, &test_ofs, &test_count))!=0){
            retval=serr;
        }

        // set number found if provided
        if(NULL!=r_count){
            *r_count=test_count;
        }

        if(test_count==rec_idx){
            *r_ofs=test_ofs;
            retval=0;
        }
    }
    return retval;
}

// find offset of nth record before/after current position
// on entry, file pointer (fp_src) points to start of search
// fp_src is NOT restored on exit [1], r_ofs contains offset of nth record
// stops searching at end/beginning of file and returns
// return 0 and r_ofs set to start of record on success, error code otherwise:
// -1 invalid argument
// -2 EOF
// -3 beginning of file
// -4 IO/file error
// [1] For this application, restoring the file pointer would
//     would result in unnecessary seek calls
static int s_log_find_rel(FILE_T *fp_src, int n, bool dir, int *r_count, uint32_t *r_ofs, int *r_sync)
{
    int retval=-1;
    int i=0;
    uint32_t test_ofs=0;
    int serr=0;
//       OUT_ERR("find_rel ENTER ret[%d] n[%d] dir[%c] ofs[%"FMT_U32"] count[%d] sync[%d] fp[%"FMT_U32"]\r\n",retval,n,(dir?'Y':'N'),test_ofs,*r_count,*r_sync,iow_ftell(fp_src));

    if(NULL!=fp_src && NULL!=r_ofs && n>=0){

        for(i=0; i<n ;i++){
            test_ofs=0;
            if( (serr=s_find_sync(fp_src,dir,&test_ofs,r_sync))==0){
                // keep going
                //                DPRINT("find_rel - sync @%lu\r\n",test_ofs);
            }else{
                retval=serr;
                break;
            }
        }
        if(NULL!=r_count)*r_count=i;
        if(i==n){
            *r_ofs=test_ofs;
            retval=0;
        }
    }// else invalid argument
//    OUT_ERR("find_rel EXIT  ret[%d] ofs[%"FMT_U32"] count[%d] sync[%d] fp[%"FMT_U32"]\r\n",retval,test_ofs,
//                (NULL==r_count?-1:*r_count),(NULL==r_sync?-1:*r_sync),iow_ftell(fp_src));
    return retval;
}


static int s_log_search(FILE_T *fp_src, bool fwd, log_search_t *srch, licor_zrecord_t *r_zrec, mdata_Z_t *r_mdata, DWORD *r_ofs, int *r_sync)
{
    int retval=-1;
    licor_zrecord_t test_rec,*ptrec=&test_rec;
    ZERO_MEM(ptrec,sizeof(licor_zrecord_t));
    // restore_ofs:
    // 0: entry
    // 1: found_record start
    // 2: found mdata start
    uint32_t restore_ofs[3]={0};
    restore_ofs[0]=iow_ftell(fp_src);
    uint32_t test_ofs=0;
    size_t br=0;

    bool match_found=false;

    if(NULL!=fp_src && NULL!=srch && NULL!=r_ofs){

        while(!match_found && s_log_find_rel(fp_src, 1, fwd, NULL, &test_ofs, r_sync)==0){

            // save offset of found record
            restore_ofs[1]=test_ofs;
            // load header
            ZERO_MEM(&test_rec,sizeof(licor_zrecord_t));
            iow_fseek(fp_src,test_ofs);
            size_t read_len=sizeof(licor_zrecord_t);
            if( (br=iow_fread(fp_src,&test_rec,read_len))==read_len){
                // compare
                switch(srch->type){
                    case SRCH_TIME:
                        //                        DPRINT("SRCH_TIME [%"FMT_U64" >=? %"FMT_U64"]\r\n",ptrec->hdr.timestamp,srch->u64val);
                        if(ptrec->hdr.timestamp>=srch->u64val){
                            match_found=true;
                        }
                        break;
                    case SRCH_RTID:
                        if(ptrec->hdr.type_id==srch->u16val){
                            match_found=true;
                        }
                        break;
                    case SRCH_RMID:
                        if(ptrec->hdr.meas_id==srch->u16val){
                            match_found=true;
                        }
                        break;
                    case SRCH_MMID:
                        OUT_ERR("ERR - MMID search not implemented\r\n");
                        break;
                    default:
                        OUT_ERR("ERR - invalid option[%d]\r\n",srch->type);
                        break;
                }

                if(match_found){
                    // at this point, the file pointer points
                    // to mdata, which hasn't been read yet
                    // The file pointer and return values
                    // Depend on what return pointers are passed in,

                    // tentative success
                    // (could still fail reading mdata)
                    retval=0;

                    // only set return values if everything requested OK
                    if(NULL!=r_mdata){
                        // copy mdata and record header
                        // optionally load mdata
                        size_t read_len = LI_ZREC_DATA_LEN(ptrec);
                        br=0;
                        // save offset (mdata start)
                        restore_ofs[2]=iow_ftell(fp_src);
                        if((br=iow_fread(fp_src,&r_mdata,read_len))==read_len){
                            if(NULL!=r_zrec){
                                // return record header
                                memcpy(r_zrec,ptrec,sizeof(licor_zrecord_t));
                                // set mdata pointer
//                                r_zrec->mdata=LI_ZREC_PMDATA(r_zrec);
                                if(NULL!=r_mdata){
                                    r_mdata = LI_ZREC_PMDATA(r_zrec);//r_zrec->mdata;
                                }
                            }
                            // save offset (start of found record)
                            *r_ofs=restore_ofs[1];

                            // file pointer @ end of mdata
                        }else{
                            // mdata read error
                            OUT_ERR("ERR - mdata read failed iow_errno[%d] br[%"FMT_SZ"] fp(%"FMT_U32"/%"FMT_U32")\r\n",iow_errno,br,iow_ftell(fp_src),iow_fsize(fp_src));
                            retval=-1;
                        }
                    }else if(NULL!=r_zrec){
                        // return offset and record header
                        // copy record header
                        memcpy(r_zrec,ptrec,sizeof(licor_zrecord_t));
                        // invalidate mdata pointer;
//                        r_zrec->mdata=NULL;
                        // return offset (start of found record)
                        *r_ofs=restore_ofs[1];
                        // file pointer @ start of mdata
                    }else{
                        // only return offset
                        // save offset (start of found record)
                        *r_ofs=restore_ofs[1];
                        // file pointer @ start of record
                        iow_fseek(fp_src,*r_ofs);
                    }
                }else{
                    // no match, update offset and continue
                    if(fwd){
                        // file pointer should be at mdata
                        // skip past mdata
                        int64_t new_pos=iow_ftell(fp_src)+LI_ZREC_DATA_LEN(ptrec);
                        iow_fseek(fp_src, new_pos);
                    }else{
                        // will have to slog backwards...
                        // point to byte before this record
                        iow_fseek(fp_src, (restore_ofs[0]-1));
                    }
                }
            }else{
                // header read error
                OUT_ERR("ERR - header read failed iow_errno[%d] br[%"FMT_SZ"] fp(%"FMT_U32"/%"FMT_U32")\r\n",iow_errno,br,iow_ftell(fp_src),iow_fsize(fp_src));
                // if error occurred (file pointer not at start/end of file)
                // restore pointer to entry value
                if(!iow_feof(fp_src) && iow_ftell(fp_src)!=(int64_t)0){
                    iow_fseek(fp_src,restore_ofs[0]);
                }
                // else at file start/end
                break;
            }
        }
    }
    return retval;
}

return_code_t lu_lless(char *fpath, queue_output_flag qflags)
{
    return_code_t retval=ERR_FAILURE;
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
    FILE_T file_s, *fp=&file_s;
#else
    FILE_T *fp;
#endif

    byte ctrl=0;
    uint32_t eof=0;
    uint32_t ofs_head=0;
    uint32_t ofs_tail=0;
    uint32_t ofs_test=0;
    uint32_t ofs_new=0;
    uint32_t ofs_saved=0;

    // will we get away with putting this monster on the stack?
    char zrbuf[LI_ZREC_SIZEN(6)];
    licor_zrecord_t *zrec=(licor_zrecord_t *)zrbuf;
    enum {HELP=0,WAIT, QUIT, MOVE, PREV, NEXT, HEAD, TAIL, CMD, SET_OFLAG, SRCH_NEXT, SRC_PREV, SEARCH,STATE_COUNT}state;
    //    char *stnames[STATE_COUNT]={"HELP","WAIT", "QUIT", "MOVE", "PREV", "NEXT",
    //    "HEAD", "TAIL", "CMD", "SET_OFLAG", "SRCH_NEXT", "SRC_PREV", "SEARCH"};
    char ibuf[64]={0};
    uint64_t srch_time[2]={0LL,0LL};
    log_search_t search_s,*search=&search_s;
    struct tm tm_iso;
    char *ip=ibuf;
    int move_n=0;
    bool move_fwd=true;
    queue_output_flag oflags[2]={qflags,QOF_NONE};

    if(NULL!=fpath){

        // open or die
        if(iow_fopen(&fp, fpath, IOW_READ)==0){
            // initialize state machine parameters
            bool init=false;
            state=HELP;
            // calculate end of file (can't be negative)
            eof=iow_fsize(fp);
            eof=( (eof<=0) ? 0 : (eof-1));
//            OUT_ERR("eof[%"FMT_U32"] fsize[%"FMT_U32"]\r\n",eof,iow_fsize(fp));
            // set head (offset of first record)
            iow_fseek(fp,0);

            if(s_log_find_rel(fp, 1, true, NULL, &ofs_test, NULL)==0){
                ofs_head=ofs_test;
            }else{
                TRACE();
            }
            // set tail (offset of last record)
            // TODO: could make faster if subtract mdata_Z_t size
            // (assumes valid record has at least 1 mdata entry)
            iow_fseek(fp,eof);
            if(s_log_find_rel(fp, 1, false, NULL, &ofs_test, NULL)==0){
                ofs_tail=ofs_test;
            }else{
                TRACE();
            }

            // set file pointer to start of file
            iow_fseek(fp,0);

            // init search params
            ZERO_MEM(search,sizeof(log_search_t));
            search->type=SRCH_NONE;

            // enter state machine:
            // - compute offset (modified for some states/operations)
            // - output text
            // - get input
            // - update state
            while(state!=QUIT && !g_logutils_interrupt){
                //                DPRINT(">>> st[%s] ofs_sv[%lu] ofs_nw[%lu] fp[%"FMT_U32"]] mvn[%d] mfw[%c]\r\n",stnames[state],ofs_saved,ofs_new,iow_ftell(fp),move_n,(move_fwd?'Y':'N'));
                // adjust file offset per state
                if(state==HEAD){
                    ofs_new=ofs_head;
                }else if(state==TAIL){
                    ofs_new=ofs_tail;
                }else if(state==PREV){
                    if(ofs_saved==ofs_head){
                        OUT_ERR("reached start of file - wrapping\r\n");
                        // already at head, go to tail
                        ofs_new=ofs_tail;
                    }else{
                        iow_fseek(fp,ofs_saved);
                        if(s_log_find_rel(fp, 1, false, NULL, &ofs_test, NULL)==0){
                            ofs_new=ofs_test;
                        }else{
                            OUT_ERR("reached start of file - wrapping\r\n");
                            ofs_new=ofs_tail;
                        }
                    }
                }else if(state==NEXT){
                    if(s_log_find_rel(fp, 1, true, NULL, &ofs_test, NULL)==0){
                        ofs_new=ofs_test;
                    }else{
                        OUT_ERR("reached end of file - wrapping\r\n");
                        ofs_new=ofs_head;
                    }
                }else if(state==MOVE){
                    int mtotal=0;
                    int serr=0;

                    if(move_n==0){
                        // if repeat last
                        // just load the last offset
                        iow_fseek(fp,ofs_saved);
                    }else{
                        if(!move_fwd){
                            // if reverse move,
                            // start at current offset
                            iow_fseek(fp,ofs_saved);
                        }
                        //                   DPRINT("MOVE - serr[%d] fp[%lu] ofs_new[%"FMT_U32"]] mtotal/move_n/fwd[%d/%d/%c]\r\n",serr,iow_ftell(fp),ofs_new,mtotal,move_n,(move_fwd?'Y':'N'));
                        bool move_stop=false;
                        while(mtotal<move_n && serr>-4 && !move_stop){
                            int mcount=0;

                            if( (serr=s_log_fast_find_rel(fp, move_n-mtotal, move_fwd, &mcount, &ofs_test, NULL))==0){
                                //                            if( (serr=s_log_find_rel(fp, move_n-mtotal, move_fwd, &mcount, &ofs_test, NULL))==0){
                                ofs_new=ofs_test;
                            }else{
                                switch(serr){
                                    case -1:
                                        TRACE();
                                        break;
                                    case -2:
                                        // EOF
                                        if(move_fwd){
                                            OUT_ERR("end of file\r\n");
                                            ofs_new=eof;
                                        }else{
                                            OUT_ERR("start of file\r\n");
                                            ofs_new=0;
                                        }
                                        move_stop=true;
                                        break;
                                    case -3:
                                        // start of file
                                        if(move_fwd){
                                            OUT_ERR("end of file\r\n");
                                            ofs_new=eof;
                                        }else{
                                            OUT_ERR("start of file\r\n");
                                            ofs_new=0;
                                        }
                                        move_stop=true;
                                        break;
                                    case -4:
                                        // IO error
                                        OUT_ERR("io error [%d]\r\n",iow_errno);
                                        move_stop=true;
                                        break;
                                }
                            }
                            mtotal+=mcount;
                            //                        DPRINT("MOVE - serr[%d] fp[%lu] ofs_new[%"FMT_U32"]] mtotal/move_n/fwd[%d/%d/%c]\r\n",serr,iow_ftell(fp),ofs_new,mtotal,move_n,(move_fwd?'Y':'N'));
                        }
                    }
                }else if(state==SEARCH){
                    uint32_t ofs_match=0;

                    if(search->type == SRCH_IDX){
                        uint32_t r_count=0;
                        if(s_log_find_abs(fp,search->u32val,&ofs_match,&r_count)==0){
                            ofs_new=ofs_match;
                        }else{
                            // not found
                            OUT_ERR("record idx[%"FMT_U32"] not found (max[%"FMT_U32"])\r\n",search->u32val,r_count);
                            state=WAIT;
                        }
                    }else{
                        if(s_log_search(fp, search->fwd, search, NULL, NULL, &ofs_match, NULL)==0){
                            ofs_new=ofs_match;
                        }else{
                            if(iow_feof(fp)){
                                OUT_ERR("search reached end of file\r\n");
                            }else{
                                OUT_ERR("ERR - search failed\r\n");
                            }
                            state=WAIT;
                        }
                    }

                }else if(state==HELP){
                    OUT_CTTY("            Command Summary\r\n\r\n");
                    OUT_CTTY("  Movement  :\r\n");
                    OUT_CTTY("    First/Last [ g <    ] / [ G >  ]\r\n");
                    OUT_CTTY("    Next/Prev  [ dD<CR> ] / [ uU   ]\r\n");
                    OUT_CTTY("    This       [ <SPC>  ]\r\n");
                    OUT_CTTY("    Jump +/-   [ j<n>   ] / [ k<n> ]\r\n");
                    OUT_CTTY("\r\n");
                    OUT_CTTY("  Session   :\r\n");
                    OUT_CTTY("    Help [h H ?]   Quit [q Q x X]\r\n");
                    OUT_CTTY("\r\n");
                    OUT_CTTY("  Command   :\r\n");
                    OUT_CTTY("    Enter cmd mode   [ / ]\r\n");
                    OUT_CTTY("    Search -\r\n");
                    OUT_CTTY("      tm<msec> ti<iso1806>\r\n");
                    OUT_CTTY("      rt<rtid> rm<mtid>\r\n");
                    OUT_CTTY("      i<record index>\r\n");
                    OUT_CTTY("      Next match [n]\r\n");
                    OUT_CTTY("      Prev match [N]\r\n");
                    OUT_CTTY("    Output -\r\n");
                    OUT_CTTY("      out:[pbcrx[x*]hdv]\r\n");
                    OUT_CTTY("\r\n");

                    if(!init){
                        init=true;
                        state=HEAD;
                        ofs_saved=0;
                        ofs_new=0;
                    }
                }

                // Output record if found
                if(state!=WAIT && state!=HELP){
                    iow_fseek(fp,ofs_new);
                    ofs_saved=ofs_new;
                    //                DPRINT("+++ st[%s] ofs_sv[%"FMT_U32"]] ofs_nw[%"FMT_U32"]] fp[%lu]\r\n",stnames[state],ofs_saved,ofs_new,iow_ftell(fp));

                    size_t br=0;
                    size_t read_len=LI_ZREC_HDR_LEN;
                    ZERO_MEM(zrec,LI_ZREC_SIZEN(6));
                    if((br=iow_fread(fp,zrec,read_len))==read_len){
//                        fr=0;
                        br=0;
                        read_len=LI_ZREC_DATA_LEN(zrec);
                        if((br=iow_fread(fp,LI_ZREC_PMDATA(zrec),read_len))==read_len){
//                            zrec->mdata=LI_ZREC_PMDATA(zrec);
                            OUT_CTTY("\r\n\r\n");
                            if( (oflags[0]&QOF_PRETTY) != 0){
                                if( (oflags[0]&QOF_VERBOSE) != 0){
                                    OUT_CTTY("Record @ [%"FMT_U32"]\r\n",ofs_new);
                                }
                                licor_zrecord_outs(zrec, oflags[0]);
                            }
                            OUT_CTTY("\r\n");
                        }else{
                            TRACE();
                            OUT_ERR("ERR - data read iow_errrno[%d] br[%"FMT_SZ"]\r\n",iow_errno,br);
                        }
                    }else{
                        if(iow_feof(fp)){
                            OUT_ERR("end of file\r\n");
                        }else if(iow_ftell(fp)==0){
                            OUT_ERR("start of file\r\n");
                        }else{
                            OUT_ERR("ERR - header read iwo_errno[%d] br[%"FMT_SZ"]\r\n",iow_errno,br);
                            TRACE();
                        }
                    }
                }

                // Get input
                int inchar=0;
                while((inchar=iow_get_char(false))==0){
                  iow_delay_msec(500);
                }
                ctrl=inchar;
//                OUT_CTTY("char[%02X]\r\n",ctrl);

                // Update state
                state=WAIT;

                move_n=0;
                move_fwd=true;
                switch(ctrl){
                    case 'j':
                    case 'J':
                    case 'k':
                    case 'K':
                        OUT_CTTY("\b%c",ctrl);
                        ZERO_MEM(ibuf,IOF_SRCH_MAX_BYTES);
                        iow_get_line(ibuf,IOF_SRCH_MAX_BYTES);
                        move_n=1;
                        move_fwd=true;
                        sscanf(ibuf,"%d",&move_n);
                        if(ctrl=='k'||ctrl=='K')move_fwd=false;
                        state=MOVE;
                        break;
                    case 'U':
                    case 'u':
                        move_n=1;
                        move_fwd=false;
                        state=PREV;
                        break;
                    case 'D':
                    case 'd':
                    case '\r':
                    case '\n':
                        state=NEXT;
                        move_n=1;
                        break;
                    case '<':
                    case 'g':
                        state=HEAD;
                        break;
                    case '>':
                    case 'G':
                        state=TAIL;
                        break;
                    case ' ':
                        move_n=0;
                        state=MOVE;
                        break;
                    case '/':
                        state=CMD;
                        OUT_CTTY("\b/");
                        ZERO_MEM(ibuf,IOF_SRCH_MAX_BYTES);
                        iow_get_line(ibuf,IOF_SRCH_MAX_BYTES);

                        if((ip=strstr(ibuf,"tm"))!=NULL){
                            if(sscanf(ibuf+2,"%"FMT_U64"",&srch_time[1])==1){
                                search->type=SRCH_TIME;
                                search->u64val=srch_time[1];
                                search->fwd=true;
                                state=SEARCH;
                            }
                        }else if((ip=strstr(ibuf,"ti"))!=NULL){
                            //                          uint16_t iso_msec=0;

                            if(sscanf(ibuf+2,"%d-%d-%dT%d:%d:%d",
                                      &tm_iso.tm_year, &tm_iso.tm_mon,
                                      &tm_iso.tm_mday, &tm_iso.tm_hour,
                                      &tm_iso.tm_min, &tm_iso.tm_sec)==6){

                                tm_iso.tm_year-=1900;
                                tm_iso.tm_mon-=1;
                                search->type=SRCH_TIME;
                                search->u64val=mktime(&tm_iso)*1000ULL;
                                OUT_CTTY("srch %s -> %"FMT_U64"\r\n",ibuf+2,search->u64val);
                                search->fwd=true;
                                state=SEARCH;
                            }
                        }else if((ip=strstr(ibuf,"rt"))!=NULL){
                            if(sscanf(ibuf+2,"%"FMT_U16"",&search->u16val)==1){
                                search->type=SRCH_RTID;
                                search->fwd=true;
                                state=SEARCH;
                            }
                        }else if((ip=strstr(ibuf,"rm"))!=NULL){
                            if(sscanf(ibuf+2,"%"FMT_U16"",&search->u16val)==1){
                                search->type=SRCH_RMID;
                                search->fwd=true;
                                state=SEARCH;
                            }
                        }else if((ip=strstr(ibuf,"mm"))!=NULL){
                            if(sscanf(ibuf+2,"%"FMT_U16"",&search->u16val)==1){
                                search->type=SRCH_MMID;
                                search->fwd=true;
                                state=SEARCH;
                            }
                        }else if((ip=strstr(ibuf,"i"))!=NULL){
                            if(sscanf(ibuf+1,"%"FMT_U32"",&search->u32val)==1){
                                search->type=SRCH_IDX;
                                search->fwd=true;
                                state=SEARCH;
                            }
                        }else if((ip=strstr(ibuf,"out:"))!=NULL){
                            ip+=4;
                            oflags[1]=QOF_NONE;

                            if(NULL!=strstr(ip,"X*") || NULL!=strstr(ip,"x*")){
                                oflags[1]|=QOF_PRETTY_HEX|QOF_HEX;
                            }else if(NULL!=strstr(ip,"XX") || NULL!=strstr(ip,"xx")){
                                oflags[1]|=QOF_PRETTY_HEX;
                            }else if(NULL!=strstr(ip,"X") || NULL!=strstr(ip,"x")){
                                oflags[1]|=QOF_HEX;
                            }
                            while(*ip!='\0'){
                                if(*ip=='h' || *ip=='H')oflags[1]|=QOF_HEADER|QOF_PRETTY;
                                if(*ip=='b' || *ip=='B')oflags[1]|=QOF_BASE85;
                                if(*ip=='c' || *ip=='C')oflags[1]|=QOF_CSV;
                                if(*ip=='r' || *ip=='R')oflags[1]|=QOF_RAW;
                                if(*ip=='u' || *ip=='U')oflags[1]|=QOF_UUENCODE;
                                if(*ip=='d' || *ip=='D')oflags[1]|=QOF_DATA|QOF_PRETTY;
                                //                                if(*ip=='x' || *ip=='X')oflags[1]|=QOF_PRETTY_HEX;
                                if(*ip=='v' || *ip=='V')oflags[1]|=QOF_VERBOSE;
                                ip++;
                            }
                            if(oflags[0]!=QOF_NONE){
                                oflags[0]=oflags[1];
                            }
                            state=WAIT;
                        }
                        // if no valid cmd, return to WAIT
                        if(state==CMD)
                            state=WAIT;
                        break;

                    case 'N':
                        search->fwd=false;
                        if(iow_ftell(fp)==0){
                            iow_fseek(fp,ofs_tail);
                        }
                        state=SEARCH;
                        break;
                    case 'n':
                        search->fwd=true;
                        if(iow_feof(fp)){
                            iow_fseek(fp,ofs_head);
                        }
                        state=SEARCH;
                        break;

                    case 'Q':
                    case 'q':
                    case 'X':
                    case 'x':
                        if(state==SEARCH){
                            state=WAIT;
                        }else{
                            state=QUIT;
                        }
                        break;
                    case 'H':
                    case 'h':
                    case '?':
                        state=HELP;
                        break;
                }
            }
            iow_fclose(fp);
            retval=ERR_SUCCESS;
        }else{
            OUT_ERR("ERR - iowfile [%d]\r\n",iow_errno);
            iow_fclose(fp);
        }
    }
    OUT_CTTY("\r\n");
    return retval;
}

return_code_t lu_lcat(char *fpath, int start, int count, queue_output_flag oflags)
{
    return_code_t retval=ERR_FAILURE;
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
    FILE_T file_s, *fp=&file_s;
#else
    FILE_T *fp;
#endif
    uint32_t eof=0;
    uint32_t ofs_test=0;

//    OUT_CTTY("entry - file[%s] s[%d] c[%d] oflags[%08"FMT_FLAG"]\r\n",fpath,start,count,oflags);

    if(NULL!=fpath){

        // open or die
        if( iow_fopen(&fp, fpath, IOW_READ)==0 ){

            // mark end of file
            eof=iow_fsize(fp)-1;
            // set head (offset of first record)
            iow_fseek(fp,0);

            int mcount=0;
            int rec_count=0;
            int terr=0;
            bool fwd=true;
            // records are zero indexed
            // start record is start+1
            int start_n=start+1;
            if(start<0){
                // for reverse (e.g. last n)
                // use -start, i.e. relative to 0 index
                fwd=false;
                start_n = -start;
                iow_fseek(fp,eof);
            }

            //            DPRINT("lcat - start_n[%d] fwd[%c] fp[%"FMT_U32"]]\r\n",start_n,BOOLCHAR(fwd),iow_ftell(fp));
            // skip to start record
            if( (terr=s_log_fast_find_rel(fp, start_n, fwd, &mcount, &ofs_test, NULL))==0){

                // output <count> records
                while( (count<0 || (rec_count < count) ) && ofs_test<eof && !g_logutils_interrupt){

                    iow_fseek(fp,ofs_test);
                   //                    DPRINT(">>> rec[%d] ofs_test[%lu] fp[%"FMT_U32"]]\r\n",rec_count,ofs_test,iow_ftell(fp));
                    char zrbuf[LI_ZREC_SIZEN(6)];
                    licor_zrecord_t *zrec=(licor_zrecord_t *)zrbuf;

                    size_t br=0;
                    size_t read_len=LI_ZREC_HDR_LEN;
                    ZERO_MEM(zrec,LI_ZREC_SIZEN(6));

                    if((br=iow_fread(fp,(void *)zrec,read_len))==read_len){

                        br=0;
                        read_len=LI_ZREC_DATA_LEN(zrec);
                        if(read_len<=(LI_ZREC_SIZEN(6)-LI_ZREC_HDR_LEN) && (br=iow_fread(fp,(void *)LI_ZREC_PMDATA(zrec),read_len))==read_len){
//                            zrec->mdata=LI_ZREC_PMDATA(zrec);

//                            OUT_CTTY("%s",LINEEND);
                            if( (oflags&QOF_VERBOSE) != 0){
                                OUT_CTTY("Record[%d @ %"FMT_U32"]\r\n",start_n+rec_count,ofs_test);
                            }
                            licor_zrecord_outs(zrec, oflags);
//                            OUT_CTTY("%s",LINEEND);
                        }else{
                            TRACE();
                        }
                    }

                    uint32_t next_ofs=ofs_test+LI_ZREC_SIZEN(zrec->hdr.meas_count);
//                    OUT_ERR("%s:%d - next ofs:%"FMT_U32"\r\n",__func__,__LINE__,next_ofs);
                    iow_fseek(fp,next_ofs);
                    rec_count++;
                    ofs_test=0;
                    mcount=0;

                    if(count<0 || (rec_count < count)){
                       // if not done, read next record
                        if(  (terr=s_log_find_rel(fp, 1, true, &mcount, &ofs_test, NULL))==0 &&
                           mcount==1 ){
                            // read OK
                        }else{
                            // else sync not found (EOF probably)
                            if(terr==-2){
                                OUT_ERR("end of file found[%d/%d]\r\n",rec_count,count);
                            }else {
                                OUT_ERR("%d - record not found [%d] err[%d]\r\n",__LINE__,(start+rec_count),terr);
                            }
                            break;
                        }
                        byte brk=0;
                        brk=iow_get_char(false);
                        if(brk=='\r' || brk=='\n' || brk==CTRL_D){
                            break;
                        }
                    }
                }
            }else{
                // start record not found
                OUT_ERR("%d - record not found [%d] err[%d]\r\n",__LINE__,(start+rec_count),terr);
            }

            if(rec_count==count || count<1){
                retval=ERR_SUCCESS;
            }

            iow_fclose(fp);
        }else{
            EPRINT("open failed file[%s] [%d]",fpath,iow_errno);
        }
    }else{
        EMSG("invalid arg\r\n");
    }

    return retval;
}

return_code_t lu_linfo(char *fpath, linfo_flags oflags, linfo_stats_t *r_stats)
{
    return_code_t retval=ERR_FAILURE;
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
    FILE_T file_s, *fp=&file_s;
#else
    FILE_T *fp;
#endif

    linfo_stats_t *pstats=r_stats;
    if(NULL==r_stats){
        // use local instance (NULL instanced passed in)
        linfo_stats_t stats_s;
        pstats=&stats_s;
    }
    memset(pstats,0,sizeof(linfo_stats_t));
    pstats->fsize=0;
    pstats->time_bin[2]=0xFFFFFFFFFFFFFFFFULL;
    pstats->seq_bin[2]=0xFFFFFFFFUL;
    pstats->seq_last=-1;
    pstats->time_last=-1;

    uint32_t ofs_test=0;
    if(NULL!=fpath){
        // open or die
        if(iow_fopen(&fp, fpath, IOW_READ)==0){
            pstats->fsize=iow_fsize(fp);
            // mark end of file
//            uint32_t eof=fsize-1;
            // set head (offset of first record)
            iow_fseek(fp,0);

            int mcount=0;
            int sync_bytes=0;
            while(iow_feof(fp)==0){
                int test=0;
                if( (test=s_log_find_rel(fp, 1, true, &mcount, &ofs_test, &sync_bytes))==0){

                    // point to start record
                    iow_fseek(fp,ofs_test);
                    char zrbuf[LI_ZREC_HDR_LEN];
                    licor_zrecord_t *zrec=(licor_zrecord_t *)zrbuf;
                    size_t br=0;
                    size_t read_len=LI_ZREC_HDR_LEN;

                    ZERO_MEM(zrec,LI_ZREC_HDR_LEN);
                    if((br=iow_fread(fp,zrec,read_len)) == read_len){

                        // licor_zrecord_outs(zrec,QOF_PRETTY|QOF_PRETTY_HEX|QOF_HEADER);

                        // timestamp, sequence ranges
                        if(zrec->hdr.timestamp<=pstats->time_bin[T_RNGSTART])
                            pstats->time_bin[T_RNGSTART]=zrec->hdr.timestamp;
                        if(zrec->hdr.timestamp>=pstats->time_bin[T_RNGEND])
                            pstats->time_bin[T_RNGEND]=zrec->hdr.timestamp;

                        if(zrec->hdr.seq_n<=pstats->seq_bin[SEQ_RNGSTART]){
                            pstats->seq_bin[SEQ_RNGSTART]=zrec->hdr.seq_n;
                        }
                        if(zrec->hdr.seq_n>=pstats->seq_bin[SEQ_RNGEND]){
                            pstats->seq_bin[SEQ_RNGEND]=zrec->hdr.seq_n;
                        }

                        if(pstats->record_count==0){
                            pstats->time_bin[T_FIRST]=zrec->hdr.timestamp;
                            pstats->time_bin[T_LAST]=zrec->hdr.timestamp;
                            pstats->seq_bin[SEQ_FIRST]=zrec->hdr.seq_n;
                            pstats->seq_bin[SEQ_LAST]=zrec->hdr.seq_n;
                        }else{
                            pstats->time_bin[T_LAST]=zrec->hdr.timestamp;
                            pstats->seq_bin[SEQ_LAST]=zrec->hdr.seq_n;
                        }

                        // timestamp errors
                        if(zrec->hdr.timestamp==0){
                            pstats->time_bin[E_TMZERO]++;
                        }
                        if(pstats->record_count!=0 && !(zrec->hdr.timestamp>pstats->time_last)){
                            pstats->err_bin[E_TMMONO]++;
                        }
                        pstats->time_last=zrec->hdr.timestamp;


                        // seq errors
                        if( (pstats->record_count!=0) && (zrec->hdr.seq_n<=pstats->seq_last)){
                            pstats->err_bin[E_SEQMONO]++;
                        }
                        pstats->seq_last=zrec->hdr.seq_n;

                        // measurement count errors
                        if(zrec->hdr.meas_count<0 || zrec->hdr.meas_count>LICOR_MAX_CYCLE_MEASUREMENTS)pstats->err_bin[E_CTINVAL]++;

                        // record len errors
                        if(zrec->hdr.len==0 || ((zrec->hdr.len%LI_ZREC_MDATA_LEN)!=0) )pstats->err_bin[E_LENINVAL]++;
                        // record type stats, errors
                        switch(zrec->hdr.type_id){
                            case RTID_INVALID:
                                pstats->record_bin[R_RTID_INVAL]++;
                                break;
                            case RTID_ZRECv0:
                                pstats->record_bin[R_RTID_ZRECV1]++;
                                break;
                            default:
                                pstats->err_bin[E_RTID]++;
                                break;
                        }
                        switch(zrec->hdr.meas_id){
                            case RMID_INVALID:
                                pstats->record_bin[R_RMID_INVAL]++;
                                break;
                            case RMID_NONE:
                                pstats->record_bin[R_RMID_NONE]++;
                                break;
                            case RMID_SAMPLE:
                                pstats->record_bin[R_RMID_SAMPLE]++;
                                break;
                            case RMID_SCAL:
                                pstats->record_bin[R_RMID_SCAL]++;
                                break;
                            case RMID_ZCAL:
                                pstats->record_bin[R_RMID_ZCAL]++;
                                break;
                            default:
                                pstats->err_bin[E_RMID]++;
                                break;
                        }

                        if( (oflags&LINF_REC_COUNTER) !=0)
                        if( (pstats->record_count%100) == 0)OUT_CTTY("\b\b\b\b\b\b%6d",pstats->record_count);

                        pstats->record_count++;

                        uint32_t next_ofs=ofs_test+LI_ZREC_SIZEN(zrec->hdr.meas_count);
//                        OUT_CTTY("rcount[%d] ofs_test[%"FMT_U32"] next_ofs:%"FMT_U32"\r\n",pstats->record_count,ofs_test,next_ofs);
                        iow_fseek(fp,next_ofs);

                        // let user interrupt
                        byte brk=0;
                        brk=iow_get_char(false);
                        if(brk=='\r' || brk=='\n' || brk==CTRL_D)break;

                    }else{TRACE();}// else read error
                }else{
                    pstats->err_bin[E_SYNC]++;
                    if(test<0)break;
                    OUT_ERR("ERR - fread test[%d] fp[%"FMT_U32"] feof[%d] ferr[%d]\r\n",test,iow_ftell(fp), iow_feof(fp), iow_ferror(fp));
                }
            }// end while

            iow_fclose(fp);

            if( (oflags&LINF_REC_COUNTER) !=0)
            OUT_CTTY("\b\b\b\b\b\b%6d",pstats->record_count);

            // sum errors
            int i=0;
            for(i=0;i<EBIN_COUNT;i++){
                pstats->error_count+=pstats->err_bin[i];
            }

            // output summary
            if( ((oflags&LINF_FMT_PRETTY)!=0) ||
               ((oflags&LINF_FMT_FLAGS)==0)){
                OUT_CTTY("\r\nLog Summary [%s]\r\n",fpath);
                OUT_CTTY("file len         : %15"FMT_U32"\r\n",pstats->fsize);
                OUT_CTTY("record count     : %15d\r\n",pstats->record_count);
                char *tstr = striso8601(NULL,pstats->time_bin[T_FIRST],ISO8601_PREC_MSEC);
                OUT_CTTY("time first       : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_FIRST],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_LAST],ISO8601_PREC_MSEC);
                OUT_CTTY("time last        : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_LAST],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_RNGSTART],ISO8601_PREC_MSEC);
                OUT_CTTY("time range start : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_RNGSTART],tstr);
                tstr = striso8601(NULL,pstats->time_bin[T_RNGEND],ISO8601_PREC_MSEC);
                OUT_CTTY("time range end   : %15"FMT_U64"/%s\r\n",pstats->time_bin[T_RNGEND],tstr);
                OUT_CTTY("seq first        : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_FIRST]);
                OUT_CTTY("seq last         : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_LAST]);
                OUT_CTTY("seq range start  : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_RNGSTART]);
                OUT_CTTY("seq range end    : %15"FMT_U32"\r\n",pstats->seq_bin[SEQ_RNGEND]);
                OUT_CTTY("Error Summary    :\r\n");
                OUT_CTTY("error count      : %15d\r\n",pstats->error_count);
                for(i=0;i<EBIN_COUNT;i++){
                    OUT_CTTY("  %14s  %10s%6d\r\n",linfo_ebin_names[i],"",pstats->err_bin[i]);
                }
                OUT_CTTY("Record Summary:\r\n");
                for(i=0;i<RBIN_COUNT;i++){
                    OUT_CTTY("  %14s  %10s%6d\r\n",linfo_rbin_names[i],"",pstats->record_bin[i]);
                }
            }

            if( (oflags&LINF_FMT_CSV)!=0 ){
                if( (oflags&LINF_CSV_HEADER)!=0){
                    OUT_CTTY("file_len,rec_count,tfirst,tlast,tmin,tmax,");
                    OUT_CTTY("seq_first,seq_last,seq_min,seq_max,err_count,");
                    OUT_CTTY("E_SYNC,E_TMZERO,E_TMMONO,E_CTINVAL,E_SEQINVAL,");
                    OUT_CTTY("E_LENINVAL,E_RTID,E_RMID,RMID_INVAL,RMID_NONE,");
                    OUT_CTTY("RMID_SAMPLE,RMID_SCAL,RMID_ZCAL,RTID_INVAL,RTID_ZRECV1\r\n");
                }
                OUT_CTTY("%"FMT_U32",",pstats->fsize);
                OUT_CTTY("%d,",pstats->record_count);

                if( (oflags&LINF_TIME_ISO) !=0){
                    char *tstr = striso8601(NULL,pstats->time_bin[T_FIRST],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_LAST],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_RNGSTART],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                    tstr = striso8601(NULL,pstats->time_bin[T_RNGEND],ISO8601_PREC_MSEC);
                    OUT_CTTY("%s,",tstr);
                }else{
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_FIRST]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_LAST]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_RNGSTART]);
                    OUT_CTTY("%"FMT_U64",",pstats->time_bin[T_RNGEND]);
                }

                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_FIRST]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_LAST]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_RNGSTART]);
                OUT_CTTY("%"FMT_U32",",pstats->seq_bin[SEQ_RNGEND]);
                OUT_CTTY("%d,",pstats->error_count);
                for(i=0;i<EBIN_COUNT;i++){
                    OUT_CTTY("%d,",pstats->err_bin[i]);
                }
                for(i=0;i<RBIN_COUNT;i++){
                    OUT_CTTY("%d%s",pstats->record_bin[i],(i<(RBIN_COUNT-1)?",":"\r\n"));
                }
            }
            retval=ERR_SUCCESS;
        }
    }
    return retval;
}

//return_code_t lu_rcat(char *fpath, rcat_flags oflags)
//{
//    return_code_t retval=ERR_FAILURE;
//#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
//    FILE_T file_s, *fp=&file_s;
//#else
//    FILE_T *fp;
//#endif
//
//    uint32_t ofs_test=0;
//    uint32_t fsize=0;
//    if(NULL!=fpath){
//        // open or die
//        if(iow_fopen(&fp, fpath, IOW_READ)==0){
//            fsize=iow_fsize(fp);
//            iow_fseek(fp,0);
//            // find start condition
//
//            // iterate until end condition...
//
//            // output per format options
//
//            iow_fclose(fp);
//            retval=ERR_SUCCESS;
//        }
//    }
//    return retval;
//}
