///
/// @file licor8xx-shared.c
/// @authors k. headley
/// @date 03 mar 2020

/// Shared (cross-platform) Licor 8xx data API
/// Carbon Waveglider (CWG) Licor data structures and functions

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


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

 Copyright 2002-YYYY 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 <string.h>
#include <time.h>
#include "licor8xx-shared.h"
#include "z85.h"
#include "sequence.h"
#include "iowfile.h"

#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
#include "sequence.h"
#include "cwgsys.h"
#include "ioserial.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
/////////////////////////

licor_config_t g_licor_config={
    LICOR_MODEL_DFL, LICOR_VER_DFL, LICOR_SN_DFL, \
    LI_CFLAG_DFL, LI_OFLAG_DFL, \
    LICOR_OUTRATE_DFL, LICOR_FILTER_DFL, \
    LICOR_REC_DEL_DFL, LICOR_REC_SEP_DFL
};


// NOTE field_names indexed using ZID_
//
static const char *g_zfield_names[ZID_FIELD_COUNT]=
{
    "co2","co2abs",
    "h2o","h2oabs","h2odewpoint",
    "celltemp","cellpres",
    "ivolt","flowrate",
    "raw_co2","raw_co2ref","raw_h2o","raw_h2oref"
};
static const char *g_zextra_names[ZID_EXTRA_COUNT]=
{
    "co2-ep0","co2-ep1",
    "co2-area",
    "MX","SX","SXY"
};

static const licor_id_map_element_t li_rtype_map[]=
{
    {"RTZ0",RTID_ZRECv0},
    {"RTINVAL",RTID_INVALID},
    {NULL,0x00000}
};

static const licor_id_map_element_t li_rmeas_map[]=
{
    {"RMNONE",RMID_NONE},
    {"RMSAMPLE|RMS",RMID_SAMPLE},
    {"RMZCAL|RMZC",RMID_ZCAL},
    {"RMSCAL|RMSC",RMID_SCAL},
    {"RMINVAL",RMID_INVALID},
    {NULL,0x00000}
};

static const licor_id_map_element_t li_mmeas_map[]=
{
    {"MMNONE",MMID_NONE},
    {"MMAIR|MMA",MMID_AIR},
    {"MMSTD|MMST",MMID_STD},
    {"MMZERO|MMZ",MMID_ZERO},
    {"MMPRES|MMPRS",MMID_PRES},
    {"MMPOSTS|MMPOS",MMID_POSTS},
    {"MMPREZ|MMPRZ",MMID_PREZ},
    {"MMPOSTZ|MMPOZ",MMID_POSTZ},
    {"MMPREB|MMPRB",MMID_PREB},
    {"MMPOSTB|MMPOB",MMID_POSTB},
    {"MMBLINE|MMB",MMID_BLINE},
    {"MMSAMPLE|MMSA",MMID_SAMPLE},
    {"MMZCAL|MMZC",MMID_ZCAL},
    {"MMSCAL|MMSC",MMID_SCAL},
    {"MMINVAL",MMID_INVALID},
    {NULL,0x00000}
};

static const licor_meas_def_t li_meas_map[]=
{
    {"NONE",MMID_NONE,0,0,0,0},
    {"AIR",MMID_AIR,MAIR_BLANK_MSEC,MAIR_MEAS_N,MAIR_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"STD",MMID_STD,MSTD_BLANK_MSEC,MSTD_MEAS_N,MSTD_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"ZERO",MMID_ZERO,MZERO_BLANK_MSEC,MZERO_MEAS_N,MZERO_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"PRES",MMID_PRES,MPRES_BLANK_MSEC,MPRES_MEAS_N,MPRES_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"POSTS",MMID_POSTS,MPOSTS_BLANK_MSEC,MPOSTS_MEAS_N,MPOSTS_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"PREZ",MMID_PREZ,MPREZ_BLANK_MSEC,MPREZ_MEAS_N,MPREZ_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"POSTZ",MMID_POSTZ,MPOSTZ_BLANK_MSEC,MPOSTZ_MEAS_N,MPOSTZ_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"PREB",MMID_PREB,MPREB_BLANK_MSEC,MPREB_MEAS_N,MPREB_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"POSTB",MMID_POSTB,MPOSTB_BLANK_MSEC,MPOSTB_MEAS_N,MPOSTB_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"BLINE",MMID_BLINE,MBLINE_BLANK_MSEC,MBLINE_MEAS_N,MBLINE_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"SAMPLE",MMID_SAMPLE,MSAMPLE_BLANK_MSEC,MSAMPLE_MEAS_N,MSAMPLE_MEAS_PERIOD_SEC,MX_CAL_WR_OFF},
    {"ZCAL",MMID_ZCAL,MZCAL_BLANK_MSEC,MZCAL_MEAS_N,MZCAL_MEAS_PERIOD_SEC,MX_CAL_WR_ON},
    {"SCAL",MMID_SCAL,MSCAL_BLANK_MSEC,MSCAL_MEAS_N,MSCAL_MEAS_PERIOD_SEC,MX_CAL_WR_ON},
    {NULL,MMID_INVALID,0,0}
};

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

int licor_map_slookup(const licor_id_map_element_t *map, const char *key, int var)
{
    int retval=-1;

    if(NULL!=map && NULL!=key){
        const licor_id_map_element_t *next=map;

        while(retval<0 && NULL!=next->key){
            char *toks[var];
            char kcpy[strlen(next->key)+1];
            sprintf(kcpy,"%s",next->key);
            int ntoks=0;
            // copy the key for strtok (modifies string)
//            char *kcpy = pstrdup(next->key);


            if((ntoks=tokenize(kcpy,toks,"|",var))>0){
                // iterate over variants, delimited w/ '|'
                // this enables exact match, so that command line
                // args that depend on it can be called in any order
                // e.g. AIR matches MMAIR and AIR using strstr
                // but if MMAIR searched first, would match incorrectly
                char **tok=toks;
                int i=0;
                for(i=0;i<ntoks;i++){
//                    OUT_CTTY("%d - tok[%d][%s] key[%s]\r\n",__LINE__,i,tok[i],key);
                    if(strcmp(tok[i],key)==0){
                        retval=next->id;
                        break;
                    }
                }
            }
            // free copy
//            pfree(kcpy);
            next++;
        }
    }
    return retval;
}

licor_rtype_id licor_rtid_slookup(const char *key)
{
    licor_rtype_id retval=(licor_rtype_id)licor_map_slookup(li_rtype_map,key,2);
    return retval;
}
licor_rmeas_id licor_rmid_slookup(const char *key)
{
    licor_rmeas_id retval=(licor_rmeas_id)licor_map_slookup(li_rmeas_map,key,2);
    return retval;
}
licor_mmeas_id licor_mmid_slookup(const char *key)
{
    licor_mmeas_id retval=(licor_mmeas_id)licor_map_slookup(li_mmeas_map,key,2);
    return retval;
}

const licor_meas_def_t *licor_meas_slookup(const char *key)
{
    const licor_meas_def_t *retval=NULL;
    if(NULL!=key){
        const licor_meas_def_t *next=li_meas_map;
        while(NULL!=next && NULL!=next->key){
            if(strcmp(next->key,key)==0){
                retval=next;
                break;
            }
            next++;
        }
    }
    return retval;
}
const licor_meas_def_t *licor_meas_ilookup(int idx)
{
    const licor_meas_def_t *retval=NULL;
    if(idx>=0 && idx<MMID_COUNT){
        retval = &li_meas_map[idx];
    }
    return retval;
}
void licor_meas_def_show(const licor_meas_def_t *self, uint16_t indent)
{
    if(NULL!=self){
        int wkey=12;
        int wval=20;
        OUT_CTTY("%*s%*s  %*s\r\n",indent,"",wkey,"key",wval,self->key);
        OUT_CTTY("%*s%*s  %*"FMT_U16"\r\n",indent,"",wkey,"mmid",wval,self->mmid);
        OUT_CTTY("%*s%*s  %*"FMT_U32"\r\n",indent,"",wkey,"blank_ms",wval,self->blank_ms);
        OUT_CTTY("%*s%*s  %*"FMT_U32"\r\n",indent,"",wkey,"meas_n",wval,self->meas_n);
        OUT_CTTY("%*s%*s  %*.3f\r\n",indent,"",wkey,"period_s",wval,self->period_s);
        OUT_CTTY("%*s%*s  %*"FMT_U16"\r\n",indent,"",wkey,"cal_wr",wval,self->cal_wr);
    }
}

int licor_logrechdr_show(log_rec_header_t *self, bool verbose, int indent)
{
    int wbytes=0;
    if( NULL!=self){
        wbytes+=OUT_CTTY("%*s%12s  %13p%s",    indent,(indent>0?" ":""),"self", self,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %5s%08"FMT_U32"%s",indent,(indent>0?" ":""),"sync","",self->sync,LINEEND);
        char *tstr=striso8601(NULL,self->timestamp,ISO8601_PREC_MSEC);
        wbytes+=OUT_CTTY("%*s%12s  %13"FMT_U64"/%s%s",  indent,(indent>0?" ":""),"timestamp",self->timestamp,tstr,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13"FMT_U32"%s",   indent,(indent>0?" ":""),"seq_n",self->seq_n,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13"FMT_U32"%s",   indent,(indent>0?" ":""),"cycle_id",self->cycle_id,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %9s%04X%s", indent,(indent>0?" ":""),"type_id","",self->type_id,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %9s%04X%s", indent,(indent>0?" ":""),"meas_id","",self->meas_id,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13"FMT_U16"%s",   indent,(indent>0?" ":""),"meas_count",self->meas_count,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13"FMT_U32"%s",   indent,(indent>0?" ":""),"len",self->len,LINEEND);
    }else(TRACE());
    return wbytes;
}

int licor_record_bytes_outx(byte *src, size_t len, sys_hexfmt_flag flags, int indent, char *term)
{
    int wbytes=0;

    if( NULL!=src ){
        byte *cur=(byte *)src;
        int wbytes=0;

        if(flags&XFMT_PRETTY){
            hex_show(cur, len, 16, indent, ((flags&XFMT_OFFSET)!=0));
        }else{
            char *fmt = ((flags&XFMT_UCASE)!=0 ? "%02X" : "%02x");
            while(cur < ((byte *)src+len)) {
                wbytes+=OUT_CTTY(fmt,*cur++);
            }
            if(NULL!=term){
                wbytes+=OUT_CTTY("%s",term);
            }
        }
    }
    return wbytes;
}

int licor_zmdata_show(mdata_Z_t *self, bool verbose, int indent)
{
    int wbytes=0;
    if( NULL!=self){
        mdata_Z_t *mdata=self;

        wbytes+=OUT_CTTY("%*s%12s  %13p%s",    indent,(indent>0?" ":""),"self", self,LINEEND);
        char *tstr=striso8601(NULL,mdata->timestamps[0],ISO8601_PREC_MSEC);
        wbytes+=OUT_CTTY("%*s%12s  %13"FMT_U64"/%s%s",  indent,(indent>0?" ":""),"timestamp[0]",mdata->timestamps[0],tstr,LINEEND);
        tstr=striso8601(NULL,mdata->timestamps[1],ISO8601_PREC_MSEC);
        wbytes+=OUT_CTTY("%*s%12s  %13"FMT_U64"/%s%s",  indent,(indent>0?" ":""),"timestamp[1]",mdata->timestamps[1],tstr,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %9s%04X%s", indent,(indent>0?" ":""),"meas_id","",mdata->meas_id,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13d%s",    indent,(indent>0?" ":""),"stat_n",mdata->stat_n,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %8s%5.2f%s",    indent,(indent>0?" ":""),"period","",mdata->period,LINEEND);
        if(verbose){
            OUT_CTTY("%*s%13s  %41s%s",indent,(indent>0?" ":""),"# min/max/M/S", "",LINEEND);
            int j=0;
            for(j=0;j<ZID_FIELD_COUNT;j++){
                wbytes+=OUT_CTTY("%*s%12s %+.3e %+.3e %+.3e %+.3e%s",indent,(indent>0?" ":""), g_zfield_names[j],mdata->min[j],mdata->max[j],mdata->M[j],XM_SDEV(mdata,j),LINEEND);
            }

            wbytes+=OUT_CTTY("%*s%12s %11s%s",indent,(indent>0?" ":""),"# Metrics", "",LINEEND);
            for(j=0;j<ZID_EXTRA_COUNT;j++){
                wbytes+=OUT_CTTY("%*s%12s %+.3e %s",indent,(indent>0?" ":""), g_zextra_names[j],mdata->X[j],LINEEND);
            }
            wbytes+=OUT_CTTY("%*s%12s %11s%s",indent,(indent>0?" ":""),"# CO2 Trend", "",LINEEND);
            wbytes+=OUT_CTTY("%*s%12s %+.3e %s",indent,(indent>0?" ":""), "co2_m",ZMBLINE_m(self),LINEEND);
            wbytes+=OUT_CTTY("%*s%12s %+.3e %s",indent,(indent>0?" ":""), "co2_b",ZMBLINE_b(self),LINEEND);
            wbytes+=OUT_CTTY("%*s%12s %+.3e %s",indent,(indent>0?" ":""), "co2_R",ZMBLINE_r(self),LINEEND);
            wbytes+=OUT_CTTY("%*s%12s %+.3e %s",indent,(indent>0?" ":""), "co2_R^2",ZMBLINE_r2(self),LINEEND);
            mdata++;
        }
    }
    return wbytes;
}

int licor_zrecord_show(licor_zrecord_t *self, bool verbose, int indent)
{
    int wbytes=0;

    if( NULL!=self){

        log_rec_header_t *hdr=&self->hdr;
        wbytes+=licor_logrechdr_show(hdr,verbose,indent);

        mdata_Z_t *mdata=LI_ZREC_PMDATA(self);//&self->mdata[0];

        int i=0;
        for(i=0;i<self->hdr.meas_count;i++){
            wbytes+=OUT_CTTY("%*s%13s  %12d%s",   indent,(indent>0?" ":""),"# Measurement",i,LINEEND);
            wbytes+=licor_zmdata_show(mdata, verbose, indent);
            mdata++;
        }
    }else(TRACE());
    return wbytes;
}

int licor_zrecord_outb(licor_zrecord_t *self, char *term)
{
    int retval=0;

    if( NULL!=self ){

        byte *cur = (byte *)self;
        size_t chunk_size=64;
        size_t ebuf_sz=5*chunk_size/4+1;
        int elen=chunk_size;
        byte data[chunk_size];
        char encoded[ebuf_sz];

        size_t len = LI_ZREC_SIZE(self);
        size_t dlen = len;
        size_t encode_len=0;
        size_t pad_len=0;

        while(cur < ((byte *)self+len) ){
            ZERO_MEM(data,chunk_size);
            size_t rem=((byte *)self+len)-cur;
            if(rem>=chunk_size){
                elen = chunk_size;
                dlen = chunk_size;
            }else{
                elen = rem+(4-rem%4);
                dlen = rem;
                // truncate 1 byte for each padding byte
                pad_len=elen-dlen;
                //                   DPRINT("encoding data dlen[%zu] elen[%zu] len[%zu] rem[%zu] pad[%zu]\r\n",dlen,elen,len,rem,pad_len);
            }
            ZERO_MEM(data,chunk_size);
            ZERO_MEM(encoded,ebuf_sz);
            memcpy(data,cur,dlen);
            //                   DPRINT("encoding data dlen[%zu] elen[%zu] len[%zu] rem[%zu] pad[%zu]\r\n",dlen,elen,len,rem,pad_len);
            Z85_encode(encoded,data,elen);
            encoded[strlen(encoded)-pad_len]='\0';
            // truncate bytes generated by padding
            encode_len+=strlen(encoded);
            retval+=OUT_CTTY("%s",encoded);
            cur+=elen;
        }
        if(NULL!=term){
            retval+=OUT_CTTY("%s",term);
        }
        //           DPRINT("len[%zu] enclen[%zu]\r\n",len,encode_len);
    }
    return retval;
}

int licor_zmdata_outc(mdata_Z_t *self, queue_output_flag oflags, char *del, char *term)
{
    int wbytes=0;
    if( NULL!=self && NULL!=del){

        mdata_Z_t *mdata=self;
        // output header
        wbytes+=OUT_CTTY("%"FMT_U64"%s",mdata->timestamps[0],del);
        wbytes+=OUT_CTTY("%"FMT_U64"%s",mdata->timestamps[1],del);
        wbytes+=OUT_CTTY("%04X%s",mdata->meas_id,del);
        wbytes+=OUT_CTTY("%.2f%s",mdata->period,del);
        wbytes+=OUT_CTTY("%d%s",mdata->stat_n,del);
        // output fields
        if( (oflags&QOF_CSVX)!=0 ){
                // mixed output mode: CSV using ASCII hex fields
            int j=0;
            char xstr[32]={0};
            for(j=0;j<ZID_FIELD_COUNT;j++){
                ZERO_MEM(xstr,32);
                if(bytes_to_xstr((byte *)&mdata->min[j], xstr, sizeof(float), 32, (XFMT_UCASE))>0){
                    wbytes+=OUT_CTTY("%s%s",xstr,del);
                }
                ZERO_MEM(xstr,32);
                if(bytes_to_xstr((byte *)&mdata->max[j], xstr, sizeof(float), 32, (XFMT_UCASE))>0){
                    wbytes+=OUT_CTTY("%s%s",xstr,del);
                }
                ZERO_MEM(xstr,32);
                if(bytes_to_xstr((byte *)&mdata->M[j], xstr, sizeof(float), 32, (XFMT_UCASE))>0){
                    wbytes+=OUT_CTTY("%s%s",xstr,del);
                }
                ZERO_MEM(xstr,32);
                float sdev=XM_SDEV(mdata,j);
                if(bytes_to_xstr((byte *)&sdev, xstr, sizeof(float), 32, (XFMT_UCASE))>0){
                    wbytes+=OUT_CTTY("%s%s",xstr,del);
                }
            }
            for(j=0;j<ZID_EXTRA_COUNT;j++){
                ZERO_MEM(xstr,32);
                if(bytes_to_xstr((byte *)&mdata->X[j], xstr, sizeof(float), 32, (XFMT_UCASE))>0){
                    wbytes+=OUT_CTTY("%s%s",xstr,del);
                }
            }
            float line_m=ZMBLINE_m(self);
            float line_b=ZMBLINE_b(self);
            float line_r2=ZMBLINE_r2(self);
            ZERO_MEM(xstr,32);
            if(bytes_to_xstr((byte *)&line_m, xstr, sizeof(float), 32, (XFMT_UCASE))>0){
                wbytes+=OUT_CTTY("%s%s",xstr,del);
            }
             ZERO_MEM(xstr,32);
            if(bytes_to_xstr((byte *)&line_b, xstr, sizeof(float), 32, (XFMT_UCASE))>0){
                wbytes+=OUT_CTTY("%s%s",xstr,del);
            }
             ZERO_MEM(xstr,32);
            if(bytes_to_xstr((byte *)&line_r2, xstr, sizeof(float), 32, (XFMT_UCASE))>0){
                wbytes+=OUT_CTTY("%s",xstr);
            }
        }else{
            // CSV
            mdata_Z_t *mdata=self;
            wbytes+=OUT_CTTY("%"FMT_U64"%s",mdata->timestamps[0],del);
            wbytes+=OUT_CTTY("%"FMT_U64"%s",mdata->timestamps[1],del);
            wbytes+=OUT_CTTY("%04X%s",mdata->meas_id,del);
            wbytes+=OUT_CTTY("%.2f%s",mdata->period,del);
            wbytes+=OUT_CTTY("%d%s",mdata->stat_n,del);

            int j=0;
            for(j=0;j<ZID_FIELD_COUNT;j++){
                wbytes+=OUT_CTTY("%+.3e%s",mdata->min[j],del);
                wbytes+=OUT_CTTY("%+.3e%s",mdata->max[j],del);
                wbytes+=OUT_CTTY("%+.3e%s",mdata->M[j],del);
                wbytes+=OUT_CTTY("%+.3e%s",XM_SDEV(mdata,j),del);
            }
            for(j=0;j<ZID_EXTRA_COUNT;j++){
                wbytes+=OUT_CTTY("%+.3e%s",mdata->X[j],del);
            }
            wbytes+=OUT_CTTY("%+.3e%s",ZMBLINE_m(self),del);
            wbytes+=OUT_CTTY("%+.3e%s",ZMBLINE_b(self),del);
    //        wbytes+=OUT_CTTY("%+.3e%s",ZMBLINE_r(self),del);
            wbytes+=OUT_CTTY("%+.3e",ZMBLINE_r2(self));
        }

        if(NULL!=term)
            wbytes+=OUT_CTTY("%s",term);
        mdata++;

    }
    return wbytes;
}

int licor_zrecord_outc(licor_zrecord_t *self, queue_output_flag oflags, char *del, char *term)
{
    int wbytes=0;

    if( NULL!=self && NULL!=del ){
        log_rec_header_t *hdr=&self->hdr;

        wbytes+=OUT_CTTY("%"FMT_U64"%s", hdr->timestamp,del);
        wbytes+=OUT_CTTY("%"FMT_U32"%s",hdr->seq_n,del);
        wbytes+=OUT_CTTY("%"FMT_U32"%s",hdr->cycle_id,del);
        wbytes+=OUT_CTTY("%04X%s",hdr->type_id,del);
        wbytes+=OUT_CTTY("%04X%s",hdr->meas_id,del);
        wbytes+=OUT_CTTY("%u%s",hdr->meas_count,del);
        wbytes+=OUT_CTTY("%"FMT_U32"%s",hdr->len,del);
        mdata_Z_t *mdata=LI_ZREC_PMDATA(self);//self->mdata;

        int i=0;
        for(i=0;i<self->hdr.meas_count;i++){
            wbytes+=licor_zmdata_outc(mdata, oflags, del, NULL);
            if(i<(self->hdr.meas_count-1)){
                wbytes+=OUT_CTTY("%s",del);
            }
            mdata++;
        }
        if(NULL!=term)
            wbytes+=OUT_CTTY("%s",term);
     }
    return wbytes;
}

licor_zrecord_t *licor_parse_csv(licor_zrecord_t *dest, size_t len, char *src)
{
    licor_zrecord_t *retval=NULL;
    if(NULL!=src){
        licor_zrecord_t *zrec=NULL;
        size_t zlen=len;
        log_rec_header_t zhdr_s={0},*hdr=&zhdr_s;
        hdr->sync=LI_ZREC_SYNC;

        // time,seq,cycle,tid,mid,meas_n,len

        if(sscanf(src,"%"FMT_U64",%"FMT_U32",%"FMT_U32",%04"FMT_X16",%04"FMT_X16",%"FMT_U16",%"FMT_U32"",&hdr->timestamp,
                &hdr->seq_n,&hdr->cycle_id,&hdr->type_id,&hdr->meas_id,&hdr->meas_count,&hdr->len)!=7){
            // parse error
            TRACE();
        }

        zlen = (LI_ZREC_SIZEN(hdr->meas_count)>len ? LI_ZREC_SIZEN(hdr->meas_count) : len);
        if(NULL==dest){
            zrec = (licor_zrecord_t *)malloc(zlen);
            if(NULL!=zrec){
                memset(zrec,0,zlen);
                memcpy(zrec,hdr,LI_ZREC_HDR_LEN);
            }
        }else{
            if(zlen<=len){
                zrec=dest;
                memcpy(zrec,hdr,LI_ZREC_HDR_LEN);
            }else{
                // dest too small
                zrec=NULL;
            }
        }

        if(NULL!=zrec){
            // update header
            hdr=(log_rec_header_t *)zrec;
            int err=0;

            // input cursor
            char *cur[3] = {src,NULL,(src+strlen(src))};
            // get mdata pointer
            mdata_Z_t *mdata=LI_ZREC_PMDATA(zrec);
            // parse mdata
            uint16_t i=0;

            while(i<6){
                // init cursor after record metadata
                cur[1]=strstr(cur[0],",");
                if(cur[1]==NULL){
                    err++;
                    TRACE();
                    break;
                }
                cur[0]=cur[1]+1;
                i++;
            }

            int rf=0;
            for(i=0;err==0 && i<hdr->meas_count && cur[0]<cur[2];i++){

//                while(err==0){
//                    OUT_ERR("meas[%u] rf[%d] cur[0][",i,rf);
//                    char *px=cur[0];
//                    while(*px!=',' && *px!='\0'){
//                        OUT_ERR("%c",*px);
//                        px++;
//                    }
//                    OUT_ERR("]\r\n");

                int j=0;
                    for(j=0;j<5;j++){
                        // metadata
                        switch(j){
                            case 0:
                                err += (sscanf(cur[0],"%"FMT_U64"",&mdata[i].timestamps[0])==1 ? 0 : 1);
                                break;
                            case 1:
                                err += (sscanf(cur[0],"%"FMT_U64"",&mdata[i].timestamps[1])==1 ? 0 : 1);
                                break;
                            case 2:
                                err += (sscanf(cur[0],"%04"FMT_X16"",&mdata[i].meas_id)==1 ? 0 : 1);
                                break;
                            case 3:
                                err += (sscanf(cur[0],"%f",&mdata[i].period)==1 ? 0 : 1);
                                break;
                            case 4:
                                err += (sscanf(cur[0],"%"FMT_D16"",&mdata[i].stat_n)==1 ? 0 : 1);
                                break;
                            default:
                                err++;
                                TRACE();
                                break;
                        }
                        rf++;
                        cur[1]=strstr(cur[0],",");
                        if(NULL==cur[1]){
                            err++;
                            TRACE();
                            break;
                        }
                        cur[0]=cur[1]+1;
                    }

                    for(j=0;j<ZID_FIELD_COUNT;j++){
                        int k=0;
                        for(k=0;k<4;k++){
                             // ZID_FIELDS : min,max,M,S
                            float val=0.0;
                            if(sscanf(cur[0],"%e",&val)==1){
                                switch(k){
                                    case 0:
                                        mdata[i].min[j]=val;
                                        break;
                                    case 1:
                                        mdata[i].max[j]=val;
                                        break;
                                    case 2:
                                        mdata[i].M[j]=val;
                                        break;
                                    case 3:
                                        mdata[i].S[j]=XM_SDEV_TO_S(mdata,val);
                                        break;
                                    default:
                                        err++;
                                        TRACE();
                                        break;
                                }
                            }else{
                                err++;
                            }
                            rf++;
                            cur[1]=strstr(cur[0],",");
                            if(NULL==cur[1]){
                                err++;
                                TRACE();
                                break;
                            }
                            cur[0]=cur[1]+1;
                        }
                    }

                    for(j=0;j<ZID_EXTRA_COUNT;j++){
                         // extra fields
                        if(sscanf(cur[0],"%e",&mdata[i].X[j])!=1){
                            err++;
                            TRACE();
                        }
                        rf++;
                        cur[1]=strstr(cur[0],",");
                        if(NULL==cur[1]){
                            err++;
                            TRACE();
                            break;
                        }
                        cur[0]=cur[1]+1;
                    }

                    for(j=0;j<3;j++){
                        rf++;
                       // skip derived fields
                        cur[1]=strstr(cur[0],",");
                        if(NULL==cur[1]){
                            if(j<2){
                                err++;
                                TRACE();
                            }
                            break;
                        }
                        cur[0]=cur[1]+1;
                    }

//                }// while mf

//                 OUT_ERR("cur{%p,%p,%p} rf[%03d] err[%d]\r\n",cur[0]==NULL?0:cur[0],cur[1]==NULL?0:cur[1],cur[2]==NULL?0:cur[2],rf,err);
           }// for meas_count

            // set return value
            if(err==0){
                retval=zrec;
            }else{

                OUT_ERR("src[%p] cur{%p,%p,%p} err[%d]\r\n",src,cur[0]==NULL?0:cur[0],cur[1]==NULL?0:cur[1],cur[2]==NULL?0:cur[2],err);
            }
            /*
            wbytes+=OUT_CTTY("%"FMT_U64"%s",mdata->timestamps[0],del);
            wbytes+=OUT_CTTY("%"FMT_U64"%s",mdata->timestamps[1],del);
            wbytes+=OUT_CTTY("%04X%s",mdata->meas_id,del);
            wbytes+=OUT_CTTY("%.2f%s",mdata->period,del);
            wbytes+=OUT_CTTY("%d%s",mdata->stat_n,del);

            int j=0;
            for(j=0;j<ZID_FIELD_COUNT;j++){
                wbytes+=OUT_CTTY("%+.3e%s",mdata->min[j],del);
                wbytes+=OUT_CTTY("%+.3e%s",mdata->max[j],del);
                wbytes+=OUT_CTTY("%+.3e%s",mdata->M[j],del);
                wbytes+=OUT_CTTY("%+.3e%s",XM_SDEV(mdata,j),del);
            }
            for(j=0;j<ZID_EXTRA_COUNT;j++){
                wbytes+=OUT_CTTY("%+.3e%s",mdata->X[j],del);
            }
            */
        }else{TRACE();}// else zrec NULL
    }else{TRACE();}// else invalid arg
    return retval;
}

int licor_zrecord_outr(licor_zrecord_t *self, char *term)
{
    int wbytes=0;

    if( NULL!=self ){
        size_t len = LI_ZREC_SIZE(self);
        byte *cur=(byte *)self;
        int wbytes=0;

        while(cur < ((byte *)self+len)) {
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
            // can't use OUT_CTTY b/c it stops processing at NULL
            // wait for room in TX buffer
            while(serIsTxFull(SER_CTTY))Nop();
            // send byte
            wbytes+= (serPutChar(SER_CTTY,(char)*cur) ? 1 : 0);
#else
            // write byte
            wbytes+= (iow_fwrite(SER_CTTY,cur,1) ? 1 : 0);
#endif
            cur++;
        }
        if(NULL!=term){
            wbytes+=OUT_CTTY("%s",term);
        }
    }
    return wbytes;
}

int licor_zrecord_outs(licor_zrecord_t *self, queue_output_flag oflags)
{

    int retval=0;
//    DPRINT("outs - self[%p] oflags[%08"FMT_FLAG"]\r\n",self,oflags);
    if(NULL!=self){
        if(oflags&QOF_PRETTY){
           if( (oflags&QOF_HEADER)!=0 && (oflags&QOF_DATA)!=0){
                retval+=licor_zrecord_show(self, true, 5);
            }else{
               if( (oflags&QOF_HEADER)!=0 ){
                    retval+=licor_logrechdr_show((log_rec_header_t *)self,true,5);
                }
                if( (oflags&QOF_DATA)!=0 ){
                   int i=0;
                    mdata_Z_t *mdata = LI_ZREC_PMDATA(self);
                    for(i=0;i<self->hdr.meas_count;i++)
                        retval+=licor_zmdata_show(&mdata[i],true,5);
                }
            }
        }
        if(oflags&QOF_BASE85){
            retval+=licor_zrecord_outb(self,LINEEND);
        }
        if(oflags&QOF_CSV){
            retval+=licor_zrecord_outc(self,oflags,",",LINEEND);
        }
        if(oflags&QOF_PRETTY_HEX){
            retval+=licor_zrecord_outx(self,XFMT_PRETTY|XFMT_OFFSET|XFMT_UCASE,0,LINEEND);
        }
        if(oflags&QOF_HEX){
            retval+=licor_zrecord_outx(self,XFMT_UCASE,0,LINEEND);
        }
        if(oflags&QOF_RAW){
            retval+=licor_zrecord_outr(self,LINEEND);
        }
    }

    return retval;
}

int licor_zrecord_outx(licor_zrecord_t *self, sys_hexfmt_flag flags, int indent, char *term)
{
    int wbytes=0;

    if( NULL!=self ){
        wbytes = licor_record_bytes_outx((byte *)self, (size_t) LI_ZREC_SIZE(self), flags, indent, term);
    }
    return wbytes;
}

licor_orecord_t *licor_parse_raw(licor_orecord_t *dest, char *src, uint64_t tstamp)
{
    licor_orecord_t *retval=NULL;

    if(NULL!=dest && NULL!=src){
        licor_orecord_t *pr=dest;
        char *px=strstr(src,"<data>");
        char *py=NULL;
        char *pz=NULL;

        if(NULL!=px){
//            ZERO_MEM(pr,sizeof(licor_orecord_t));
            // take timestamp
//            DPRINT("%s - tstamp[%llu]\r\n",__FUNCTION__,tstamp);
            pr->timestamp = tstamp;

//            DPRINT("%s - oflags[%0lX]\r\n",__FUNCTION__,pr->oflags);

            if( (py=strstr(px,"<co2>"))!=NULL){
                pr->fields[FID_CO2] = atof(py+5);
            }
            if( (py=strstr(px,"<co2abs>"))!=NULL){
                pr->fields[FID_CO2ABS] = atof(py+8);
            }
            if( (py=strstr(px,"<h2o>"))!=NULL){
                pr->fields[FID_H2O] = atof(py+5);
            }
            if( (py=strstr(px,"<h2oabs>"))!=NULL){
                pr->fields[FID_H2OABS] = atof(py+8);
            }
            if( (py=strstr(px,"<h2odewpoint>"))!=NULL){
                pr->fields[FID_H2ODEWPOINT] = atof(py+13);
            }
            if( (py=strstr(px,"<cellpres>"))!=NULL){
                pr->fields[FID_CELLPRES] = atof(py+10);
            }
            if( (py=strstr(px,"<celltemp>"))!=NULL){
                pr->fields[FID_CELLTEMP] = atof(py+10);
            }
            if( (py=strstr(px,"<ivolt>"))!=NULL){
                pr->fields[FID_IVOLT] = atof(py+7);
            }
            if( (py=strstr(px,"<flowrate>"))!=NULL){
                pr->fields[FID_FLOWRATE] = atoi(py+10);
            }

            if( (py=strstr(px,"<raw>"))!=NULL){
                if( (pz=strstr(py,"<h2o>"))!=NULL){
                    pr->fields[FID_RAW_H2O] = atof(pz+5);
                }
                if( (pz=strstr(py,"<h2oref>"))!=NULL){
                    pr->fields[FID_RAW_H2OREF] = atof(pz+8);
                }
                if( (pz=strstr(py,"<co2>"))!=NULL){
                    pr->fields[FID_RAW_CO2] = atof(pz+5);
                }
                if( (pz=strstr(py,"<co2ref>"))!=NULL){
                    pr->fields[FID_RAW_CO2REF] = atof(pz+8);
                }
            }
            retval = dest;
        }else{
            EMSG("data tag missing\r\n");
        }
    }
     return retval;
}
#define LOG_TIMESTAMP_BYTES 24
#define LL2POSIXDS(ll, dest) sprintf(dest,"%.3lf",(ll/1000.0))

int licor_orecord_show(licor_orecord_t *self, bool verbose, int indent)
{
    int wbytes=0;

    if( NULL!=self){
        wbytes+=OUT_CTTY("%*s%12s %14p%s",    indent,(indent>0?" ":""),"self", self,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s %14llu/%s%s",  indent,(indent>0?" ":""),"timestamp",self->timestamp,striso8601(NULL,self->timestamp,ISO8601_PREC_MSEC),LINEEND);
        wbytes+=OUT_CTTY("%*s%12s %14"FMT_U32"%s",  indent,(indent>0?" ":""),"seq_n",self->seq_n,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s %14"FMT_U32"%s",  indent,(indent>0?" ":""),"cycle_id",self->cycle_id,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s %14"FMT_U16"%s",  indent,(indent>0?" ":""),"meas_n",self->meas_n,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s %14"FMT_U16"%s",  indent,(indent>0?" ":""),"ens_n",self->ens_n,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s %14"FMT_U16"%s",  indent,(indent>0?" ":""),"ens_len",self->ens_len,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s %10s%04X%s",  indent,(indent>0?" ":""),"id","",self->id,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s %14e%s",  indent,(indent>0?" ":""),"period_s",self->period_s,LINEEND);
        if(verbose){
            int i=0;
            for(i=0;i<FID_LICOR_FIELDS;i++)
                wbytes+=OUT_CTTY("%*s%12s %14e%s",indent,(indent>0?" ":""), g_zfield_names[i],self->fields[i],LINEEND);
        }
     }else(TRACE());
    return wbytes;
}

int licor_orecord_str(char *dest, licor_orecord_t *src, queue_output_flag fflags)
{
    int retval=-1;

    if(NULL!=src && NULL!=dest){
        char *dp = dest;
        const char *pdel="";
        int rbytes=0;

        // TODO: pass arg, get cfg or replace oflags
        uint32_t oflags=0xFFFFFFFFUL;

        // generate timestamps, per config
        // may be ISO and/or POSIX
        // if both, ISO first (arbitrary)
        char tstamp_str[LOG_TIMESTAMP_BYTES*2]={0};
        uint64_t tems=src->timestamp;

//            DPRINT("%s - tems[%llu]\r\n",__FUNCTION__,tems);

        if(ISSET(g_licor_config.oflags,LOF_TS_ISO8601)){
            time_t tt = (time_t)(tems/1000llu);
            struct tm *pt = gmtime(&tt);
            TM2ISO8601(pt,tstamp_str);
        }

        if(ISSET(g_licor_config.oflags,LOF_TS_POSIX)){
            char *pp = tstamp_str+strlen(tstamp_str);
            if(ISSET(g_licor_config.oflags,LOF_TS_ISO8601)){
                pdel=g_licor_config.rec_del;
                int i=0;
                if( (i=sprintf(pp,"%s",pdel))>0){
                    pp+=i;
                }
            }

            LL2POSIXDS(tems,pp);
        }

        if(strlen(tstamp_str)>0 &&
                ((rbytes=(sprintf(dp,"%s",tstamp_str)))>0)){
            dp+=rbytes;
            pdel=g_licor_config.rec_del;
        }

        if( (rbytes=sprintf(dp,"%s%"FMT_U32"",pdel,src->seq_n))>0){
            dp+=rbytes;
        }
        if( (rbytes=sprintf(dp,"%s%"FMT_U32"",pdel,src->cycle_id))>0){
            dp+=rbytes;
        }
        if( (rbytes=sprintf(dp,"%s%"FMT_U16"",pdel,src->meas_n))>0){
            dp+=rbytes;
        }
        if( (rbytes=sprintf(dp,"%s%"FMT_U16"",pdel,src->ens_n))>0){
            dp+=rbytes;
        }
        if( (rbytes=sprintf(dp,"%s%"FMT_U16"",pdel,src->ens_len))>0){
            dp+=rbytes;
        }
        if( (rbytes=sprintf(dp,"%s%04X",pdel,src->id))>0){
            dp+=rbytes;
        }
        double dval =src->period_s;
        if( (rbytes=sprintf(dp,"%s%-e",pdel,dval))>0){
            dp+=rbytes;
        }

        if( (fflags&QOF_CSVX)!=0 ){
            // mixed output mode: CSV using ASCII hex fields
            char xstr[256]={0};
            bytes_to_xstr((byte *)src, xstr, sizeof(licor_orecord_t), 256, (XFMT_UCASE|XFMT_CRC8) );
            if( (rbytes=sprintf(dp,"%s%s",pdel,xstr))>0){
                dp+=rbytes;
            }
            pdel=g_licor_config.rec_del;

        }else{
            // CSV
            int i=0;
            for(i=0;i<FID_LICOR_FIELDS;i++){
                if(ISSET(oflags,(1 << i))){
                    dval = src->fields[i];
                    if( (rbytes=sprintf(dp,"%s%e",pdel,dval))>0){
                        dp+=rbytes;
                    }
                    pdel=g_licor_config.rec_del;
                }
            }
        }

        // compute and append string length
        // including length field
        int odigits=0;
        int j=strlen(dest);
        while(j>0){
            odigits++;
            j/=10;
        }

        if( (rbytes=sprintf(dp,"%s%d",pdel,(int)(odigits+strlen(dest)+1)))>0){
            dp+=rbytes;
        }else{TRACE();}

        retval = strlen(dest)+1;

   }
    return retval;
}

int licor_orecord_outs(licor_orecord_t *self, queue_output_flag oflags)
{

    int retval=0;

    if(NULL!=self){
        // save terminal
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
        SerPort sav_tty = g_ctty;
        SerPort dev[2]={SER_INVALID,SER_INVALID};
#else
#define SER_INVALID NULL
        FILE *sav_tty = stderr;
        FILE *dev[2]={stderr,NULL};
        FILE *g_ctty = stderr;

#endif

        // set terminal for output per oflags
        if( (oflags&QOF_CONSOLE)!=0 ){
           dev[0]=SER_CONS;
        }
        if( (oflags&QOF_HOST)!=0 ){
            dev[1]=SER_HOST;
        }
        int i=0;
        for(i=0;i<2;i++){
            if(dev[i]!=SER_INVALID){
                g_ctty =  dev[i];
                if(oflags&QOF_PRETTY){
                       retval+=licor_orecord_show(self, true, 2);
                }
                if(oflags&QOF_BASE85){
                    retval+=licor_orecord_outb(self,LINEEND);
                }
                if(oflags&QOF_CSV){
                   retval+=licor_orecord_outc(self,oflags,",",LINEEND);
                }
                if(oflags&QOF_PRETTY_HEX){
                    retval+=licor_orecord_outx(self,XFMT_PRETTY|XFMT_OFFSET|XFMT_UCASE,0,LINEEND);
                }
                if(oflags&QOF_HEX){
                    retval+=licor_orecord_outx(self,XFMT_UCASE,0,LINEEND);
                }
                if(oflags&QOF_RAW){
                    retval+=licor_orecord_outr(self,NULL);
                }
                // wait for output to finish
                // (otherwise, may switch and output garbage)
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
                serTxWait(g_ctty);
#endif
            }// IF dev valid
        }// FOR dev
        // restore tty
        g_ctty = sav_tty;
    }

    return retval;
}

int licor_orecord_outb(licor_orecord_t *self, char *term)
{
    int retval=0;

    if( NULL!=self ){
        char dest[256]={0};
        char *xstr=Z85_encode (dest, (byte *)self, sizeof(licor_orecord_t));
        if(NULL!=xstr){
            retval+=OUT_CTTY("%s%s",xstr,(NULL!=term ? term : ""));
        }
     }
    return retval;
}

int licor_orecord_outc(licor_orecord_t *self, queue_output_flag oflags, char *del, char *term)
{
    int wbytes=0;

    if( NULL!=self && NULL!=del ){
        char obuf[256]={0};
        licor_orecord_str(obuf,self,oflags);
        wbytes+=OUT_CTTY("%s",obuf);

        if(NULL!=term){
            wbytes+=OUT_CTTY("%s",term);
        }
    }
    return wbytes;
}

int licor_orecord_outr(licor_orecord_t *self, char *term)
{
    int wbytes=0;

    if( NULL!=self ){
        size_t len = sizeof(licor_orecord_t);
        byte *cur=(byte *)self;
        int wbytes=0;

        while(cur < ((byte *)self+len)) {
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
            // can't use OUT_CTTY b/c it stops processing at NULL
            // wait for room in TX buffer
            while(serIsTxFull(SER_CTTY))Nop();
            // send byte
            wbytes+= (serPutChar(SER_CTTY,(char)*cur) ? 1 : 0);
#else
            // write byte
            wbytes+= (iow_fwrite(SER_CTTY,cur,1) ? 1 : 0);
#endif
            cur++;
        }
        if(NULL!=term){
            wbytes+=OUT_CTTY("%s",term);
        }
    }
    return wbytes;
}

int licor_orecord_outx(licor_orecord_t *self, sys_hexfmt_flag flags, int indent, char *term)
{
    int wbytes=0;

    if( NULL!=self ){
        wbytes = licor_record_bytes_outx((byte *)self, (size_t) sizeof(licor_orecord_t), flags, indent, term);
    }
    return wbytes;
}
