/*
    zserial_ftdi - portable serial port API
	Module for FTDI USB converters using libftdi
    Copyright (C) 2012-2023 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <zserial.h>

#ifdef Z_HAVE_LIBFTDI

#include <zdebug.h>
#include <zthread.h>
#include <ztime.h>

#include <ctype.h>
#ifdef Z_HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef Z_HAVE_LIBFTDI
#include <ftdi.h>
#endif


static int zserial_ftdi_open(struct zserial *zser){
    enum ftdi_parity_type par;

    if (zser->ftdi) return 0;

    zser->ftdi = ftdi_new();
    if (!zser->ftdi){
        g_string_printf(zser->errorstr, "Can't alloc ftdi context (out of memory?)");
        zserial_close(zser);
        return -1;
    }

    if (zser->serial && *zser->serial){
        if (ftdi_usb_open_desc(zser->ftdi, zser->vid, zser->pid, NULL, zser->serial)){
            g_string_printf(zser->errorstr, "Can't open FTDI device %04X:%04X-%s : %s", zser->vid, zser->pid, zser->serial, ftdi_get_error_string(zser->ftdi));  
            zserial_close(zser);
            return -1;
        }
        zser->id = g_strdup_printf("%04X:%04X-%s", zser->vid, zser->pid, zser->serial);
    }else{
        if (ftdi_usb_open(zser->ftdi, zser->vid, zser->pid)){
            g_string_printf(zser->errorstr, "Can't open FTDI device %04X:%04X : %s", zser->vid, zser->pid, ftdi_get_error_string(zser->ftdi));  
            zserial_close(zser);
            return -1;
        }
        zser->id = g_strdup_printf("%04X:%04X", zser->vid, zser->pid);
    }
    
    if (ftdi_set_baudrate(zser->ftdi, zser->baudrate)){
        g_string_printf(zser->errorstr, "Can't set baudrate for %s: %s", zser->id, ftdi_get_error_string(zser->ftdi));
        zserial_close(zser);
        return -1;
    }
    switch(toupper(zser->parity)){
        case 'O': par = ODD; break;
        case 'E': par = EVEN; break;
        case 'M': par = MARK; break;
        case 'S': par = SPACE; break;
        default: par = NONE; break;
    }
    if (ftdi_set_line_property(zser->ftdi, 
        zser->bits == 7 ? BITS_7 : BITS_8,
        zser->stopbits == 2 ? STOP_BIT_2 : STOP_BIT_1,
        par)){

        g_string_printf(zser->errorstr, "Can't set line for %s: %s", zser->id, ftdi_get_error_string(zser->ftdi));
        zserial_close(zser);
        return -1;
    }
    zser->opened = 1;
    return 0;
}

static int zserial_ftdi_read(struct zserial *zser, void *data, int len, int timeout_ms){
    int r, tmo;

    tmo = ztimeout_init(timeout_ms);
    while(!ztimeout_occured(tmo)){
        r = ftdi_read_data(zser->ftdi, (unsigned char*)data, len);
        if (r < 0) { // usb device unavailable or usb_bulk_read failed
            g_string_printf(zser->errorstr, "Can't read from %s: %s", zser->id, ftdi_get_error_string(zser->ftdi));
            zserial_close(zser);
            return -1;
        }
        if (r > 0) return r;
        // no data was available
        usleep(1000);
    }

    return 0; // timeout occured
}

static int zserial_ftdi_write(struct zserial *zser, void *data, int len){
    int ret;

    ret = ftdi_write_data(zser->ftdi, (unsigned char *)data, len);
    if (ret < 0){
        g_string_printf(zser->errorstr, "Can't write to %s: %s", zser->id, ftdi_get_error_string(zser->ftdi));
        zserial_close(zser);
        return -1;
    }
    return ret;
}

static int zserial_ftdi_close(struct zserial *zser){
    if (zser->ftdi) ftdi_free(zser->ftdi);
    zser->ftdi = NULL;
    return 0;
}

static int zserial_ftdi_dtr(struct zserial *zser, int on){
    if (ftdi_setdtr(zser->ftdi, on)){ // TODO polarity
        g_string_printf(zser->errorstr, "Can't set DTR on %s: %s", zser->id, ftdi_get_error_string(zser->ftdi));  
        return -1;
    }
    return 0;
}

static int zserial_ftdi_rts(struct zserial *zser, int on){
    dbg("zserial_rts(zser->ftdi=%p\n", zser->ftdi);
    if (ftdi_setrts(zser->ftdi, on)){ // TODO polarity
        g_string_printf(zser->errorstr, "Can't set RTS on %s: %s", zser->id, ftdi_get_error_string(zser->ftdi));  
        return -1;
    }
    return 0;
}
#endif


struct zserial *zserial_init_ftdi(const int vid, const int pid, char *serial){
#ifdef Z_HAVE_LIBFTDI
    struct zserial *zser = zserial_init();
	zser->type = ZSERTYPE_FTDI;
    zser->id = g_strdup_printf("%04X:%04X", vid, pid);
    zser->vid = vid;
    zser->pid = pid;
    zser->serial = NULL;
    if (serial) zser->serial = g_strdup(serial);

    zser->zs_open = zserial_ftdi_open;
    zser->zs_read = zserial_ftdi_read;
    zser->zs_write = zserial_ftdi_write;
    zser->zs_close = zserial_ftdi_close;
    zser->zs_dtr = zserial_ftdi_dtr;
    zser->zs_rts = zserial_ftdi_rts;
    return zser;
#else
	return NULL;
#endif
}

