
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#if defined(__APPLE__) || defined(__CYGWIN__) || defined(__LINUX__)
#include <termios.h>
#include <errno.h>
#endif

#include "logutils.h"
#include "licor8xx-shared.h"
#include "cwg-shared.h"
#include "iowfile.h"

#if defined(__APPLE__) || defined(__CYGWIN__) || defined(__LINUX__)
#define GL_DEVICE_DFL NULL
#define GL_START_DFL 0
#define GL_LAST_DFL 0
#define GL_COUNT_DFL -1
#define GL_OFLAGS_DFL (QOF_BASE85|QOF_HEADER|QOF_DATA)
#define GL_FD_DFL -1
#define MSEC2NSEC(ms) (ms*1000000L)

typedef struct app_cfg_s{
    char *device;
    int start;
    unsigned int last;
    int count;
    queue_output_flag oflags;
    int fd;
    struct termios terminal;
    struct termios term_save;
}app_cfg_t;

/// @fn void s_parse_args(int argc, char **argv, app_cfg_t *cfg)
/// @brief parse command line arguments.
/// @param[in] argc number of args
/// @param[in] argv argument strings
/// @param[in] cfg configuration instance
/// @return none
static void s_parse_args(int argc, char **argv, app_cfg_t *cfg)
{
    int i=0;
    if(argc>1){
        if(NULL!=cfg->device){
            free(cfg->device);
        }
        cfg->device=strdup(argv[1]);
        for(i=2;i<argc;i++){
            if(sscanf(argv[i],"s%d",&cfg->start)==1 || sscanf(argv[i],"S%d",&cfg->start)==1){
            }
            if(sscanf(argv[i],"c%d",&cfg->count)==1 || sscanf(argv[i],"C%d",&cfg->count)==1){
            }
            if(sscanf(argv[i],"l%u",&cfg->last)==1 || sscanf(argv[i],"L%u",&cfg->last)==1){
                cfg->start=-cfg->last;
                cfg->count=cfg->last;
            }
            if(strstr(argv[i],"OUT:")!=NULL || strstr(argv[i],"out:")!=NULL){

                cfg->oflags=0;

                if(strstr(argv[i]+4,"X*") || strstr(argv[i]+4,"x*")){
                    cfg->oflags|=QOF_HEX|QOF_PRETTY_HEX;
                }else if(strstr(argv[i]+4,"XX") || strstr(argv[i]+4,"xx")){
                    cfg->oflags|=QOF_PRETTY_HEX;
                }else if(strstr(argv[i]+4,"X") || strstr(argv[i]+4,"x")){
                    cfg->oflags|=QOF_HEX;
                }
                char *cp=argv[i]+4;
                while(*cp!='\0'){
                    if(*cp=='P' || *cp=='p')cfg->oflags|=QOF_PRETTY;
                    if(*cp=='B' || *cp=='b')cfg->oflags|=QOF_BASE85;
                    if(*cp=='C' || *cp=='c')cfg->oflags|=QOF_CSV;
                    if(*cp=='R' || *cp=='r')cfg->oflags|=QOF_RAW;
                    if(*cp=='U' || *cp=='u')cfg->oflags|=QOF_UUENCODE;
                    if(*cp=='H' || *cp=='h')cfg->oflags|=QOF_HEADER;
                    if(*cp=='D' || *cp=='d')cfg->oflags|=QOF_DATA;
                    if(*cp=='V' || *cp=='v')cfg->oflags|=QOF_VERBOSE;
                    cp++;
                }
            }
        }

        // use pretty by default
        if( (cfg->oflags&QOF_FMT_FLAGS) == 0){
            cfg->oflags|=QOF_PRETTY;
        }
        if( ((cfg->oflags&QOF_CONTENT_FLAGS) == 0) ||
           ((cfg->oflags&QOF_CONTENT_FLAGS) == QOF_VERBOSE))cfg->oflags|=QOF_HEADER|QOF_DATA;
    }
}
// End function s_parse_args

/// @fn void s_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:
            fprintf(stderr,"\nsig received[%d]\n",signum);
            g_logutils_interrupt=true;
            break;
        default:
            fprintf(stderr,"\ns_termination_handler: sig not handled[%d]\n",signum);
            break;
    }
}
// End function termination_handler

/// @fn int s_configure_terminal(app_cfg_t *cfg)
/// @brief configure terminal IO settings.
/// @param[in] cfg configuration instance
/// @return 0 for success, -1 otherwise
static int s_configure_terminal(app_cfg_t *cfg)
{
    int retval=-1;
    if(NULL!=cfg && NULL!=cfg->device){

	    cfg->fd = open(cfg->device,O_RDWR,O_NOCTTY|O_NONBLOCK);
        if(cfg->fd>0){
            tcgetattr(cfg->fd,&cfg->term_save);
            tcgetattr(cfg->fd,&cfg->terminal);
            struct termios *terminal = &cfg->terminal;

            // set raw
            cfmakeraw(terminal);

            // Minimum number of characters for noncanonical read (MIN).
            terminal->c_cc[VMIN]  = 0;//min;

            // Timeout in deciseconds for noncanonical read (TIME).
            terminal->c_cc[VTIME] = 0;//time_dsec;

            // set speed
            int err_count=0;
            speed_t speed=B38400;
            if(cfsetispeed(terminal,speed)!=0){
                err_count++;
                fprintf(stderr,"setispeed failed [%d/%s]\n",errno,strerror(errno));
            }

            if(cfsetospeed(terminal,speed)!=0){
                err_count++;
                fprintf(stderr,"setospeed failed [%d/%s]\n",errno,strerror(errno));
            }

            if(err_count==0){
                // set parity
                terminal->c_cflag &= ~PARENB;
                // Cygwin returns invalid argument exception
                // when ISTRIP is cleared
#if defined(__CYGWIN__)
                terminal->c_cflag &= ~(INPCK);
#else
                terminal->c_cflag &= ~(INPCK | ISTRIP);
#endif
                // set csize
                terminal->c_cflag &= ~CSIZE;
                terminal->c_cflag |= CS8;

                // set stopb
                terminal->c_cflag &= ~CSTOPB;

                // set flow
                terminal->c_cflag &= ~CRTSCTS;
                terminal->c_cflag &= ~(IXON | IXOFF | IXANY);

                // apply settings
                if( (retval=tcsetattr(cfg->fd, TCSANOW, terminal))!=0){
                    fprintf(stderr,"tcsetattr failed [%d/%s]\n",errno,strerror(errno));
                }
            }
        }else{
            fprintf(stderr,"open failed for [%s] [%d/%s]\n",cfg->device,errno,strerror(errno));
        }
    }else{
        fprintf(stderr,"%s - invalid arg\n",__func__);
    }
    return retval;
}
// End function s_configure_terminal

static void s_delay_msec(long msec)
{
    struct timespec delay={0};
    struct timespec rem={0};
    delay.tv_sec=msec/1000L;
    delay.tv_nsec=MSEC2NSEC(msec%1000);
//    fprintf(stderr,"s[%ld] ns[%ld]\n",delay.tv_sec,delay.tv_nsec);
    while(!g_logutils_interrupt && nanosleep(&delay,&rem)!=0){
        // interrupted - update sleep time w/ remainder
        delay.tv_sec=rem.tv_sec;
        delay.tv_nsec=rem.tv_nsec;
        // reset remainder
        rem.tv_sec=0L;
        rem.tv_nsec=0L;
    }
    return;
}

/// @fn int s_app_main(app_cfg_t *cfg)
/// @brief application main.
/// @param[in] cfg configuration instance
/// @return 0 for success, -1 otherwise
static int s_app_main(app_cfg_t *cfg)
{
    int retval=-1;
    if(NULL!=cfg && NULL!=cfg->device){

        if(s_configure_terminal(cfg)==0){
            fprintf(stderr,"configured [%s]\n",cfg->device);
            char cmd[128]={0};
            sprintf(cmd,"lcat DAT0520.000 out:PH\r\n");
            write(cfg->fd,cmd,strlen(cmd));
            s_delay_msec(20L);
            char rbuf[256]={0};
            size_t rlen=0;
            while( !g_logutils_interrupt && (rlen=read(cfg->fd,rbuf,256))>0){
                fwrite(rbuf,1,rlen,stderr);
                memset(rbuf,0,256);
            }
            // restore terminal settings and close
            tcsetattr(cfg->fd, TCSANOW, &cfg->term_save);
            close(cfg->fd);

        }else{
            fprintf(stderr,"could not configure terminal [%s]\n",cfg->device);
        }
    }else{
        fprintf(stderr,"%s - invalid argument [%s]\n",__func__,(NULL==cfg?"config":"device"));
    }
    return retval;
}
// End function s_app_main

int main(int argc, char **argv)
{
    int retval=-1;
#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

    app_cfg_t cfg_s={
        GL_DEVICE_DFL,
        GL_START_DFL,
        GL_LAST_DFL,
        GL_COUNT_DFL,
        GL_OFLAGS_DFL,
        GL_FD_DFL,
        {0},
        {0}
    };
    app_cfg_t *cfg=&cfg_s;

    s_parse_args(argc,argv,cfg);


    fprintf(stderr,"TODO: structure as client telemetry driver? generic cmd TX/RX?\n");

    OUT_ERR("device[%s] s[%d] c[%d] oflags[%08"FMT_FLAG"]%s",cfg->device,cfg->start,cfg->count,cfg->oflags,LINEEND);
    retval = s_app_main(cfg);

    return retval;
}
#endif // supported platforms
