///
/// @file log.c
/// @authors k. Headley
/// @date 05 dec 2019

/// Log implementation


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


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

 Copyright 2002-2019 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
/////////////////////////

/////////////////////////
// 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 "carbonwg"

/// @def COPYRIGHT
/// @brief header software copyright info
#define COPYRIGHT "Copyright 2002-2019 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"
*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "log.h"
#include "config.h"
#include "cwgsys.h"
#include "cwg-shared.h"
#include "licor8xx-shared.h"
#include "ioserial.h"
#include "iodigital.h"
#include "fileutils.h"
#include "iowfile.h"

#ifdef WITH_SDCARD
#include "ff.h"
#endif

/////////////////////////
// Declarations
/////////////////////////
static return_code_t s_log_open(log_handle_t hnd, unsigned int mode);

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

/////////////////////////
// Module Global Variables
/////////////////////////

uint32_t g_log_err_flags=0;

/// message buffer for syslog messages
/// initialized at boot time, size LOG_SYSLOG_MSG_BYTES
char *g_syslog_msg=NULL;

/// log file handles
static FILE_T log_files[LOG_HND_COUNT];
/// log sequence number file handles
/// sequence numbers for each log are maintained in files
/// to persist across power cycles.
static FILE_T seq_files[LOG_HND_COUNT];
static char seq_file_names[LOG_HND_COUNT][LOG_PATH_BYTES]={
    LOG_ZBINSEQ_NAME,
    LOG_SYSSEQ_NAME,
    LOG_RAWSEQ_NAME,
    LOG_OBINSEQ_NAME,
    LOG_CYCSEQ_NAME
};
/// log info data structures
log_t log_table[LOG_HND_COUNT]={
    {&log_files[LOG_ZBIN],&seq_files[LOG_ZBIN],LOG_ZBINLOG_NAME_DFL,seq_file_names[LOG_ZBIN],LOG_REC_SEP_DFL,0,0},
    {&log_files[LOG_SYSLOG],&seq_files[LOG_SYSLOG],LOG_SYSLOG_NAME_DFL,seq_file_names[LOG_SYSLOG],LOG_REC_SEP_DFL,0,0},
    {&log_files[LOG_RAW],&seq_files[LOG_RAW],LOG_RAWLOG_NAME_DFL,seq_file_names[LOG_RAW],LOG_REC_SEP_DFL,MBYTE*20,0},
    {&log_files[LOG_OBIN],&seq_files[LOG_OBIN],LOG_OBINLOG_NAME_DFL,seq_file_names[LOG_OBIN],LOG_REC_SEP_DFL,MBYTE*20,0},
    {&log_files[LOG_CBIN],&seq_files[LOG_CBIN],LOG_CBINLOG_NAME_DFL,seq_file_names[LOG_CBIN],LOG_REC_SEP_DFL,MBYTE*20,0}
};

/// queue instances
/// NOTE : indexed by queue_handle_t values

/// QUEUE_MEAS entry data storage (must be separate to support independent sizes)
byte queue_meas_mem[QUEUE_MEAS_DEPTH*QUEUE_MEAS_ENTRY_BYTES];
/// QUEUE_MEAS data entry structures
queue_entry_t queue_meas_entries[QUEUE_MEAS_DEPTH];

///// QUEUE_CYCLE entry data storage (must be separate to support independent sizes)
byte queue_cycle_mem[QUEUE_CYCLE_DEPTH*QUEUE_CYCLE_ENTRY_BYTES];
///// QUEUE_CYCLE data entry structures
queue_entry_t queue_cycle_entries[QUEUE_CYCLE_DEPTH];

/// data queues
/// [resources defined above]
static data_queue_t queue_table[QUEUE_COUNT]={
    {QUEUE_MEAS_DEPTH,QUEUE_MEAS_ENTRY_BYTES, queue_meas_entries, queue_meas_mem, 0, NULL, NULL},
    {QUEUE_CYCLE_DEPTH,QUEUE_CYCLE_ENTRY_BYTES, queue_cycle_entries, queue_cycle_mem, 0, NULL, NULL}
};

// alternative sequence numbers if no-sdc
// NOT PERSISTENT: reset to zero on power cycle
uint32_t seq_alt_mem[LOG_HND_COUNT]={0};

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

#ifdef WITH_LOGGING

// dest must hold 8.3 filename+NULL (13 bytes)
char *log_file_name(log_handle_t hnd, char *dest)
{
    char *retval=NULL;
    if( LOG_HRANGE_VALID(hnd) && (NULL!=dest)){
        Time now;
        log_t *pl=&log_table[hnd];
        rtcRead(&now);
//        sprintf(dest, "%s%.2d%.2d.%s", pl->name,now.tm_year%100, now.tm_mon+1,pl->suffix);
        sprintf(dest, "%s%.2d%.2d.%03lu", pl->name,now.tm_year%100, now.tm_mon+1,pl->segment);
        retval=dest;
    }else{
        EPRINT("invalid arg hnd[%d] [%p]",hnd,dest);
    }
    return retval;
}

static char *s_seq_file_name(log_handle_t hnd, char *dest)
{
    char *retval=NULL;
    if( LOG_HRANGE_VALID(hnd) && (NULL!=dest)){
        Time now;
        log_t *pl=&log_table[hnd];
        rtcRead(&now);
//        sprintf(dest, "%s%.2d%.2d.%s", pl->name,now.tm_year%100, now.tm_mon+1,pl->suffix);
        sprintf(dest, "%c%cQ%.2d%.2d.%03lu", pl->name[0],pl->name[1],now.tm_year%100, now.tm_mon+1,pl->segment);
        retval=dest;
    }else{
        EPRINT("invalid arg hnd[%d] [%p]",hnd,dest);
    }
    return retval;
}

return_code_t log_close(log_handle_t hnd)
{
    return_code_t retval=ERR_INVALID;
//    char path[LOG_PATH_BYTES]={0};
//    log_file_name(hnd,path);
//    DPRINT("closing [%s]\r\n",path);
    if(sys_sdc_ok() && LOG_HRANGE_VALID(hnd)){
        log_t *pl = &log_table[hnd];
        iow_fclose(pl->fp_dat);
        pl->is_open=false;
        retval=ERR_SUCCESS;
   }
   return retval;
}

return_code_t log_truncate(log_handle_t hnd)
{
    return_code_t retval=ERR_INVALID, rc=ERR_FAILURE;
    if(sys_sdc_ok() && LOG_HRANGE_VALID(hnd)){
        log_t *pl = &log_table[hnd];

        if( (rc=s_log_open(hnd,(FA_WRITE | FA_OPEN_ALWAYS)))==ERR_SUCCESS){
            if( (rc=iow_ftruncate(pl->fp_dat))==ERR_SUCCESS){
                retval=ERR_SUCCESS;
            }else{TRACE();}
            log_close(hnd);
        }else{TRACE();}
   }else{TRACE();}
   return retval;
}

return_code_t log_open(log_handle_t hnd)
{
    return_code_t rc=s_log_open(hnd,(FA_WRITE | FA_OPEN_ALWAYS));
    return rc;
}

static return_code_t s_log_open(log_handle_t hnd, unsigned int mode)
{
    return_code_t retval=ERR_FAILURE;

    char log_path[LOG_PATH_BYTES]={0};
    char seq_path[LOG_PATH_BYTES]={0};
    log_t *pl = NULL;
    uint32_t len = 0;
    int test=0;
    if(sys_sdc_ok() && LOG_HRANGE_VALID(hnd)){
        pl = &log_table[hnd];
        if(!pl->is_open){

            uint32_t ms=log_max_seg(hnd);

            if(ms!=pl->segment){
                // reset segment if it has changed
                // i.e. month change, file copy/deletion, etc.
                pl->segment=ms;
            }

             log_file_name(hnd,log_path);
             s_seq_file_name(hnd,seq_path);
             if(NULL!=pl && (test=iow_fopen(&pl->fp_dat, log_path, mode))==0){
                retval=ERR_SUCCESS;
                pl->is_open=true;
                sprintf(pl->name_seq,"%s",seq_path);

                if(pl->seg_size>0){
                    len = iow_fsize(pl->fp_dat);

                    if(len > pl->seg_size){
                        retval=ERR_FAILURE;
                        // file full: close and rotate
                        log_close(hnd);

                        if((pl->segment+1)<=LOG_MAX_SEG_N){
                           pl->segment++;

                            log_file_name(hnd,log_path);
                            s_seq_file_name(hnd,seq_path);

                            if(NULL!=pl && (test=iow_fopen(&pl->fp_dat, log_path, mode))==0){
                                pl->is_open=true;
                                retval=ERR_SUCCESS;
                                sprintf(pl->name_seq,"%s",seq_path);
                            }else{
                                EPRINT("open failed err[%d] path[%s]",iow_errno,log_path);
                            }
                        }else{
                            EPRINT("max segment exceeded [%s]",log_path);
                        }

                    }
                }
            }else{
                EPRINT("fopen failed path[%s] err[%d]",log_path,iow_errno);
            }
            if(retval==ERR_SUCCESS){
                char *key=NULL;
                switch(hnd){
                    case LOG_CBIN:
                        key=CK_CBIN_S;
                        break;
                    case LOG_OBIN:
                        key=CK_OBIN_S;
                        break;
                    case LOG_ZBIN:
                        key=CK_ZBIN_S;
                        break;
                    case LOG_SYSLOG:
                        key=CK_SYS_S;
                        break;
                    default:
                        break;
                }
                if(NULL!=key && strlen(log_path)>0){
                    config_set(key,log_path);
                }
            }
        }else{
            // already open
            retval=ERR_SUCCESS;
        }
    }// else invalid hnd

    return retval;
}

static return_code_t s_init_seg(log_handle_t hnd)
{
    return_code_t retval=ERR_SUCCESS;

    if( (LOG_HRANGE_VALID(hnd))){
        uint32_t max_seg=0;
        char path[LOG_PATH_BYTES]={0};
        log_t *pl=&log_table[hnd];

        if(pl->seg_size>0){
            // if segmented log, get last segment
            max_seg=log_max_seg(hnd);
            if(max_seg<=LOG_MAX_SEG_N){
                // validate, set current segment
                pl->segment=max_seg;

            }else{
                EPRINT("max segment exceeded [%s]",pl->name);
                retval=ERR_FAILURE;
            }
        }// else use static init default

        // open/create/rotate as needed
        if(retval==ERR_SUCCESS && (retval=s_log_open(hnd,(IOW_READ|IOW_OPEN_ALWAYS)))==ERR_SUCCESS){
            log_close(hnd);
        }else{
            EPRINT("log open failed path[%s] rv[%d]",log_file_name(hnd,path),retval);
        }
    }

    return retval;
}

uint32_t log_max_seg(log_handle_t hnd)
{
    uint32_t retval=0;

    if(sys_sdc_ok() && LOG_HRANGE_VALID(hnd)){
        Time now;
        log_t *pl=&log_table[hnd];
        rtcRead(&now);
        char match[LOG_PATH_BYTES]={0};
        sprintf(match, "%s%.2d%.2d", pl->name,now.tm_year%100, now.tm_mon+1);

#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
        DIRENT_T finfo, *fp=&finfo;
        DIR_T dir,*pd=&dir;
#else
        DIRENT_T *fp;
        DIR_T *pd;
#endif
        int i=0;
        int max_seg=0;
        int seg=1000;

        if(iow_opendir(&pd,"/")==0){
            while(iow_readdir(pd,&fp)==0){

                if(fp->fname[0]=='\0')
                    break;
                if(!(fp->fattrib & AM_DIR)){
                    char *x[2]={NULL,NULL};
                    x[0]=strtok(fp->fname,".");
                    x[1]=strtok(NULL,".");
                    if( NULL!=x[0] && strcmp(x[0],match)==0){
                        if(NULL!=x[1] && (seg=atoi(x[1]))>=0){
                            if(seg>max_seg){
                                max_seg=seg;
                                retval=max_seg;
                            }
                        }
                    }
                }
                i++;
            }// WHILE
        }// IF OPENDIR
     }
    return retval;
}

uint32_t log_err_flags()
{
    return g_log_err_flags;
}

return_code_t log_init()
{
    return_code_t retval=ERR_SUCCESS;
    g_syslog_msg = (char *)palloc(LOG_SYSLOG_MSG_BYTES);
    retval=log_queue_init();
    int i=0;
    for(i=0;i<LOG_HND_COUNT;i++){
        if(s_init_seg(i)!=ERR_SUCCESS)
            retval=ERR_FAILURE;
    }

    return retval;
}

uint32_t log_bytes(log_handle_t hnd)
{
    uint32_t retval=0;
    log_t *pl = NULL;

    if(sys_sdc_ok() && LOG_HRANGE_VALID(hnd)){
         pl = &log_table[hnd];

        if(s_log_open(hnd,(FA_READ | FA_OPEN_ALWAYS))==ERR_SUCCESS){
            retval = iow_fsize(pl->fp_dat);
            log_close(hnd);
        }
    }
    return retval;
}

void log_show(log_handle_t hnd, bool verbose, int indent)
{

    log_t *self = (LOG_HRANGE_VALID(hnd)?&log_table[hnd]:NULL);
    if( NULL!=self ){
        int wkey=10;
        int wval=16;

       OUT_CTTY("%*s%*s %*p%s",indent,(indent>0?" ":""), wkey,"self",wval,self,LINEEND);
       OUT_CTTY("%*s%*s %*p%s",indent,(indent>0?" ":""), wkey,"fp_dat",wval,self->fp_dat,LINEEND);
       OUT_CTTY("%*s%*s %*p%s",indent,(indent>0?" ":""), wkey,"fp_seq",wval,self->fp_seq,LINEEND);
       OUT_CTTY("%*s%*s %*s%s",indent,(indent>0?" ":""), wkey,"name",wval,self->name,LINEEND);
       OUT_CTTY("%*s%*s %*s%s",indent,(indent>0?" ":""), wkey,"name_seq",wval,self->name_seq,LINEEND);
       OUT_CTTY("%*s%*s %*s",indent,(indent>0?" ":""), wkey,"rec_sep",wval-4,"");
       const char *cp=self->rec_sep;
       while(*cp!='\0'){
           OUT_CTTY((*cp>=0x20)?"%c":"%02X",*cp);
           cp++;
       }
       OUT_CTTY("%s",LINEEND);
       OUT_CTTY("%*s%*s %*lu%s",indent,(indent>0?" ":""), wkey,"seg_size",wval,self->seg_size,LINEEND);
       OUT_CTTY("%*s%*s %*s%03lu%s",indent,(indent>0?" ":""), wkey,"segment",wval-3,"",self->segment,LINEEND);
       OUT_CTTY("%*s%*s %*s%s",indent,(indent>0?" ":""),wkey,"is_open",wval,self->is_open?"Y":"N",LINEEND);

    }
    return;
}

int log_fget_seq_number(FILE_T *fp, char *path, uint32_t *r_seq, bool increment)
{
    int retval=0;
    uint32_t seq_no=0;

    if(sys_sdc_ok() && NULL!=fp && NULL!=path){

        if(iow_fopen(&fp,path,(IOW_WRITE | IOW_READ | IOW_OPEN_ALWAYS))==0){
           UINT br=0;
            if(iow_fsize(fp)>0){
                if( (br=iow_fread(fp,(void *)&seq_no,sizeof(uint32_t)))==sizeof(uint32_t) ){
                    retval=0;
                    if(NULL!=r_seq){
                        *r_seq=seq_no;
                    }
                    if(increment){
                        seq_no++;
                        iow_fseek(fp,0);
                        br=iow_fwrite(fp,&seq_no,sizeof(uint32_t));
                    }
                }else{
                    EPRINT("%s - invalid read [%lu/%s]",__FUNCTION__,br,path);
                }
            }else{
                // this shouldn't happen (should initialize from)
                if(XTEST(0xFE)){
                WPRINT("seq file empty [%lu/%s]",iow_fsize(fp),path);
                }
                retval=0;
                seq_no=0;
                if(NULL!=r_seq){
                    *r_seq=seq_no;
                }
                if(increment){
                    seq_no++;
                    iow_fseek(fp,0);
                    br=iow_fwrite(fp,&seq_no,sizeof(uint32_t));
                }
            }

            iow_fclose(fp);
        }else{
            EMSG("fopen failed\r\n");
        }
    }else{
        if(!sys_sdc_ok()){
            EMSG("SDC unavailable\r\n");
        }
    }
    return retval;
}

int log_fset_seq_number(FILE_T *fp, char *path, uint32_t seq_n)
{
    int retval=0;

    if(sys_sdc_ok() && NULL!=fp && NULL!=path){

        if(iow_fopen(&fp,path,(IOW_WRITE | IOW_READ | IOW_OPEN_ALWAYS))==0){
            UINT br=0;

            iow_fseek(fp,0);
            if( (br=iow_fwrite(fp,&seq_n,sizeof(uint32_t)))!=sizeof(uint32_t)){
                EMSG("fwrite failed\r\n");
            }
            iow_fclose(fp);
            retval=0;
        }else{
            EMSG("fopen failed\r\n");
        }

    }else{
        if(!sys_sdc_ok() ) {
            EMSG("SDC unavailable\r\n");
        }
    }

    return retval;
}

int log_get_seq_number(log_handle_t hnd, uint32_t *r_seq, bool increment)
{
    int retval=0;

    log_t *pl = NULL;

    if(sys_sdc_ok()){
        if( LOG_HRANGE_VALID(hnd)){
           pl = &log_table[hnd];
           retval = log_fget_seq_number(pl->fp_seq, pl->name_seq,r_seq, increment);
        }
    }else{
        MPRINT(MOD_LOG,"SDC unavailable\r\n");
        if(NULL!=r_seq && LOG_HRANGE_VALID(hnd)){
            *r_seq=seq_alt_mem[hnd];
        }
        if(increment){
            seq_alt_mem[hnd]++;
        }
        retval=0;
    }
    return retval;
}

int log_set_seq_number(log_handle_t hnd, uint32_t seq_n)
{
    int retval=0;

    log_t *pl = NULL;

    if(sys_sdc_ok()){
        if(LOG_HRANGE_VALID(hnd)){
            pl = &log_table[hnd];
            retval = log_fset_seq_number(pl->fp_seq, pl->name_seq, seq_n);
        }
    }else{
        MPRINT(MOD_LOG,"SDC unavailable\r\n");
        if(LOG_HRANGE_VALID(hnd)){
            seq_alt_mem[hnd]=seq_n;
        }
        retval=0;
    }

    return retval;
}

return_code_t log_addx(log_handle_t hnd, byte* data, size_t data_len, Time *time, const char *del, const char *sep, log_flags_t flags)
{
    return_code_t retval=ERR_INVALID, rc=ERR_FAILURE;
	char tsBuff[LOG_TIMESTAMP_BYTES];
	char psz[16]={0};
    char path[LOG_PATH_BYTES]={0};
    log_t *pl = NULL;
    uint32_t len=0;
    FRESULT fr=FR_OK;
     if(sys_sdc_ok() && LOG_HRANGE_VALID(hnd)){

        pl = &log_table[hnd];
        if( (rc=s_log_open(hnd,(FA_WRITE | FA_OPEN_ALWAYS)))!=ERR_SUCCESS){
            s_init_seg(hnd);
            rc=s_log_open(hnd,(FA_WRITE | FA_OPEN_ALWAYS));
        }
        if( rc==ERR_SUCCESS){
            len = iow_fsize(pl->fp_dat);
//         DPRINT("opened [%s] len[%lu]\r\n",path,len);
            if( len == 0){
                // new file
                // TODO: write date? config? to syslog?
            }
            if ( iow_fseek(pl->fp_dat, len)==0){

                if(NULL!=time){
                    TM2ISO8601(time,tsBuff);
//                  DPRINT("%s - tsbuf[%s] time[%d %d]\r\n",__FUNCTION__,tsBuff,time->tm_year,time->tm_mday);
                    iow_fputs(pl->fp_dat,tsBuff);
                    iow_fputs(pl->fp_dat, (NULL==del? LOG_REC_DEL_DFL : del));
                }

                if( (flags&LF_WITH_REC_LEN)!=0 ){
                    sprintf(psz,"%u",data_len);
                    iow_fputs(pl->fp_dat,psz);
                    iow_fputs( pl->fp_dat,(NULL==del? LOG_REC_DEL_DFL : del));
                }

                if(data_len>0){
                    size_t bytes_written=0;
                    bytes_written=iow_fwrite(pl->fp_dat,data,data_len);
                    if((flags&LF_WITHOUT_REC_DEL)==0)
                    iow_fputs( pl->fp_dat,(NULL==del? LOG_REC_DEL_DFL : del));
                    if((flags&LF_WITHOUT_REC_SEP)==0)
                    iow_fputs( pl->fp_dat, (NULL==sep? LOG_REC_SEP_DFL : sep));
                }else{
                    WMSG("data len <= 0");
                }

                if( (flags&LF_NOCLOSE)==0){
                    log_close(hnd);
                }

                retval=ERR_SUCCESS;

            }else{ //goto end of file
                EPRINT("seek failed[%s] len[%lu] err[%d]",path,len,fr);
                log_close(hnd);
                return (ERR_FAILURE);
            }

        }else{
            //  open failed
            EPRINT("fopen failed hnd[%d] err[%d]",hnd,rc);
        }
    }else{
        if(!sys_sdc_ok()){
            MPRINT(MOD_LOG,"SDC unavailable\r\n");
        }
    }

	return (retval);
}

return_code_t log_adds(log_handle_t hnd, char* data, Time *time, log_flags_t flags)
{
    return log_addx(hnd, (byte *)data, strlen(data), time, NULL, NULL, flags);
}

return_code_t log_addb(log_handle_t hnd, byte* data, size_t len, Time *time, log_flags_t flags)
{
    return log_addx(hnd, data, len, time, NULL, NULL, flags);
}

return_code_t log_set_segsize(log_handle_t hnd, uint32_t val)
{
    return_code_t retval=ERR_INVALID;
     log_t *pl = NULL;
     if(LOG_HRANGE_VALID(hnd)){
         pl = &log_table[hnd];
         pl->seg_size=val;
         retval=ERR_SUCCESS;
     }
     return retval;
}
return_code_t log_segsize(log_handle_t hnd, uint32_t *dest)
{
    return_code_t retval=ERR_FAILURE;
     log_t *pl = NULL;
     if(LOG_HRANGE_VALID(hnd) && NULL!=dest){
         pl = &log_table[hnd];
         *dest=pl->seg_size;
         retval=ERR_SUCCESS;
     }
     return retval;
}

 // there is not vsnprintf for PIC24
 // max message length set by LOG_SYSLOG_MSG_BYTES
int log_printf(log_handle_t hnd, char *fmt, ... )
{
    int retval=-1;
    // try to alloc a big buffer
    char *buf = (char *)palloc(LOG_SYSLOG_ALLOC_BYTES);
    uint32_t len = LOG_SYSLOG_ALLOC_BYTES;
    bool do_free=true;
    if(NULL==buf){
        // fall back to default buffer if alloc fails
        buf=g_syslog_msg;
        len = LOG_SYSLOG_MSG_BYTES;
        do_free=false;
    }

    ZERO_MEM(buf,len);
    va_list ap;
    va_start(ap,fmt);
    // there is no vsnprintf implementation
    // or support for print to NULL to test length.
    // The UI parser catches and truncates input,
    // provided the buffers are sized appropriately.
    retval=vsprintf(buf,fmt,ap);
    // ensure NULL terminated
    buf[len-1]='\0';
    va_end(ap);
    Time now;
    rtcRead(&now);
    log_adds(hnd, buf, &now, LF_WITHOUT_REC_DEL);
    // free buffer if alloc'd
    if(do_free)
        pfree(buf);
    return retval;
}

int log_syslog(char *fmt, ... )
{
    int retval=-1;
    if(sys_sdc_ok()){
        // try to alloc a big buffer
        char *buf = (char *)palloc(LOG_SYSLOG_ALLOC_BYTES);
        uint32_t len = LOG_SYSLOG_ALLOC_BYTES;
        bool do_free=true;
        if(NULL==buf){
            // fall back to default buffer if alloc fails
            buf=g_syslog_msg;
            len = LOG_SYSLOG_MSG_BYTES;
            do_free=false;
        }

        ZERO_MEM(buf,len);
        va_list ap;
        va_start(ap,fmt);
        // there is no vsnprintf implementation
        // or support for print to NULL to test length.
        // The UI parser catches and truncates input,
        // provided the buffers are sized appropriately.
        retval=vsprintf(buf,fmt,ap);
        // ensure NULL terminated
        buf[len-1]='\0';
        va_end(ap);
        Time now;
        rtcRead(&now);
        log_adds(LOG_SYSLOG, buf, &now, LF_WITHOUT_REC_DEL);
        // free buffer if alloc'd
        if(do_free)
            pfree(buf);
    }
    return retval;
}

static data_queue_t *s_log_queue_lookup(queue_handle_t hnd)
{
    return &queue_table[hnd%QUEUE_COUNT];
}

return_code_t log_queue_init()
{
    return_code_t retval=ERR_SUCCESS;
    data_queue_t *pq = queue_table;
    while(pq < (queue_table+QUEUE_COUNT)){
    pq->push=NULL;
    pq->pop=NULL;
    pq->count=0;
    memset(pq->entries,0,pq->depth*sizeof(queue_entry_t));
    int i=0;
    for(i=0;i<pq->depth;i++){
        pq->entries[i].data=(pq->entry_mem+i*pq->entry_size);
        memset(pq->entries[i].data,0,pq->entry_size);
    }
    pq++;
    }
    return retval;
}

return_code_t log_qshow_cmd(int argc, char **argv)
{
    int hnd=0;
    int i=0;
    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"I%d",&hnd)==1){
            continue;
        }else if(strcmp(argv[i],"M")==0){
            hnd=0;
            continue;
        }else if(strcmp(argv[i],"C")==0){
            hnd=1;
            continue;
        }
    }
    log_queue_show(hnd, true, 0);
    return ERR_SUCCESS;
}

void log_queue_show(queue_handle_t hnd, bool verbose, int indent)
{
    data_queue_t *pq=s_log_queue_lookup(hnd);
    if(NULL!=pq){
        int wkey=10;
        int wval=12;

        DPRINT("%s - dq_entries[%p] dq_mem[%p]\r\n",__FUNCTION__,pq->entries,pq->entry_mem);

        OUT_CTTY("%*s%*s %*p%s",indent,(indent>0?" ":""), wkey,"self",wval,pq,LINEEND);
        char sval[20]={0};
        sprintf(sval,"%p-%p",pq->entries,(pq->entries+(pq->depth-1)));
        OUT_CTTY("%*s%*s %*s%s",indent,(indent>0?" ":""), wkey,"entries",wval,sval,LINEEND);
        sprintf(sval,"%p-%p",pq->entry_mem,(pq->entry_mem+(pq->depth-1)*pq->entry_size));
        OUT_CTTY("%*s%*s %*s%s",indent,(indent>0?" ":""), wkey,"entry_data",wval,sval,LINEEND);
        OUT_CTTY("%*s%*s %*u%s",indent,(indent>0?" ":""), wkey,"entry_size",wval,pq->entry_size,LINEEND);
        OUT_CTTY("%*s%*s %*p%s",indent,(indent>0?" ":""), wkey,"push",wval,pq->push,LINEEND);
        OUT_CTTY("%*s%*s %*p%s",indent,(indent>0?" ":""), wkey,"pop",wval,pq->pop,LINEEND);
        OUT_CTTY("%*s%*s %*lu%s",indent,(indent>0?" ":""), wkey,"count",wval,pq->count,LINEEND);
        OUT_CTTY("%*s%*s %*d%s",indent,(indent>0?" ":""), wkey,"depth",wval,pq->depth,LINEEND);
        OUT_CTTY("%*s%*s %s",indent,(indent>0?" ":""), wkey,"-- entries --",LINEEND);

        int i=0;
        for(i=0;i<pq->depth;i++){
            OUT_CTTY("%*s n[%02d] e@[%4p] d@[%4p] id[%02d] len[%03d]%s",indent+2,(indent>0?" ":""), i,&pq->entries[i],pq->entries[i].data,pq->entries[i].id,pq->entries[i].len,LINEEND);
         }
    }
}

void log_qentry_show(queue_entry_t *self, bool verbose, int indent)
{
    if(NULL!=self){
       int wkey=10;
       int wval=16;

        OUT_CTTY("%*s%*s %*p%s",indent,(indent>0?" ":""), wkey,"self",wval,self,LINEEND);
        OUT_CTTY("%*s%*s %*u%s",indent,(indent>0?" ":""), wkey,"len",wval,self->len,LINEEND);
        OUT_CTTY("%*s%*s %*d%s",indent,(indent>0?" ":""), wkey,"id",wval,self->id,LINEEND);
        OUT_CTTY("%*s%*s %*p%s",indent,(indent>0?" ":""), wkey,"data",wval,self->data,LINEEND);
     }
}
int log_queue_type(queue_handle_t hnd, uint32_t idx)
{
    int retval=0;
    data_queue_t *pq=s_log_queue_lookup(hnd);
    if(NULL!=pq){
        retval = pq->entries[(int)idx%pq->depth].id;
    }
    return retval;
}

size_t log_queue_len(queue_handle_t hnd, uint32_t idx)
{
    size_t retval=0;
    data_queue_t *pq=s_log_queue_lookup(hnd);
    if(NULL!=pq){
        retval = pq->entries[(int)idx%pq->depth].len ;
    }
    return retval;
 }

uint32_t log_queue_count(queue_handle_t hnd)
 {
    uint32_t retval=0;
    data_queue_t *pq=s_log_queue_lookup(hnd);
    if(NULL!=pq){
        retval = pq->count ;
    }
    return retval;
 }

size_t log_queue_entry_size(queue_handle_t hnd)
{
    size_t retval=0;
    data_queue_t *pq=s_log_queue_lookup(hnd);
    if(NULL!=pq){
        retval = pq->entry_size;
    }
    return retval;

}

return_code_t log_queue_push(queue_handle_t hnd, byte *src, int id, size_t len)
{
    return_code_t retval=ERR_FAILURE;
    data_queue_t *pq=s_log_queue_lookup(hnd);
    size_t entry_size=(pq!=NULL?pq->entry_size:0);

    if(NULL!=pq && NULL!=src && len>0 && len<=entry_size){

        if(NULL==pq->push){
            pq->push = pq->entries;
            pq->pop = pq->push;
        }
        // fill entry data w/ zero
        memset(pq->push->data,0,entry_size);

        // fill entry
        pq->push->id=id;
        pq->push->len=len;
        memcpy( pq->push->data, src, len);

        // adjust pointers, queue parameters
        if(pq->push == pq->pop && pq->count==pq->depth){
//            DPRINT("%d - push[%p] pop[%p][%s](@POP)\r\n",__LINE__,pq->push,pq->pop,(char *)pq->pop->data);
            pq->pop = pq->push+1;
            if(pq->pop >= (pq->entries+pq->depth) ){
//           DPRINT("%d - push[%p] pop[%p][%s](POP WRAP)\r\n",__LINE__,pq->push,pq->pop,(char *)pq->pop->data);
                pq->pop = pq->entries;
            }
        }
        pq->push++;

        if(pq->push >= (pq->entries+pq->depth) ){
            pq->push = pq->entries;
        }

        pq->count++;
        if(pq->count>pq->depth){
            pq->count=pq->depth;
        }

        retval=ERR_SUCCESS;
    }else{
      OUT_ERR("invalid - len[%ld] max[%ld]\r\n",(int32_t)len,(int32_t)entry_size );
    }

    return retval;
}

return_code_t log_queue_peek(queue_handle_t hnd, int32_t idx, queue_entry_t *dest)
{
    return_code_t retval=ERR_FAILURE;
    data_queue_t *pq=s_log_queue_lookup(hnd);
//    DPRINT("%s - hnd[%d] idx[%ld] dest[%p]\r\n",__FUNCTION__,hnd,idx,dest);
     if(NULL!=pq && NULL!=dest ){
        // by default (idx<0), return next (pop)
        // copy, even if empty
        // note that idx wraps around if too large
        queue_entry_t *qe=pq->pop;
        if(idx>=0){
//            qe = &pq->entries[idx%pq->depth];
            int z = ((pq->pop-&pq->entries[0])+idx)%pq->depth;
            qe = &pq->entries[z];//&pq->entries[idx%pq->depth];
        }
        if(NULL!=qe->data && pq->entry_size>0){
            dest->id = qe->id;
            dest->len = qe->len;
            memcpy( dest->data, qe->data, pq->entry_size);
            retval=ERR_SUCCESS;
        }else{
            EPRINT("%s - invalid values dp[%p] es[%d]",__FUNCTION__,qe->data , pq->entry_size);
        }
    }
    return retval;
}

return_code_t log_queue_clear(queue_handle_t hnd)
{
    return_code_t retval=ERR_SUCCESS;

    while(log_queue_count(hnd)>0 && retval==ERR_SUCCESS)
        retval=log_queue_pop(hnd,NULL);

    return retval;
}

return_code_t log_queue_pop(queue_handle_t hnd, queue_entry_t *dest)
{
    return_code_t retval=ERR_FAILURE;
    data_queue_t *pq=s_log_queue_lookup(hnd);

    if( NULL!=pq && pq->count>0 &&
            NULL!=pq->pop &&
            pq->pop->len>0){

        if(NULL!=dest){
        if(NULL!=pq->pop->data && pq->entry_size>0){

            dest->id = pq->pop->id;
            dest->len = pq->pop->len;
//        DPRINT("dest[%p] dest->data[%p] len[%d] es[%d]\r\n",dest,dest->data,dest->len,pq->entry_size);
            memcpy( dest->data, pq->pop->data, pq->entry_size);
        }else{
            EPRINT("%s - invalid values dest[%p] dp[%p] es[%d]",__FUNCTION__,dest,pq->pop->data, pq->entry_size);
        }
        }

        pq->pop->id=0;
        pq->pop->len=0;
        if(NULL!=pq->pop->data && pq->entry_size>0){
            memset(pq->pop->data,0,pq->entry_size);
        }

        pq->pop++;
        if(pq->pop >= (pq->entries+pq->depth) ){
//           DPRINT("%d - push[%p] pop[%p] (POP WRAP)\r\n",__LINE__,pq->push,pq->pop);
           pq->pop = pq->entries;
        }
        if(pq->pop == pq->push){
//            DPRINT("%d - push[%p] pop[%p] (EMPTY)\r\n",__LINE__,pq->push,pq->pop);
            pq->pop=NULL;
            pq->push=NULL;
        }
        pq->count--;
        if(pq->count<0){
            pq->count=0;
        }
        retval=ERR_SUCCESS;
    }

    return retval;
}


#else
#warning "WITH_LOGGING disabled - using telemetry stubs"
return_code_t tlmLogLine(char* data)
{
    return ERR_NO_IMPL;
}


return_code_t tlmUWLine(struct LiSample_s* datum)
{
    return ERR_NO_IMPL;
}

#endif // WITH_LOGGING
