///
/// @file lout.c
/// @authors k. headley
/// @date 03 mar 2020

/// Carbon Waveglider (CWG) log reader utility

/// @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 <stdbool.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#if defined(__CYGWIN__)
// for POSIX file IO: seek, read, close
#include <unistd.h>
#endif

#include "cwg-shared.h"
#include "z85.h"
#include "licor8xx-shared.h"

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

#define LOUT_NAME "lout"
#ifndef LOUT_BUILD
/// @def LOUT_BUILD
/// @brief module build date.
/// Sourced from CFLAGS in Makefile
/// w/ -DLIB_BUILD=`date`
#define LOUT_BUILD ""VERSION_STRING(LIB_BUILD)
#endif

/////////////////////////
// Declarations
/////////////////////////
typedef struct app_cfg_s {
    char *ifile;
    bool verbose;
    bool test;

    FILE *fs;
    int fd;
}app_cfg_t;

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

/////////////////////////
// Module Global Variables
/////////////////////////
int g_signal=0;
int g_interrupt=false;

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

/// @fn void s_show_help()
/// @brief output user help message to stdout.
/// @return none
static void s_show_help()
{
    char help_message[] = "\nlog output utility\n";
    char usage_message[] = "\nloud [options]\n"
    "--debug     : debug output\n"
    "--verbose   : verbose output\n"
    "--help      : output help message\n"
    "--version   : output version info\n"
    "--ifile     : input file\n"
    "--test      : test codec\n"
    "\n";
    printf("%s",help_message);
    printf("%s",usage_message);
}
// End function s_show_help

/// @fn void parse_args(int argc, char ** argv, app_cfg_t * cfg)
/// @brief parse command line args, set application configuration.
/// @param[in] argc number of arguments
/// @param[in] argv array of command line arguments (strings)
/// @param[in] cfg application config structure
/// @return none
void parse_args(int argc, char **argv, app_cfg_t *cfg)
{
    extern char WIN_DECLSPEC *optarg;
    int option_index;
    int c;
    bool help=false;
    bool version=false;

    static struct option options[] = {
        {"debug", no_argument, NULL, 0},
        {"verbose", required_argument, NULL, 0},
        {"help", no_argument, NULL, 0},
        {"version", no_argument, NULL, 0},
        {"ifile", required_argument, NULL, 0},
        {"test", no_argument, NULL, 0},
        {NULL, 0, NULL, 0}};

    // process argument list
    while ((c = getopt_long(argc, argv, "", options, &option_index)) != -1){
        switch (c) {
                // long options all return c=0
            case 0:
                // verbose
                if (strcmp("verbose", options[option_index].name) == 0) {
                    cfg->verbose=true;
                    g_vprint=atoi(optarg);
                }
                if (strcmp("debug", options[option_index].name) == 0) {
                    g_dprint=1;
                }

                // help
                else if (strcmp("help", options[option_index].name) == 0) {
                    help = true;
                }
                // version
                else if (strcmp("version", options[option_index].name) == 0) {
                    version = true;
                }

                // ifile
                else if (strcmp("ifile", options[option_index].name) == 0) {
                    if(NULL!=cfg->ifile){
                        free(cfg->ifile);
                    }
                    cfg->ifile=strdup(optarg);
                }
                // map
                else if (strcmp("test", options[option_index].name) == 0) {
                    cfg->test=true;
                }
                break;
            default:
                help=true;
                break;
        }
        if (version) {
            fprintf(stderr,"%s build %s\n",LOUT_NAME,LOUT_BUILD);
            exit(0);
        }
        if (help) {
            //            mbtrn_show_app_version(TCPC_NAME,TCPC_BUILD);
            s_show_help();
            exit(0);
        }
    }// while
    VPRINT("verbose   [%s]\n",(cfg->verbose?"Y":"N"));
    VPRINT("ifile     [%s]\n",cfg->ifile);
    VPRINT("test      [%s]\n",cfg->test?"Y":"N");
}
// End function parse_args

/// @fn void termination_handler (int signum)
/// @brief termination signal handler.
/// @param[in] signum signal number
/// @return none
static void s_termination_handler (int signum)
{
    switch (signum) {
#if ! defined(__WIN32) && ! defined(__WIN64)
        case SIGHUP:
#endif
        case SIGINT:
        case SIGTERM:
            VPRINT("sig received[%d]\n",signum);
            g_interrupt=true;
            g_signal=signum;
            break;
        default:
            fprintf(stderr,"s_termination_handler: sig not handled[%d]\n",signum);
            break;
    }
}
// End function termination_handler

static app_cfg_t *app_cfg_new()
{
    app_cfg_t *instance = (app_cfg_t *)malloc(sizeof(app_cfg_t));

    if(NULL!=instance){
        memset(instance,0,sizeof(app_cfg_t));
        instance->ifile=NULL;
        instance->test=false;
        instance->fs=NULL;
        instance->fd=-1;
    }
    return instance;
}

static void app_cfg_destroy(app_cfg_t **pself)
{
    if(NULL!=pself){
        app_cfg_t *self = (app_cfg_t *)(*pself);
        if(NULL!=self){
            if(NULL!=self->ifile)
                free(self->ifile);
            free(self);
            *pself=NULL;
        }
    }

}

static int s_test_codec()
{

    int retval=-1;
    char *str="the quick brown fox jumps over the lazy dog 1.2.3...";
    size_t slen=strlen(str)+1;
    size_t esize=5*(slen)/4;
    char encoded[esize+1];
    size_t elen=slen+(4-slen%4);
    byte data[elen+1];
    DPRINT("original:\"%s\"\r\n",str);
    DPRINT("strlen(%zu) slen[%zu] esize[%zu] elen[%zu]\r\n",strlen(str),slen,esize,elen);

    ZERO_MEM(encoded,esize+1);
    ZERO_MEM(data,elen+1);
    memcpy(data,str,slen);
    if(NULL!=Z85_encode(encoded,data,elen)){
        encoded[esize]='0';
        DPRINT("encoded:\r\n%s\r\n",encoded);
    }else{
        DPRINT("Z85_encode err NULL\r\n");
    }

    size_t dsize=4*(strlen(encoded)+1)/5;
    byte decoded[dsize+1];
    DPRINT("dsize[%zu]\r\n",dsize);
    ZERO_MEM(decoded,dsize);
    if(NULL!=Z85_decode(decoded,dsize,encoded,0)){
        decoded[dsize]='\0';
        DPRINT("decoded:\"%s\"\r\n",(char *)decoded);
    }else{
        DPRINT("Z85_decode err NULL\r\n");
    }

    retval=strcmp((char *)decoded,str);

    return retval;
}

int s_sync_fn(licor_zrecord_t *zrec, app_cfg_t *cfg, int *sync_bytes)
{
    int retval=-1;
    if(NULL!=zrec && NULL!=cfg && cfg->fd>0){
        bool sync_valid=false;
        uint32_t sync=0x0;
        byte *cur=(byte *)&sync;
        int test=0;

        while(!sync_valid){
            if( (test=read(cfg->fd,cur,1))==1 && *cur==0x02){
                cur++;
                if( (test=read(cfg->fd,cur,1))==1 && *cur==0x4C){
                    cur++;
                    if( (test=read(cfg->fd,cur,1))==1 && *cur==0xEA){
                        cur++;
                        if( (test=read(cfg->fd,cur,1))==1 && *cur==0x0C){
                            sync_valid=true;
                            zrec->hdr.sync=sync;
                            retval=0;
                        }else{
                                fprintf(stderr,"ERR - sync read 0x0C t[%d] c[%02X] [%d/%s]\n",test,*cur,errno,strerror(errno));
                            if(test==-1 || test==0){
                                break;
                            }
                            cur=(byte *)&sync;
                            sync=0x0;
                            if(NULL!=sync_bytes){
                                *sync_bytes+=4;
                            }
                        }
                    }else{
                            fprintf(stderr,"ERR - sync read 0xEA t[%d] c[%02X] [%d/%s]\n",test,*cur,errno,strerror(errno));
                        if(test==-1 || test==0){
                            break;
                        }
                        cur=(byte *)&sync;
                        sync=0x0;
                        if(NULL!=sync_bytes){
                            *sync_bytes+=3;
                        }
                    }
                }else{
                        fprintf(stderr,"ERR - sync read 0x4C t[%d] c[%02X] [%d/%s]\n",test,*cur,errno,strerror(errno));
                    if(test==-1 || test==0){
                        break;
                    }
                    cur=(byte *)&sync;
                    sync=0x0;
                    if(NULL!=sync_bytes){
                        *sync_bytes+=2;
                    }
                }
            }else{
                if(test==-1 || test==0){
                    if(test==0){
                        retval=-2;
                    }else{
                        fprintf(stderr,"ERR - sync read 0x02 t[%d] c[%02X] [%d/%s]\n",test,*cur,errno,strerror(errno));
                    }
                    break;
                }
                cur=(byte *)&sync;
                sync=0x0;
                if(NULL!=sync_bytes){
                    *sync_bytes+=1;
                }
            }
        }

    }
    return retval;
}

int s_hdr_fn(licor_zrecord_t *zrec, app_cfg_t *cfg, int *sync_bytes)
{
    int retval=-1;
    if(NULL!=zrec && NULL!=cfg && cfg->fd>0){
//        int test=0;
//        byte b8[8]={0};
//        mdata_Z_t *mdata = mdata_Z_t *((byte *)zrec+sizeof(licor_zrecord_t));
//        if( (test=read(cfg->fd,b8,8))==8){
//            mdata->timestamps[0]=(uint64_t)b8;
//            DPRINT("timestamp[0] [%lu]\n",zrec->hdr.timestamps[0]);
//        }
    }
    return retval;
}

static int s_state_mach(app_cfg_t *cfg)
{
    int retval=-1;
    typedef enum{ST_SYNC=0,ST_SYNC_VALID,ST_HDR_VALID, ST_DATA_VALID, ST_ERR, ST_EOF}state_t;
    typedef enum{AT_NOP=0,AT_SYNC,AT_RD_HDR, AT_RD_DATA,AT_PROC_REC, AT_QUIT}action_t;

    state_t state=ST_SYNC;
    action_t action=AT_SYNC;
    bool sm_quit=false;
    int test=0;
    int sync_bytes[2]={0,0};
    int sync_errors=0;
    int hdr_errors=0;
    int data_errors=0;
    int record_count=0;

    byte *zrec_mem=(byte *)malloc(LI_ZREC_SIZEN(6));
    ZERO_MEM(zrec_mem,LI_ZREC_SIZEN(6));
    licor_zrecord_t *zrec=(licor_zrecord_t *)zrec_mem;
    mdata_Z_t *mdata = LI_ZREC_PMDATA(zrec);

//    DPRINT(">> Data sizes\n");
//    DPRINT("licor_zrecord_t        [%zu]\n",sizeof(licor_zrecord_t));
//    DPRINT("mdata_Z_t              [%zu]\n",sizeof(mdata_Z_t));
//    DPRINT("mdata_Z_t*             [%zu]\n",sizeof(mdata_Z_t *));
//    DPRINT("log_rec_header_t       [%zu]\n",sizeof(log_rec_header_t));
//    DPRINT("float                  [%zu]\n",sizeof(float));
//    DPRINT("double                 [%zu]\n",sizeof(double));
//    DPRINT("uint64                 [%zu]\n",sizeof(uint64_t));
//    DPRINT("LI_ZREC_SIZEN(6) [%zu]\n",(size_t)LI_ZREC_SIZEN(6));

    while(!sm_quit && !g_interrupt){
        switch (state) {
            case ST_SYNC:
                ZERO_MEM(zrec_mem,LI_ZREC_SIZEN(6));
                action=AT_SYNC;
                break;
            case ST_SYNC_VALID:
                action=AT_RD_HDR;
                break;
            case ST_HDR_VALID:
                action=AT_RD_DATA;
                break;
            case ST_DATA_VALID:
                action=AT_PROC_REC;
                break;
            case ST_EOF:
                action=AT_QUIT;
                break;
            default:
                fprintf(stderr,"ERR - invalid state [%d]\n",state);
                sm_quit=true;
                break;
        }
        
        if(action==AT_SYNC){
            sync_bytes[1]=0;
            if( (test=s_sync_fn(zrec, cfg, &sync_bytes[1]))==0){
                state=ST_SYNC_VALID;
                DPRINT("%10s%8s\n","sync","OK");
            }else{
                if(test==-2){
                    fprintf(stderr,"End of file\n");
                	state=ST_EOF;
                }else{
                    fprintf(stderr,"ERR - sync not found\n");
                	sync_errors++;
                    // state is sync
                }
            }
            sync_bytes[0]+=sync_bytes[1];
        }
        if(action==AT_RD_HDR){
            // read remainder of header + mdata pointer
            size_t read_len=LI_ZREC_HDR_LEN-LI_ZREC_SYNC_LEN;
            DPRINT("hdr read_len[%zu]\n",read_len);
            byte src[read_len];
            ZERO_MEM(src,read_len);
            off_t current_ofs=lseek(cfg->fd,0,SEEK_CUR);
            byte *dest=(byte *)&zrec->hdr.type_id;
            int test=0;

            if( (test=read(cfg->fd,src,read_len))==(int)read_len){
                //TODO: validate header
                // copy header bytes (past sync, already in place)
                memcpy(dest,src,read_len);
                state=ST_HDR_VALID;
                DPRINT("%10s%8s\n","hdr","OK");
            }else{
                fprintf(stderr,"ERR - read hdr [%d] [%d/%s]\n",test,errno,strerror(errno));
                // restore offset
                lseek(cfg->fd,current_ofs,SEEK_SET);
                // initiate sync
                state=ST_SYNC;
                hdr_errors++;
            }
        }
        if(action==AT_RD_DATA){
            uint32_t read_len=LI_ZREC_DATA_LEN(zrec);
            DPRINT("data read_len[%u]\n",read_len);
            byte src[read_len];
            ZERO_MEM(src,read_len);
            off_t current_ofs=lseek(cfg->fd,0,SEEK_CUR);
            int test=0;
            byte *dest=(byte *)mdata;
            if( (test=read(cfg->fd,src,read_len))==(int)read_len){
                //TODO: validate data
                // copy mdata bytes
                memcpy(dest,src,read_len);
                state=ST_DATA_VALID;
                record_count++;
                DPRINT("%10s%8s\n","data","OK");
            }else{
                fprintf(stderr,"ERR - read data [%d] [%d/%s]\n",test,errno,strerror(errno));
                // restore offset
                lseek(cfg->fd,current_ofs,SEEK_SET);
                // initiate sync
                state=ST_SYNC;
                data_errors++;
            }
        }
        if(action==AT_PROC_REC){

            size_t len = LI_ZREC_SIZE(zrec);

            // get time string (caller must free)
            char *tstr=striso8601(NULL,zrec->hdr.timestamp,ISO8601_PREC_MSEC);
            OUT_CONS("--- %s ---\n",tstr);
            licor_zrecord_show(zrec,true,5);
            OUT_CONS("\n");
            hex_show((byte *)zrec, len, 16, 5, true);
            OUT_CONS("\n");
            // release time string
            if(NULL!=tstr)free(tstr);

            state=ST_SYNC;
        }
        if(action==AT_QUIT){
            if(NULL!=zrec_mem)free(zrec_mem);
            sm_quit=true;
        }
    }

    VPRINT("record count  [%5d]\n",record_count);
    VPRINT("sync bytes    [%5d]\n",sync_bytes[0]);
    VPRINT("sync errors   [%5d]\n",sync_errors);
    VPRINT("header errors [%5d]\n",hdr_errors);
    VPRINT("data errors   [%5d]\n",data_errors);

    return retval;
}

static int s_app_main(app_cfg_t *cfg)
{
    int retval=-1;
    if(NULL!=cfg && NULL!=cfg->ifile){
        // do test
        if(cfg->test)
            retval=s_test_codec();

        if( (cfg->fd=open(cfg->ifile,O_RDONLY))>0){
            lseek(cfg->fd,0,SEEK_SET);
            s_state_mach(cfg);
            close(cfg->fd);
        }else{
            fprintf(stderr,"ERR - fopen failed [%d/%s]\n",errno,strerror(errno));
        }

        if(g_interrupt)VPRINT("interrupted - exiting\n");
        retval=0;
    }
    return retval;
}

int main(int argc,char **argv)
{
    int retval=-1;

    // configure signal handling
#if defined(__WIN32) || defined(__WIN64)
    signal(SIGINT,s_termination_handler);
#else
    // configure signal handling
    // for main thread
    struct sigaction saStruct;

    sigemptyset(&saStruct.sa_mask);
    saStruct.sa_flags = 0;
    saStruct.sa_handler = s_termination_handler;
    sigaction(SIGINT, &saStruct, NULL);
#endif

//    struct sigaction saStruct;
//    sigemptyset(&saStruct.sa_mask);
//    saStruct.sa_flags = 0;
//    saStruct.sa_handler = s_termination_handler;
//    sigaction(SIGINT, &saStruct, NULL);

    app_cfg_t *cfg = app_cfg_new();

    // parse args...
    parse_args(argc,argv,cfg);

    // call main
    retval=s_app_main(cfg);

    // release resources
    app_cfg_destroy(&cfg);

    return retval;
}

