/*
    zserial - portable serial port API
    Copyright (C) 2011-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 <libziaint.h>
#include <zserial.h>

#include <zdebug.h>
//#include <zerror.h>
//#include <zfhs.h>
#include <zsock.h>
#include <zthread.h>
//#include <ztime.h>

#include <glib.h>

//#include <ctype.h>
//#include <errno.h>
//#include <fcntl.h>
#include <string.h>
//#ifdef Z_HAVE_SYS_SELECT
//#include <sys/select.h>
//#endif
#ifdef Z_HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
//#include <sys/stat.h>
//#include <sys/types.h>
//#ifdef Z_HAVE_TERMIOS_H
//#include <termios.h>
//#endif
#ifdef Z_HAVE_UNISTD_H
#include <unistd.h>
#endif
//#ifdef Z_HAVE_WINSOCK2_H
//#include <winsock2.h>
//#endif
//#ifdef Z_HAVE_WINDOWS_H
//#include <windows.h>
//#endif

static void zserial_free_ports(struct zserial *zser);


struct zserial *zserial_init(void){
    struct zserial *zser = g_new0(struct zserial, 1);
#ifdef Z_HAVE_TERMIOS_H
    zser->fd = -1;
#endif
#ifdef Z_HAVE_LIBFTDI
    zser->ftdi = NULL;
#endif
#ifdef WIN32
    zser->handle = INVALID_HANDLE_VALUE;
	zser->prochandle = INVALID_HANDLE_VALUE;
	zser->procstdin = INVALID_HANDLE_VALUE;
	zser->procstdout = INVALID_HANDLE_VALUE;
#endif
#ifdef Z_HAVE_PTY_H
	zser->master = -1;
#else
    zser->read_fd = -1;
    zser->write_fd = -1;
#endif
	zser->errorstr = g_string_new("");
    zser->baudrate = 9600;
    zser->bits = 8;
    zser->parity = 'N';
    zser->stopbits = 1;
	zser->pipefds[0] = -1;
	zser->pipefds[1] = -1;
    zser->sock = -1;
//    dbg("zserial_init()\n");
	MUTEX_INIT(zser->close3);
	return zser;
}

struct zserial *zserial_init_serial(const char *device){
#ifdef Z_HAVE_TERMIOS_H
    return zserial_init_tty(device);
#else
    return zserial_init_win32(device);
#endif
}

struct zserial *zserial_init_process(const char *cmd, const char *arg){
#ifdef Z_MSC_MINGW
    return zserial_init_proc_win32(cmd, arg);
#elif defined(Z_HAVE_PTY_H)
    return zserial_init_proc_pty(cmd, arg);
#else
    return zserial_init_proc_pipe(cmd, arg);
#endif
}


void zserial_set_line(struct zserial *zser, int baudrate, int bits, char parity, int stopbits){
    zser->baudrate = baudrate;
    zser->bits = bits;
    zser->parity = parity;
    zser->stopbits = stopbits;
}

void zserial_unsupported(struct zserial *zser, const char *fce){
    char *typestr;
    switch (zser->type){
        case ZSERTYPE_TTY:          typestr = "ZSERTYPE_TTY"; break;
        case ZSERTYPE_FTDI:         typestr = "ZSERTYPE_FTDI"; break;
        case ZSERTYPE_WIN32:        typestr = "ZSERTYPE_WIN32"; break;
        case ZSERTYPE_TCP:          typestr = "ZSERTYPE_TCP"; break;
        case ZSERTYPE_PROC_WIN32:   typestr = "ZSERTYPE_PROC_WIN32"; break;
        case ZSERTYPE_PROC_PTY:     typestr = "ZSERTYPE_PROC_PTY"; break;
        case ZSERTYPE_PROC_PIPE:    typestr = "ZSERTYPE_PROC_PIPE"; break;
        default:                    typestr = NULL; break;
    }
    if (typestr){
		g_string_printf(zser->errorstr, "%s: %s unsupported on this platform", fce, typestr);
    }else{
		g_string_printf(zser->errorstr, "%s: zser type %d unsupported on this platform", fce, zser->type);
    }
}


int zserial_open(struct zserial *zser){

    if (!zser->zs_open) {
        zserial_unsupported(zser, __FUNCTION__);
        return -1;
    }
    return zser->zs_open(zser);
}

int zserial_is_opened(struct zserial* zser) {
    return zser->opened;
}


int zserial_read(struct zserial *zser, void *data, int len, int timeout_ms){

    if (zserial_open(zser)) return -1;
    
    if (!zser->zs_read) {
        zserial_unsupported(zser, __FUNCTION__);
        return -1;
    }
    return zser->zs_read(zser, data, len, timeout_ms);
}

int zserial_write(struct zserial *zser, void *data, int len){
    int ret;
    
//    if (zserial_open(zser)) return -1;
    ret = zserial_open(zser);
    //dbg("zserial_open returns %d\n", ret);
    if (ret) return -1;
    
    if (!zser->zs_write) {
        zserial_unsupported(zser, __FUNCTION__);
        return -1;
    }
    return zser->zs_write(zser, data, len);
}

int zserial_close(struct zserial *zser){
    int ret = 0;

	//if (zser->thread_break) 
		//return 0; // called probably from zserial_thread_func after TerminateProcess was called, must ignore
	//if (zser->thread && zser->thread == g_thread_self()) 
//		return 0; // called from zserial_thread_func, must ignore to prevent g_thread_join(self)

	zser->thread_break = 1;

    if (zser->zs_close){
        ret |= zser->zs_close(zser);
    }

	if (zser->pipefds[0] >= 0 && zser->thread != g_thread_self()) { // zser->thread can be NULL
		closesocket(zser->pipefds[0]);
		zser->pipefds[0] = -1;
	} 
	if (zser->pipefds[1] >= 0) {
		closesocket(zser->pipefds[1]);
		zser->pipefds[1] = -1;
	}

	if (zser->thread){
		if (zser->thread != g_thread_self()){
			g_thread_join(zser->thread);
			zser->thread = NULL;
		}
	}
    zser->opened = 0;
	return ret;
}

void zserial_free(struct zserial *zser){
//    dbg("zserial_free()\n");
    zserial_close(zser);
	g_string_free(zser->errorstr, TRUE);
	if (zser->filename) g_free(zser->filename);
	if (zser->cmd) g_free(zser->cmd);
    if (zser->hostname) g_free(zser->hostname);
#ifdef Z_HAVE_LIBFTDI
    g_free(zser->serial);
#endif
    g_free(zser->id);
    if (zser->ports){
        zserial_free_ports(zser);
        g_ptr_array_free(zser->ports, TRUE);
    }
    g_free(zser);
}


const char *zserial_errorstr(struct zserial *zser){
    return zser->errorstr->str;
}

void zserial_clear_errorstr(struct zserial *zser){
    g_string_truncate(zser->errorstr, 0);
}

int zserial_dtr(struct zserial *zser, int on){
    
    if (zserial_open(zser)) return -1;

    if (!zser->zs_dtr){
        zserial_unsupported(zser, __FUNCTION__);
        return -1;
    }

    return zser->zs_dtr(zser, on);
}

int zserial_rts(struct zserial *zser, int on){
    
    if (zserial_open(zser)) return -1;
    
    if (!zser->zs_rts){
        zserial_unsupported(zser, __FUNCTION__);
        return -1;
    }

    return zser->zs_rts(zser, on);
}


static char zserial_prot_chk(unsigned char *s, int len){
	unsigned char chk, *c;
    chk=0;
	for (c=s;len;c++,len--){
		chk^=*c;
	}
	return chk;
}

#define STARTB 2
#define STOPB 1
int zserial_prot(struct zserial *zser, char saddr, char fce, char *data, int *len, int timeout){
	unsigned char rawdata[550];
	int rawlen,written,rawi;
    int ret, i;

    if (zserial_open(zser)) return -1;

    /* clearing queue, filedescriptor is non-blocking */
/*    for (i=0; i<10; i++){
        if (zserial->read(zserial, rawdata, sizeof(rawdata)-1, 100)<=0) break;}*/
	rawlen=0;
	memset(rawdata, 0xff, STARTB); rawlen+=STARTB;
	rawdata[rawlen++]=0xc5;
	rawdata[rawlen++]=fce&0x7f;
	rawdata[rawlen++]=saddr;
	rawdata[rawlen++]=(unsigned char)*len;
    memcpy(rawdata+rawlen, data, *len); rawlen+=*len;
	rawdata[rawlen]=zserial_prot_chk(rawdata+STARTB, rawlen-STARTB);
    rawlen++;
	memset(rawdata+rawlen, 0xff, STOPB); rawlen+=STOPB;
	written=zserial_write(zser, rawdata, rawlen);
	if (1){
        GString *gs = g_string_new("\nzserial_prot: write(");
		for (i=0; i<rawlen; i++) {
            if (i > 0) g_string_append_c(gs, ' ');
            g_string_append_printf(gs, "%02x", (unsigned char)rawdata[i]);
        }
		g_string_append_printf(gs, ") = %d\n", written);
        dbg("%s", gs->str);
        g_string_free(gs, TRUE);
	}
    if (written<0) {
        *len = 0;
        return written;
    }

    if (saddr == 255) return 0; // broadcast

    rawi=0;
    while(1){
        if (rawi>=sizeof(rawdata)-1) return 20;

        
        ret = zserial_read(zser, rawdata+rawi, 1/*sizeof(rawdata)-rawi*/, timeout);
//        dbg("read=%d\n", ret);
        if (ret<0) return ret;
        if (ret==0) return -4;
        rawi += ret;
		if (0)        
        {
            GString *gs = g_string_new("zserial_prot: read=(");
            int j;
            for (j=0; j<rawi; j++) {
                if (j > 0) g_string_append_c(gs, ' ');
                g_string_append_printf(gs, "%02x", (unsigned char)rawdata[j]);
            }
            g_string_append(gs, ")\n");
            dbg("%s", gs->str);
        }
        
        for (i=0; i<rawi; i++){
            if (rawdata[i]!=0xc5) continue;
#if 0            
            {
                int j;
                dbg("c5 at %d\n", i);
                for (j=0; j<rawi; j++) dbg("%02x ", (unsigned char)rawdata[j]);
                dbg("\n");
            }
#endif            
           // dbg("i+5>rawi %d+5>%d=%d\n", i, rawi, i+5>rawi);
            if (i+5>rawi) goto nextloop;
           // dbg("i+5+rawdata[i+3]>rawi %d+5+%d>%d\n", i, (unsigned char)rawdata[i+3], rawi);
            if (i+5+(unsigned char)rawdata[i+3]>rawi) goto nextloop;
           // dbg("b\n");
			if (1)        
			{
				GString *gs = g_string_new("zserial_prot: read=(");
				int j;
				for (j=0; j<rawi; j++) {
					if (j > 0) g_string_append_c(gs, ' ');
					g_string_append_printf(gs, "%02x", (unsigned char)rawdata[j]);
				}
				g_string_append(gs, ")\n");
				dbg("%s", gs->str);
			}


            if (zserial_prot_chk(rawdata+i, 5+rawdata[i+3]) != 0) return 11;
            if (rawdata[i+1]==0) return 17;
            if (rawdata[i+1]==0x80) return 14;
            if ((rawdata[i+1] & 0x80)==0) continue;
//            dbg("c\n");
            if (rawdata[i+2]!=(unsigned char)saddr) return 16;
//            dbg("d\n");
            if (rawdata[i+1]!=(fce|0x80)) return 16;

            *len = rawdata[i+3];
            memcpy(data, rawdata+i+4, *len);
/*            {
                int j;
                dbg("OK data=\n");
                for (j=0; j<*len; j++) dbg("%02x ", (unsigned char)data[j]);
                dbg("\n");
            }*/
            return 0;
        }
        //dbg("neni C5 0..%d\n", rawi);
nextloop:;        
    }
    

    
    return 0;
}

static gpointer zserial_thread_func(gpointer xxx){
	char buf[4096];
	int ret;

	struct zserial *zser = (struct zserial*)xxx;

	while(!zser->thread_break){
		ret = zserial_read(zser, buf, sizeof(buf), 10);
		if (ret < 0){
			return NULL;
			/*if (zser->thread_break) break;               // faster exit
			usleep(1000000);
			continue;*/
		}
		if (ret > 0){
			send(zser->pipefds[1], buf, ret, 0);
		}
	}
    return NULL;
}

int zserial_fd(struct zserial *zser){

	switch (zser->type){
		case ZSERTYPE_TTY:
#ifdef Z_HAVE_TERMIOS_H
			return zser->fd;
#else
			g_string_printf(zser->errorstr, "zserial_fd: ZSERTYPE_TTY unsupported on this platform");
			return -1;
#endif
        case ZSERTYPE_TCP:
            return zser->sock;
		case ZSERTYPE_FTDI:
		case ZSERTYPE_WIN32:
        case ZSERTYPE_PROC_WIN32:
			if (zser->pipefds[0] < 0){
				if (z_pipe(zser->pipefds)) zinternal("Can't create pipe");
				zser->thread_break = 0;
				//zser->thread = g_thread_create(zserial_thread_func, zser, TRUE, NULL);
				zser->thread = g_thread_try_new("zserial", zserial_thread_func, zser, NULL);
			}
			return zser->pipefds[0];
        case ZSERTYPE_PROC_PTY:
#ifdef Z_HAVE_PTY_H
            return zser->master;
#else
            return -1;
#endif
        case ZSERTYPE_PROC_PIPE:
            return zser->read_fd;
	}
	return -1;
}

const char *zserial_id(struct zserial *zser){
	return zser->id;
}

void zserial_nolocks(struct zserial *zser, int nolocks){
	zser->nolocks = nolocks;
}

static void zserial_free_ports(struct zserial *zser){
    unsigned i;
    
    for (i = 0; i < zser->ports->len; i++){
        struct zserial_port *port = (struct zserial_port*)g_ptr_array_index(zser->ports, i);
        g_free(port->filename);
        g_free(port->desc);
    }
}


int zserial_detect(struct zserial *zser){

    if (!zser->ports) zser->ports = g_ptr_array_new();
    zserial_free_ports(zser);
    
    if (!zser->zs_detect){
        zserial_unsupported(zser, __FUNCTION__);
        return 0;
    }

    return zser->zs_detect(zser);
}

