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

/// Serial IO wrapper API

/// @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 
/////////////////////////
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <stdint.h>
#include <stdbool.h>

#if defined(__WIN32) || defined(_WIN64)
// for Sleep
//#include <SyncApi.h>
#include <synchapi.h>
// for com
#include <windows.h>
#include <tchar.h>

//#pragma message "Compiling __WIN32"
//define something for Windows (32-bit and 64-bit, this part is common)
#if _MSC_VER < 1900
#       define snprintf _snprintf
#endif //MSC_VER

#define WIN_DECLSPEC __declspec(dllimport)

#ifdef _WIN64
//define something for Windows (64-bit only)
#else // is WIN32
//define something for Windows (32-bit only)
#endif // WIN64

#elif defined(__unix__) || defined(__APPLE__) || defined(__CYGWIN__)
// POSIX
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#endif // platform-specific includes

#include "iowserial.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 "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"
*/

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

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

/////////////////////////
// Module Global Variables
/////////////////////////
bool g_wser_interrupt=false;

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

#if defined(__unix__) || defined(__APPLE__) || defined(__CYGWIN__)
// POSIX implementation (OSX, Linux, Cygwin)

static void s_delay_msec(uint32_t msec)
{
    if(msec>0){
        struct timespec delay={0};
        struct timespec rem={0};
        delay.tv_sec=msec/1000;
        delay.tv_nsec=MSEC2NSEC(msec%1000);

//        OUT_ERR("s_delay - msec[%"FMT_U32"] s[%ld] ns[%ld]\n",msec,delay.tv_sec,delay.tv_nsec);
        while(!g_wser_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 static int  s_set_speed(wser_t *self, int ispeed)
/// @brief set speed (baud)
/// @param[in] self mser_serial_t reference
/// @param[in] ispeed speed (bps)
/// @return 0 on success, -1 otherwise
static int  s_set_speed(wser_t *self, int ispeed)
{
    int retval=-1;
    if (NULL!=self) {

        speed_t speed=B0;
        bool valid=true;
        struct termios *terminal=&self->term;
        switch (ispeed) {
            case 0:
            speed=B0;
            break;
            case 50:
            speed=B50;
            break;
            case 75:
            speed=B75;
            break;
            case 110:
            speed=B110;
            break;
            case 134:
            speed=B134;
            break;
            case 150:
            speed=B150;
            break;
            case 200:
            speed=B200;
            break;
            case 300:
            speed=B300;
            break;
            case 600:
            speed=B600;
            break;
            case 1200:
            speed=B1200;
            break;
            case 1800:
            speed=B1800;
            break;
            case 2400:
            speed=B2400;
            break;
            case 4800:
            speed=B4800;
            break;
            case 9600:
            speed=B9600;
            break;
            case 19200:
            speed=B19200;
            break;
            case 38400:
            speed=B38400;
            break;
            case 57600:
            speed=B57600;
            break;
            case 115200:
            speed=B115200;
            break;
            case 230400:
            speed=B230400;
            break;
            default:
            valid=false;
            break;
        }
        if (valid &&
            (cfsetispeed(terminal,speed)==0) &&
            (cfsetospeed(terminal,speed)==0)){
            self->speed=speed;
            retval=0;
        }
    }else{
        fprintf(stderr,"ERR - invalid argument self[%p] device[%p]\n",self,(NULL==self?NULL:self->device));
    }

    return retval;
}
// End function s_set_speed

/// @fn static int  s_set_parity(wser_t *self, ser_parity_t parity)
/// @brief set parity setting
/// @param[in] self mser_serial_t reference
/// @param[in] parity parity setting
/// @return 0 on success, -1 otherwise
static int  s_set_parity(wser_t *self, ser_parity_t parity)
{
    int retval=-1;
    if (NULL!=self ) {
        bool valid=true;
        struct termios *terminal=&self->term;

        switch (parity) {
            case PAR_N:
            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
            break;
            case PAR_E:
            terminal->c_cflag |= PARENB;
            terminal->c_cflag &= ~PARODD;
            terminal->c_cflag |= (INPCK | ISTRIP);
            break;
            case PAR_O:
            terminal->c_cflag |= PARENB;
            terminal->c_cflag |= PARODD;
            terminal->c_cflag |= (INPCK | ISTRIP);
            break;

            default:
            valid=false;
            break;
        }
        if(valid && (retval=tcsetattr(self->fd, TCSANOW, terminal))==0){
            self->par=parity;
        }else{
            fprintf(stderr,"fd[%d] v[%d] term[%p] err[%d/%s]\n",self->fd,valid,terminal,errno,strerror(errno));
        }
    }
    return retval;
}
// End function s_set_parity

/// @fn static int  s_set_csize(wser_t *self, int csize)
/// @brief set character size (data bits)
/// @param[in] self mser_serial_t reference
/// @param[in] csize character size setting
/// @return 0 on success, -1 otherwise
static int  s_set_csize(wser_t *self, int csize)
{
    int retval=-1;
    if (NULL!=self) {
        bool valid=true;
        struct termios *terminal=&self->term;

        switch (csize) {
            case 5:
            terminal->c_cflag &= ~CSIZE;
            terminal->c_cflag |= CS5;
            break;
            case 6:
            terminal->c_cflag &= ~CSIZE;
            terminal->c_cflag |= CS6;
            break;
            case 7:
            terminal->c_cflag &= ~CSIZE;
            terminal->c_cflag |= CS7;
            break;
            case 8:
            terminal->c_cflag &= ~CSIZE;
            terminal->c_cflag |= CS8;
            break;

            default:
            valid=false;
            break;
        }
        if(valid && (retval=tcsetattr(self->fd, TCSANOW, terminal))==0){
            self->csize=csize;
        }
    }
    return retval;
}
// End function s_set_csize

/// @fn static int  s_set_stopb(wser_t *self, int stopb)
/// @brief set stop bits
/// @param[in] self mser_serial_t reference
/// @param[in] stopb stop bits setting
/// @return 0 on success, -1 otherwise
static int  s_set_stopb(wser_t *self, int stopb)
{
    int retval=-1;
    if (NULL!=self ) {
        bool valid=true;
        struct termios *terminal=&self->term;
        switch (stopb) {
            case 1:
            terminal->c_cflag &= ~CSTOPB;
            break;
            case 2:
            terminal->c_cflag |= CSTOPB;
            break;

            default:
            valid=false;
            break;
        }
        if(valid && (retval=tcsetattr(self->fd, TCSANOW, terminal))==0){
            self->cstop=stopb;
        }
    }
    return retval;
}
// End function s_set_stopb

/// @fn static int  s_set_flow(wser_t *self, mser_flow_t flow)
/// @brief set flow control
/// @param[in] self mser_serial_t reference
/// @param[in] flow flow control setting
/// @return 0 on success, -1 otherwise
static int  s_set_flow(wser_t *self, ser_flow_t flow)
{
    int retval=-1;
    if (NULL!=self) {
        bool valid=true;
        struct termios *terminal=&self->term;
        switch (flow) {
            case FLOW_N:
            terminal->c_cflag &= ~CRTSCTS;
            terminal->c_cflag &= ~(IXON | IXOFF | IXANY);
            break;
            case FLOW_H:
            terminal->c_cflag |= CRTSCTS;
            terminal->c_cflag &= ~(IXON | IXOFF | IXANY);
            break;
            case FLOW_X:
            terminal->c_cflag &= ~CRTSCTS;
            terminal->c_cflag |= (IXON | IXOFF | IXANY);
            break;
            default:
            valid=false;
            break;
        }
        if(valid && (retval=tcsetattr(self->fd, TCSANOW, terminal))==0){
            self->flow=flow;
        }
    }
    return retval;
}
// End function s_set_flow

/// @fn static int  s_set_raw(wser_t *self, unsigned min, unsigned time_dsec)
/// @brief set raw terminal mode
/// @param[in] self mser_serial_t reference
/// @param[in] min minimum number of characters to block for in non-canonical mode
/// @param[in] time_dsec timeout in non-canonical mode
/// @return 0 on success, -1 otherwise
static int  s_set_raw(wser_t *self, unsigned min, unsigned time_dsec)
{
    int retval=-1;
    if (NULL!=self){
    struct termios *terminal=&self->term;

        //        terminal->c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK);
        //
        //        terminal->c_oflag = 0;
        //
        //        terminal->c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN );
        //
        //        // Ignore modem control lines.
        //        terminal->c_cflag |= CLOCAL;

        cfmakeraw(terminal);

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

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

        retval=tcsetattr(self->fd, TCSANOW, terminal);
    }
    return retval;
}
// End function s_set_raw

wser_t *wser_new(const char *device)
{
    wser_t *instance = (wser_t *)malloc(sizeof(wser_t));
    if(NULL!=instance){
        memset(instance,0,sizeof(wser_t));
        instance->device=(NULL!=device ? strdup(device) : NULL);
        instance->speed=SER_SPEED_DFL;
        instance->csize=SER_CSIZE_DFL;
        instance->cstop=SER_CSTOP_DFL;
        instance->par=SER_PAR_DFL;
        instance->fd=-1;
    }
    return instance;
}

void wser_destroy(wser_t **pself)
{
    if(NULL!=pself){
        wser_t *self= *pself;
        if(NULL!=self){
            // restore terminal settings, close file
            wser_disconnect(self);
            if(NULL!=self->device){
                free(self->device);
            }
        }
        *pself=NULL;
    }
}

int wser_show(wser_t *self, bool verbose, int indent)
{
    int retval=-1;
    if(NULL!=self){
        int wkey=10;
        int wval=14;
        retval=OUT_CTTY("%*s%*s %*p%s",indent,"",wkey,"self",wval,self,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*s%s",indent,"",wkey,"device",wval,self->device,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"speed",wval,self->speed,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"csize",wval,self->csize,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"cstop",wval,self->cstop,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*c%s",indent,"",wkey,"par",wval,self->par,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"fd",wval,self->fd,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"wt_msec",wval,self->wt_msec,LINEEND);
    }
    return retval;
}

int wser_connect(wser_t *self, int speed, int csize, ser_parity_t parity, int cstop, ser_flow_t flow)
{
    int retval=-1;
    if(NULL!=self ){

        if(self->fd>=0){
            close(self->fd);
        }
        self->fd=-1;
        if( (self->fd = open(self->device,O_RDWR,O_NOCTTY|O_NONBLOCK ))>0 ){
            struct termios *term=&self->term;
            struct termios *oterm=&self->oterm;
            tcgetattr(self->fd,oterm);
            tcgetattr(self->fd,term);
            uint32_t test_flags=0x0;
            int i=0;
            int min=0;
            int time_dsec=0;
            WSER_TEST_SET(test_flags,(s_set_raw(self,min,time_dsec)==0),i++);
            WSER_TEST_SET(test_flags,(s_set_speed(self,speed)==0),      i++);
            WSER_TEST_SET(test_flags,(s_set_parity(self,parity)==0),    i++);
            WSER_TEST_SET(test_flags,(s_set_csize(self,csize)==0),      i++);
            WSER_TEST_SET(test_flags,(s_set_stopb(self,cstop)==0),      i++);
            WSER_TEST_SET(test_flags,(s_set_flow(self,flow)==0),        i++);
            if (test_flags==0) {
                retval=0;
            }else{
               OUT_ERR("ERR - config %08x\n",test_flags);
            }

        }else{
            OUT_ERR("ERR - open\n");
        }
        retval=0;
    }else{
        OUT_ERR("ERR - invalid arg\n");
    }
    return retval;
}

int wser_disconnect(wser_t *self)
{
    int retval=0;
    if(NULL!=self ){
        if(self->fd>=0){
            // restore original terminal settings
            tcsetattr(self->fd,TCSANOW,&self->oterm);
            close(self->fd);
        }
        self->fd=-1;
        retval=0;
    }
    return retval;
}

int wser_read(wser_t *self, char *dest, size_t len, uint32_t *br)
{
    int32_t retval=0;

    if(NULL!=self &&  self->fd>=0 && NULL!=dest){
        ssize_t test = read(self->fd, dest, len);
        ssize_t sslen = len;

        if(test == sslen){
            retval = 0;
        }
        if(NULL!=br && test>=0){
            *br = test;
        }
    }
    return retval;
}

int wser_write(wser_t *self, char *src, size_t len, uint32_t *bw)
{
    int retval=-1;

    if(NULL!=self &&  self->fd>=0 && NULL!=src){
//        OUT_CTTY("self %p src %s len %"FMT_SZ" delayms %"FMT_D32"\r\n",self,src,len,delay_msec);

        int err=0;
        ssize_t test=0;
        uint32_t count=0;
        if(self->wt_msec>0){
//            OUT_CTTY("%s%s",msg,LINEEND);
            size_t i=0;
            uint32_t udel=self->wt_msec;
            while(!g_wser_interrupt && i<len){
 
                // test write
                test=write(self->fd,&src[i],1);

                if( test==1 ){
                    i++;
                    count++;
                }else{
                    err++;
                }
                if(udel>0){
                s_delay_msec(udel);
                }
            }
        }else{
            if( (test=write(self->fd,src,len))==(ssize_t)len){
               count = (uint32_t)len;
            }else{
                err++;
            }
        }
        if(NULL!=bw){
            *bw =  count;
            OUT_CTTY("%s:%d len[%"FMT_SZ"] count:%"FMT_U32" bw[%p/%"FMT_U32"]\r\n",__func__,__LINE__,len,count,bw,*bw);
        }
        if(err==0){
            retval=0;
        }
//        OUT_CTTY("%s",LINEEND);
    }
    return retval;
}

#elif defined(__WIN32) || defined(_WIN64)

static void s_delay_msec(uint32_t msec)
{
    if(msec>0){
        struct timespec delay={0};
        struct timespec rem={0};
        delay.tv_sec=msec/1000;
        delay.tv_nsec=MSEC2NSEC(msec%1000);

//        OUT_ERR("s_delay - msec[%"FMT_U32"] s[%ld] ns[%ld]\n",msec,delay.tv_sec,delay.tv_nsec);
        while(!g_wser_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 static int  s_set_speed(wser_t *self, int ispeed)
/// @brief set speed (baud)
/// @param[in] self mser_serial_t reference
/// @param[in] ispeed speed (bps)
/// @return 0 on success, -1 otherwise
static int  s_set_speed(wser_t *self, int ispeed)
{
    int retval=-1;
    if (NULL!=self) {{

        bool valid=true;
        int speed = 9600;
        switch (ispeed) {
            case 0:
            break;
            case 50:
            break;
            case 75:
            break;
            case 110:
            break;
            case 134:
            break;
            case 150:
            break;
            case 200:
            break;
            case 300:
            break;
            case 600:
            break;
            case 1200:
            break;
            case 1800:
            break;
            case 2400:
            break;
            case 4800:
            break;
            case 9600:
            break;
            case 19200:
            break;
            case 38400:
            break;
            case 57600:
            break;
            case 115200:
            break;
            case 230400:
            break;
            default:
            valid=false;
            break;
        }
        if (valid){
            // update terminal
            // udpate struct
            self->speed=speed;
            retval=0;
        }
    }}else{
        fprintf(stderr,"ERR - invalid argument self[%p] device[%p]\n",self,(NULL==self?NULL:self->device));
    }

    return retval;
}
// End function s_set_speed

/// @fn static int  s_set_parity(wser_t *self, ser_parity_t parity)
/// @brief set parity setting
/// @param[in] self mser_serial_t reference
/// @param[in] parity parity setting
/// @return 0 on success, -1 otherwise
static int  s_set_parity(wser_t *self, ser_parity_t parity)
{
    int retval=-1;
    if (NULL!=self ) {
        bool valid=true;

        switch (parity) {
            case PAR_N:
            break;
            case PAR_E:
            break;
            case PAR_O:
            break;

            default:
            valid=false;
            break;
        }
        if(valid){
            // update terminal
            // update struct
            self->par=parity;
        }else{
//            fprintf(stderr,"fd[%d] v[%d] term[%p] err[%d/%s]\n",self->fd,valid,terminal,errno,strerror(errno));
        }
    }
    return retval;
}
// End function s_set_parity

/// @fn static int  s_set_csize(wser_t *self, int csize)
/// @brief set character size (data bits)
/// @param[in] self mser_serial_t reference
/// @param[in] csize character size setting
/// @return 0 on success, -1 otherwise
static int  s_set_csize(wser_t *self, int csize)
{
    int retval=-1;
    if (NULL!=self) {
        bool valid=true;
        switch (csize) {
            case 5:
            break;
            case 6:
            break;
            case 7:
            break;
            case 8:
            break;

            default:
            valid=false;
            break;
        }
        if(valid){
            // update terminal
            // update struct
            self->csize=csize;
        }
    }
    return retval;
}
// End function s_set_csize

/// @fn static int  s_set_stopb(wser_t *self, int stopb)
/// @brief set stop bits
/// @param[in] self mser_serial_t reference
/// @param[in] stopb stop bits setting
/// @return 0 on success, -1 otherwise
static int  s_set_stopb(wser_t *self, int stopb)
{
    int retval=-1;
    if (NULL!=self ) {
        bool valid=true;
        switch (stopb) {
            case 1:
            break;
            case 2:
            break;
            default:
            valid=false;
            break;
        }
        if(valid){
            // update terminal
            // update struct
            self->cstop=stopb;
        }
    }
    return retval;
}
// End function s_set_stopb

/// @fn static int  s_set_flow(wser_t *self, mser_flow_t flow)
/// @brief set flow control
/// @param[in] self mser_serial_t reference
/// @param[in] flow flow control setting
/// @return 0 on success, -1 otherwise
static int  s_set_flow(wser_t *self, ser_flow_t flow)
{
    int retval=-1;
    if (NULL!=self) {
        bool valid=true;
        switch (flow) {
            case FLOW_N:
            break;
            case FLOW_H:
            break;
            case FLOW_X:
            break;
            default:
            valid=false;
            break;
        }
        if(valid){
            // udpate terminal
            // update struct
            self->flow=flow;
        }
    }
    return retval;
}
// End function s_set_flow

/// @fn static int  s_set_raw(wser_t *self, unsigned min, unsigned time_dsec)
/// @brief set raw terminal mode
/// @param[in] self mser_serial_t reference
/// @param[in] min minimum number of characters to block for in non-canonical mode
/// @param[in] time_dsec timeout in non-canonical mode
/// @return 0 on success, -1 otherwise
static int  s_set_raw(wser_t *self, unsigned min, unsigned time_dsec)
{
    int retval=-1;
    if (NULL!=self){
        // set up raw terminal
    }
    return retval;
}
// End function s_set_raw

wser_t *wser_new(const char *device)
{
    wser_t *instance = (wser_t *)malloc(sizeof(wser_t));
    if(NULL!=instance){
        memset(instance,0,sizeof(wser_t));
        instance->device=(NULL!=device ? strdup(device) : NULL);
        instance->speed=SER_SPEED_DFL;
        instance->csize=SER_CSIZE_DFL;
        instance->cstop=SER_CSTOP_DFL;
        instance->par=SER_PAR_DFL;
        instance->fd=-1;
    }
    return instance;
}

void wser_destroy(wser_t **pself)
{
    if(NULL!=pself){
        wser_t *self= *pself;
        if(NULL!=self){
            // restore terminal settings, close file
            wser_disconnect(self);
            if(NULL!=self->device){
                free(self->device);
            }
        }
        *pself=NULL;
    }
}

int wser_show(wser_t *self, bool verbose, int indent)
{
    int retval=-1;
    if(NULL!=self){
        int wkey=10;
        int wval=14;
        retval=OUT_CTTY("%*s%*s %*p%s",indent,"",wkey,"self",wval,self,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*s%s",indent,"",wkey,"device",wval,self->device,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"speed",wval,self->speed,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"csize",wval,self->csize,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"cstop",wval,self->cstop,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*c%s",indent,"",wkey,"par",wval,self->par,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"fd",wval,self->fd,LINEEND);
        retval+=OUT_CTTY("%*s%*s %*d%s",indent,"",wkey,"wt_msec",wval,self->wt_msec,LINEEND);
    }
    return retval;
}

int wser_connect(wser_t *self, int speed, int csize, ser_parity_t parity, int cstop, ser_flow_t flow)
{
    int retval=-1;
    if(NULL!=self ){
        // close if open
        if(self->fd>=0){
//            close(self->fd);
        }
        // invalidate fd/handle
        self->fd=-1;

        // test open
//        self->fd = open(self->device,O_RDWR,O_NOCTTY|O_NONBLOCK )
        if( self->fd >0 ){
            // open successful, do configure
            int i=0;
            int min=0;
            int time_dsec=0;
            uint32_t test_flags=0x0;
            WSER_TEST_SET(test_flags,(s_set_raw(self,min,time_dsec)==0),i++);
            WSER_TEST_SET(test_flags,(s_set_speed(self,speed)==0),      i++);
            WSER_TEST_SET(test_flags,(s_set_parity(self,parity)==0),    i++);
            WSER_TEST_SET(test_flags,(s_set_csize(self,csize)==0),      i++);
            WSER_TEST_SET(test_flags,(s_set_stopb(self,cstop)==0),      i++);
            WSER_TEST_SET(test_flags,(s_set_flow(self,flow)==0),        i++);
            if (test_flags==0) {
                retval=0;
            }else{
               OUT_ERR("ERR - config %08x\n",test_flags);
            }

        }else{
            OUT_ERR("ERR - open\n");
        }
        retval=0;
    }else{
        OUT_ERR("ERR - invalid arg\n");
    }
    return retval;
}

int wser_disconnect(wser_t *self)
{
    int retval=0;
    if(NULL!=self ){
        // close if open
        if(self->fd>=0){
            // restore original terminal settings
//            tcsetattr(self->fd,TCSANOW,&self->oterm);
//            close(self->fd);
        }
        // invalidate fd/handle
        self->fd=-1;
        retval=0;
    }
    return retval;
}

int wser_read(wser_t *self, char *dest, size_t len, uint32_t *br)
{
    int32_t retval=0;

    if(NULL!=self &&  self->fd>=0 && NULL!=dest){
        ssize_t test = 0;//read(self->fd, dest, len);
        ssize_t sslen = len;

        if(test == sslen){
            retval = 0;
        }
        if(NULL!=br && test>=0){
            *br = test;
        }
    }
    return retval;
}

int wser_write(wser_t *self, char *src, size_t len, uint32_t *bw)
{
    int retval=-1;

    if(NULL!=self &&  self->fd>=0 && NULL!=src){
//        OUT_CTTY("self %p src %s len %"FMT_SZ" delayms %"FMT_D32"\r\n",self,src,len,delay_msec);

        int err=0;
            ssize_t test=0;
            uint32_t count=0;
        if(self->wt_msec>0){
//            OUT_CTTY("%s%s",msg,LINEEND);
            size_t i=0;
            uint32_t udel=self->wt_msec;
            while(!g_wser_interrupt && i<len){

                // test write
                test=0;//write(self->fd,&src[i],1);

                if( test==1 ){
                    i++;
                    count++;
                }else{
                    err++;
                }
                if(udel>0){
                s_delay_msec(udel);
                }
            }
        }else{
            test=0;//=write(self->fd,src,len)
            if( test==len){
               count = (uint32_t)len;
            }else{
                err++;
            }
        }
        if(NULL!=bw){
            *bw =  count;
        }
        if(err==0){
            retval=0;
        }
//        OUT_CTTY("%s",LINEEND);
    }
    return retval;
}

#endif // if defined(platform)


/////////////////
// Common
/////////////////

int wser_set_device(wser_t *self, const char *device)
{
    int retval=-1;
    if(NULL!=self && NULL!=device){
        if(NULL!=self->device){
            free(self->device);
        }
        self->device=strdup(device);
    }
    return retval;
}

int wser_set_wt(wser_t *self, int wt_msec)
{
    int retval=-1;
    if(NULL!=self){
        self->wt_msec=wt_msec;
        retval=0;
    }
    return retval;
}

void wser_signal_int(bool enable)
{
    g_wser_interrupt=enable;
}


// read/skip until timeout/buffer full/terminator match
int32_t wser_read_until(wser_t *self, char *term, char *dest, size_t len, int rt_sec)
{
    int32_t retval=-1;
    int32_t count=0;
    if(NULL!=self && self->fd>=0 && rt_sec>=0){

        char *mp=term;
        time_t start=time(NULL);
        time_t stop=start;
        bool match=false;
        size_t i=0;
        char cin=0;
        time_t tt_tmout=rt_sec;
        while( !g_wser_interrupt ){
            if(len>0 && i>=len){
                // buffer full/len condition met
                break;
            }
            if(rt_sec>0){
                if( ((stop=time(NULL))-start)>=tt_tmout){
                    // time out expired - exit
                    break;
                }
            }

            uint32_t br=0;
            if(wser_read(self,&cin,1,&br)==0 && br==1){
                count++;
                if(NULL!=dest && len>0 && i<len){
                    // byte to output
                    dest[i]=cin;
                }

                if(NULL!=term && cin==*mp){
                    // advance match state
                    mp++;
                    if(mp==(term+strlen(term))){
                        // match complete - exit
                        i++;
                        if(NULL!=dest && len>0 && i<len){
                            dest[i]='\0';
                        }
                        match=true;
                        break;
                    }
                }else{
                    // reset match search
                    mp=term;
                }
                // update index
                i++;
                if(NULL!=dest && len>0 && i<len){
                    dest[i]='\0';
                }
            }// else byte not available
        }
        retval=count;//strlen(dest);
//        OUT_CTTY("got dest[%p] term[%p] mp[%p] match[%c] t[%ld] %s",dest,term,mp,BOOLCHAR(match), (stop-start), LINEEND);
//        OUT_CTTY("dest[%s/%ld] %s",dest,strlen(dest),LINEEND);
    }

    return retval;
}


int32_t wser_write_read(wser_t *self, char *src, size_t olen, char *dest, size_t ilen, char *term, int rt_sec)
{
    int32_t retval=0;

    if(NULL!=self &&  self->fd>=0 && NULL!=src && NULL!=dest && ilen>0 ){
        OUT_ERR("TX[%s]%s",src,LINEEND);
        uint32_t bw=0;
        int test = wser_write(self,src,olen,&bw);
        if(test==0 && bw==(uint32_t)olen){
            retval=wser_read_until(self, term, dest, ilen, rt_sec);
        }

        OUT_ERR("RX[");
        int i=0;
        while(!g_wser_interrupt && retval>0 && i<retval){
            OUT_ERR((isprint((int)dest[i]) ? "%c":"<%02X>"),(byte)dest[i]);
            i++;
        }
        OUT_ERR("]/%"FMT_D32"%s",retval,LINEEND);
    }
    return retval;
}

int32_t wser_write_read_ln(wser_t *self, char *src, size_t olen, char *dest, size_t ilen, char *term, int rt_sec)
{
    int32_t retval=0;

    if(NULL!=self &&  self->fd>=0 && NULL!=src && NULL!=dest && ilen>0 ){
        OUT_ERR("TX[%s]%s",src,LINEEND);
        uint32_t bw=0;
        wser_write(self,src,olen,&bw);
        wser_write(self,CRLF,strlen(CRLF),&bw);
//        OUT_ERR("reading [%"FMT_SZ"/%d]\r\n",ilen,rt_sec);
        retval=wser_read_until(self, term, dest, ilen, rt_sec);
        OUT_ERR("RX[");
        int i=0;
        while(!g_wser_interrupt && retval>0 && i<retval){
            OUT_ERR((isprint((int)dest[i]) ? "%c":"<%02X>"),(byte)dest[i]);
            i++;
        }
        OUT_ERR("]/%"FMT_D32"%s",retval,LINEEND);
    }
    return retval;
}


int32_t wser_write_read_str(wser_t *self, char *src, char *dest, size_t ilen, char *term, int rt_sec)
{
    int32_t retval=-1;
    if(NULL!=self && NULL!=src && NULL!=dest && ilen>0){
//        memset(dest,0,ilen);
//        wser_wdrain_rx(self,3);
        retval=wser_write_read(self, src, strlen(src), dest, ilen, term, rt_sec);
    }
    return retval;
}

int32_t wser_drain_rx_base(wser_t *self, int retries, uint32_t delay_msec, time_t rt_sec)
{
    int32_t retval=-1;

    if(NULL!=self && self->fd>0){
        retval=0;
        char ibyte=0;
        int retry_count=0;
        time_t start=time(NULL);
        int cycles=0;
        OUT_ERR("RD {%d, %"FMT_U32", %ld}[",retries, delay_msec,rt_sec);
        while( !g_wser_interrupt ){
            if((time(NULL)-start)>rt_sec ){
                break;
            }
            if(delay_msec>0){
                // delay between retries (and before first read)
                s_delay_msec(delay_msec);
            }
            uint32_t br=0;
            if(wser_read(self,&ibyte,1,&br)==0 && br==1){
                OUT_ERR((isprint((int)ibyte) ? "%c":"<%02X>"),(byte)ibyte);
                // update bytes read
                retval++;
                // reset retry counter
                retry_count=0;
            }else{
                if(retries<0){
                    // ignore retries
                }else if(retries>0){
                    if( retry_count > retries){
                        break;
                    }
                    retry_count++;
                }else{
                    break;
                }
            }
            cycles++;
        }
        OUT_ERR("]/%"FMT_D32"/%d %s",retval,cycles,LINEEND);
    }
    return retval;
}

// drain or return immediately if no input available
int32_t wser_drain_rx(wser_t *self, time_t rt_sec)
{
    return wser_drain_rx_base(self, 0, 0, rt_sec);
}

