/*
 * scbus utils to switch card settings.
 *
 * Copyright (C) 2011 Otvos Attila oattila@onebithq.com
 *
 * This 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 2 of the License, or
 * (at your option) any later version.
 *
 * This 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.
 *
 * You should have received a copy of the GNU General Public License along
 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */



#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <term.h>
#include <stdarg.h>
#include <sys/time.h>
#include <pthread.h>

#define _POSIX_SOURCE 1

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyUSB0"

#define BUFFER_SIZE                     8192

#define ERROR_NOERROR                   0
#define ERROR_NOINIT                    1
#define ERROR_INVALID_BAUD              2
#define ERROR_INVALID_SERIAL_DEVICE     3

#ifdef __cplusplus
extern "C" {
#endif

char* rs485_error[] = {
    "No error.",
    "RS485 not inited.",
    "Invalid baud rate.",
    "Inavlid serial device."};

typedef struct rs485_priv_s {
    int fd;
    int errorno;
    int run;
    struct termios newtio;
    struct termios oldtio;
    long BAUD;
    long DATABITS;
    long STOPBITS;
    long PARITYON;
    long PARITY;
    char rxbuffer[BUFFER_SIZE];
    char txbuffer[BUFFER_SIZE];
    int  rx_rptr;
    int  rx_wptr;
    int  tx_rptr;
    int  tx_wptr;
    int  tx_cptr;
    int  collision;
    int  valid;
    int  wmode;

    pthread_mutex_t mutex;
    pthread_t       thread;
} rs485_priv_t;

void setRTS(void* priv, int set) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;
    int portstatus;
    int res;

    if(!priv)
        return;
    if(rs485_priv->fd<0)
        return;
    ioctl(rs485_priv->fd, TIOCMGET, &portstatus);   // get current port status
    portstatus &= ~TIOCM_RTS;
    if(set)
        portstatus |= TIOCM_RTS;
    res=ioctl(rs485_priv->fd, TIOCMSET, &portstatus);   // set current port status
}

static void* rs485_listen(void *priv) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;
    int res;
    int wcnt=0;
    unsigned char rc;

    struct timeval tv;
    fd_set fds;
    int n,f;

    rs485_priv->run=1;
    while(rs485_priv->run) {

        FD_ZERO(&fds);
        FD_SET(rs485_priv->fd,&fds);
        tv.tv_sec=0;
        tv.tv_usec=1000;
        n = select(rs485_priv->fd+1,&fds,NULL,NULL,&tv);
        f = FD_ISSET(rs485_priv->fd,&fds);
        res=0;
        rc=0;
        if(f)
            res=read(rs485_priv->fd,&rc,1);
        if(rs485_priv->wmode)
            wcnt++;
        else
            wcnt=0;
        if(res==1 || wcnt>5) {
            wcnt=0;
            pthread_mutex_lock(&rs485_priv->mutex);
            if(rs485_priv->tx_cptr<rs485_priv->tx_wptr) {
                if(rs485_priv->txbuffer[rs485_priv->tx_cptr++]!=rc) {
                    rs485_priv->collision=1;
                    rs485_priv->wmode=0;
                    setRTS(rs485_priv,1);
                    rs485_priv->tx_cptr=rs485_priv->tx_rptr=rs485_priv->tx_wptr=0;
                }
            }
            if(rs485_priv->tx_rptr<rs485_priv->tx_wptr) {
                res=write(rs485_priv->fd,&rs485_priv->txbuffer[rs485_priv->tx_rptr],1);
                if(res>0)
                    rs485_priv->tx_rptr+=res;
            }
            if(rs485_priv->tx_wptr && rs485_priv->tx_rptr==rs485_priv->tx_wptr && rs485_priv->tx_cptr==rs485_priv->tx_wptr) {
                rs485_priv->wmode=0;
                setRTS(rs485_priv,1);
                rs485_priv->tx_cptr=rs485_priv->tx_rptr=rs485_priv->tx_wptr=0;
            }
            if(rs485_priv->rx_wptr<BUFFER_SIZE && res) {
                rs485_priv->rxbuffer[rs485_priv->rx_wptr++]=rc;
            }
            if(rc=='\n') {
                rs485_priv->valid=1;
            }
            pthread_mutex_unlock(&rs485_priv->mutex);
        }
    }
    return NULL;
}

void* init_rs485(int baud, const char *device) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)calloc(1,sizeof(rs485_priv_t));

    rs485_priv->fd=-1;
    rs485_priv->DATABITS = CS8;
    rs485_priv->STOPBITS = 0;
    rs485_priv->PARITYON = 0;
    rs485_priv->PARITY = 0;
    if(!device) {
        rs485_priv->errorno=ERROR_INVALID_SERIAL_DEVICE;
        return rs485_priv;
    }
    switch(baud) {
    case 9600:
        rs485_priv->BAUD = B9600;
        break;
    case 19200:
        rs485_priv->BAUD = B19200;
        break;
    case 38400:
        rs485_priv->BAUD = B38400;
        break;
    case 57600:
        rs485_priv->BAUD = B57600;
        break;
    case 1152000:
        rs485_priv->BAUD = B1152000;
        break;
    default:
        rs485_priv->errorno=ERROR_INVALID_BAUD;
        return rs485_priv;
    }
    if(0>(rs485_priv->fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK))) {
        free(rs485_priv);
        return NULL;
    }
    tcgetattr(rs485_priv->fd,&rs485_priv->oldtio);
    rs485_priv->newtio=rs485_priv->oldtio;
    rs485_priv->newtio.c_cflag = rs485_priv->BAUD | rs485_priv->DATABITS | rs485_priv->STOPBITS | rs485_priv->PARITYON | rs485_priv->PARITY | CLOCAL | CREAD | IGNBRK | IGNPAR;
    rs485_priv->newtio.c_oflag= 0;
    rs485_priv->newtio.c_lflag= 0;
    tcsetattr(rs485_priv->fd,TCSANOW,&rs485_priv->newtio);
    pthread_mutex_init(&rs485_priv->mutex, NULL);
    pthread_create(&rs485_priv->thread, NULL, rs485_listen, rs485_priv);
    rs485_priv->run=1;
    rs485_priv->wmode=0;
    setRTS(rs485_priv,1);
    return rs485_priv;
}

int inited_rs485(void *priv) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;

    if(!priv)
        return 0;
    if(rs485_priv->fd<0)
        return 0;
    return 1;
}

char* geterror_rs485(void *priv) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;

    if(!priv)
        return rs485_error[ERROR_NOINIT];
    if(rs485_priv->errorno==ERROR_INVALID_BAUD);
        return rs485_error[ERROR_INVALID_BAUD];
    if(rs485_priv->errorno==ERROR_INVALID_SERIAL_DEVICE);
        return rs485_error[ERROR_INVALID_SERIAL_DEVICE];
    return rs485_error[ERROR_NOERROR];
}

void done_rs485(void *priv) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;

    if(!priv)
        return;

    if(rs485_priv->run) {
        pthread_mutex_lock(&rs485_priv->mutex);
        rs485_priv->run=0;
        pthread_mutex_unlock(&rs485_priv->mutex);
        pthread_join(rs485_priv->thread, NULL);
        pthread_mutex_destroy(&rs485_priv->mutex);
    }
    if(rs485_priv->fd>=0) {
        tcsetattr(rs485_priv->fd,TCSANOW,&rs485_priv->oldtio);
        rs485_priv->wmode=0;
        setRTS(rs485_priv,1);
        close(rs485_priv->fd);
    }
    free(rs485_priv);
}

static void shiftrxbuffer(rs485_priv_t* rs485_priv) {
    char tmpbuffer[BUFFER_SIZE];
    int len=rs485_priv->rx_wptr-rs485_priv->rx_rptr;

    if(!len) {
        rs485_priv->rx_rptr=rs485_priv->rx_wptr=0;
        return;
    }
    if(len<0 || len>BUFFER_SIZE || rs485_priv->rx_rptr+len>BUFFER_SIZE) {
//        fprintf(stderr,"rptr: %d wptr: %d len: %d\n",rs485_priv->rx_rptr,rs485_priv->rx_wptr,len);
        return;
    }
    memcpy(tmpbuffer,rs485_priv->rxbuffer+rs485_priv->rx_rptr,len);
    memcpy(rs485_priv->rxbuffer,tmpbuffer,len);
    rs485_priv->rx_rptr=0;
    rs485_priv->rx_wptr=len;
}

int read_rs485(void *priv, char* buffer, int maxlen) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;
    int len = 0;

    if(!priv)
        return -1;
    if(rs485_priv->fd<0)
        return -1;
    pthread_mutex_lock(&rs485_priv->mutex);
    if(rs485_priv->rx_rptr>rs485_priv->rx_wptr) {
        len=rs485_priv->rx_wptr-rs485_priv->rx_rptr;
        if(len>maxlen)
            len=maxlen;
        memcpy(buffer,&rs485_priv->rxbuffer[rs485_priv->rx_rptr],len);
        rs485_priv->rx_rptr+=len;
    }
    shiftrxbuffer(rs485_priv);
    pthread_mutex_unlock(&rs485_priv->mutex);
    return len;
}

int readline_rs485(void *priv, char* buffer, int maxlen) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;
    int len = 0;
    int line = -1;
    int i;

    if(!priv)
        return -1;
    if(rs485_priv->fd<0)
        return -1;
    pthread_mutex_lock(&rs485_priv->mutex);
    if(rs485_priv->rx_rptr<rs485_priv->rx_wptr) {
        len=rs485_priv->rx_wptr-rs485_priv->rx_rptr;
        for(i=0;i<len;i++)
            if(rs485_priv->rxbuffer[i]=='\n') {
                line=i+1;
                break;
            }
        if(line==-1) {
            pthread_mutex_unlock(&rs485_priv->mutex);
            return 0;
        }
        len=line;
        if(len>maxlen)
            len=maxlen;
        memcpy(buffer,&rs485_priv->rxbuffer[rs485_priv->rx_rptr],len);
        rs485_priv->rx_rptr+=len;
    }
    shiftrxbuffer(rs485_priv);
    pthread_mutex_unlock(&rs485_priv->mutex);
    return len;
}

int isdata_rs485(void *priv) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;
    int ret;

    if(!priv)
        return 0;
    if(rs485_priv->fd<0)
        return 0;
    pthread_mutex_lock(&rs485_priv->mutex);
    ret=rs485_priv->valid;
    rs485_priv->valid=0;
    pthread_mutex_unlock(&rs485_priv->mutex);
    return ret;
}

int write_rs485(void *priv, char* buffer, int len) {
    rs485_priv_t* rs485_priv = (rs485_priv_t*)priv;
    int res;

    if(!priv)
        return -1;
    if(rs485_priv->fd<0)
        return -1;
    if(len>BUFFER_SIZE)
        return -1;
    pthread_mutex_lock(&rs485_priv->mutex);
    if(rs485_priv->tx_wptr) {
        pthread_mutex_unlock(&rs485_priv->mutex);
        return 0;
    }
    memcpy(rs485_priv->txbuffer,buffer,len);
    rs485_priv->tx_rptr=0;
    rs485_priv->tx_cptr=0;
    setRTS(rs485_priv,0);
    rs485_priv->wmode=1;
    usleep(1000);
    res=write(rs485_priv->fd,&rs485_priv->txbuffer[rs485_priv->tx_rptr],1);
    if(res>0)
        rs485_priv->tx_rptr+=res;
    rs485_priv->tx_wptr=len;
    pthread_mutex_unlock(&rs485_priv->mutex);
    return len;
}

#ifdef __cplusplus
};
#endif
