/*
    zserial_tcp - portable serial port API
	Module for TCP connection
    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>

#include <zdebug.h>
#include <zerror.h>
#include <zsock.h>

#include <errno.h>

#ifdef Z_HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#ifdef Z_HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif


static int zserial_tcp_open(struct zserial *zser){
    int ret;
    union zsockaddr sa;

    if (zser->sock >= 0) return 0; 
    dbg("zserial_tcp_open(%s)\n", zser->id);

    if (z_sock_aton(zser->hostname, zser->port, &sa)) return -1;

    zser->sock = socket(sa.in.sin_family, SOCK_STREAM, 0);
    dbg("sock=%d family=%d\n", zser->sock, sa.in.sin_family);
    if (zser->sock < 0) return -1;

    ret = z_sock_connect(zser->sock, &sa, 3000);
    dbg("z_sock_connect=%d\n", ret);
    if (ret < 0) return -1;

    ret = z_sock_nonblock(zser->sock, 1);
    if (ret < 0) return ret;

    zser->opened = 1;
    return 0;
}

static int zserial_tcp_read(struct zserial *zser, void *data, int len, int timeout_ms){
    int ret;
    fd_set fdr;
    struct timeval tv;
            
    FD_ZERO(&fdr);
    FD_SET(zser->sock, &fdr);
    tv.tv_usec = timeout_ms * 1000;
    tv.tv_sec = 0;
    
    ret = select(zser->sock + 1, &fdr, NULL, NULL, &tv);
//        dbg("select=%d\n", ret);
    if (ret < 0) {
        g_string_printf(zser->errorstr, "Can't select %s: ", zser->id);
        z_strerror(zser->errorstr, errno);
        zserial_close(zser);
        return ret;
    }
    if (ret == 0) {
        g_string_printf(zser->errorstr, "Can't read %s: Timeout", zser->id);
        return 0;
    }
    if (!FD_ISSET(zser->sock, &fdr)){
        g_string_printf(zser->errorstr, "Can't read %s: Data not available", zser->id);
        zserial_close(zser);
    }

    ret = recv(zser->sock, (void *)data, len, 0);
    if (ret < 0){
        g_string_printf(zser->errorstr, "Can't read from %s: ", zser->id);
        z_strerror(zser->errorstr, errno);
        zserial_close(zser);
    }
    return ret;

}

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

    ret = send(zser->sock, (void *)data, len, 0);
    if (ret < 0){
        g_string_printf(zser->errorstr, "Can't write to %s: ", zser->id);
        z_strerror(zser->errorstr, errno);
        zserial_close(zser);
    }
    return ret;
}

static int zserial_tcp_close(struct zserial *zser){
    int ret = 0;
    if (zser->sock >= 0) ret = closesocket(zser->sock);
    zser->sock = -1;
    return ret;
}

static int zserial_tcp_dtr(struct zserial *zser, int on){
    return -1;
}

static int zserial_tcp_rts(struct zserial *zser, int on){
    return -1;
}


struct zserial *zserial_init_tcp(const char *hostname, const int tcpport){
    struct zserial *zser = zserial_init();
    zser->type = ZSERTYPE_TCP;
    zser->id = g_strdup_printf("TCP:%s:%d", hostname, tcpport);
    zser->hostname = g_strdup(hostname);
    zser->port = tcpport;

    zser->zs_open = zserial_tcp_open;
    zser->zs_read = zserial_tcp_read;
    zser->zs_write = zserial_tcp_write;
    zser->zs_close = zserial_tcp_close;
    zser->zs_dtr = zserial_tcp_dtr;
    zser->zs_rts = zserial_tcp_rts;
    return zser;
}

