/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2021 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 "header.h"
#include "fifo.h"
#include "language2.h"
#include "main.h"
#include "rc.h"
#include "sdev.h"
#include "tsdl.h"
#include "zstring.h"
    

#define STARTB 4
#define STOPB 1

struct sconn sconns;
MUTEX_DEFINE(sconns);

void init_sconns(void){
	progress(VTEXT(T_INIT_SERIAL_DEVICES));		

    MUTEX_INIT(sconns);
    init_list(sconns);
}



struct sdev *sd_open_ttys(char saddr, char *filename, int timeout_ms){
    struct sconn *sc;
	struct sdev *sd;
    
/*    dbg("sd_open_ttys(%s)\n", filename);*/
    
    MUTEX_LOCK(sconns);
    foreach(sc, sconns){
        if (sc->type!=CT_TTYS) continue;
        if (strcmp(sc->ttys_filename, filename)!=0) continue;
        /*dbg("found sc=%p\n", sc);*/
    	sc->refcnt++;
        MUTEX_UNLOCK(sconns);
        goto found;
    }
    MUTEX_UNLOCK(sconns);
    /* sconn not found */
    sc=g_new0(struct sconn,1);
    dbg("new sc %p\n",sc);
    sc->refcnt++;
    MUTEX_LOCK(sconns);
    add_to_list(sconns, sc);
    MUTEX_UNLOCK(sconns);
    sc->type=CT_TTYS;
    sc->ttys_filename=g_strdup(filename);
    sc->sprotocol = SPROT_NONE;
#ifdef Z_MSC_MINGW
	sc->zser = zserial_init_win32(sc->ttys_filename);
#else
	sc->zser = zserial_init_tty(sc->ttys_filename);
#endif
	zserial_nolocks(sc->zser, cfg->nolocks);
    sc_open_common(sc);
    
found:    
    sd=g_new0(struct sdev,1);
    sc->give_me_chance = 1;
    MUTEX_LOCK(sc->sdevs);
    sc->give_me_chance = 0;
    add_to_list(sc->sdevs, sd);
    MUTEX_UNLOCK(sc->sdevs);

    sd->sconn=sc;
    sd->saddr=saddr;
    sd->timeout_ms = timeout_ms;
    /*dbg("sd=%p refcnt=%d\n",sd,sc->refcnt);*/
    return sd;
}

    
struct sdev *sd_open_ftdi(char saddr, int vid, int pid, char *serial, int timeout_ms){
    struct sconn *sc;
	struct sdev *sd;
    
/*    dbg("sd_open_ttys(%s)\n", filename);*/
    
    MUTEX_LOCK(sconns);
    foreach(sc, sconns){
        if (sc->type!=CT_FTDI) continue;
        if (serial && *serial)
            if (strcmp(sc->ftdi_serial, serial)!=0) continue;
        /*dbg("found sc=%p\n", sc);*/
    	sc->refcnt++;
        MUTEX_UNLOCK(sconns);
        goto found;
    }
    MUTEX_UNLOCK(sconns);
    /* sconn not found */
    sc=g_new0(struct sconn,1);
    dbg("new sc %p\n",sc);
    sc->refcnt++;
    MUTEX_LOCK(sconns);
    add_to_list(sconns, sc);
    MUTEX_UNLOCK(sconns);
    sc->type=CT_FTDI;
    sc->ftdi_vid = vid;
    sc->ftdi_pid = pid;
    sc->ftdi_serial = g_strdup(serial?serial:"");
    sc->sprotocol = SPROT_ROTAR;
    sc->zser = zserial_init_ftdi(sc->ftdi_vid, sc->ftdi_pid, sc->ftdi_serial);

    if (sc->zser) sc_open_common(sc);
    
found:    
    sd=g_new0(struct sdev,1);
    sc->give_me_chance = 1;
    MUTEX_LOCK(sc->sdevs);
    sc->give_me_chance = 0;
    add_to_list(sc->sdevs, sd);
    MUTEX_UNLOCK(sc->sdevs);

    sd->sconn=sc;
    sd->saddr=saddr;
    sd->timeout_ms = timeout_ms;
    /*dbg("sd=%p refcnt=%d\n",sd,sc->refcnt);*/
    return sd;
}
    
    
void sc_open_common(struct sconn *sc){

    MUTEX_INIT(sc->sdevs);
    init_list(sc->sdevs);

    MUTEX_INIT(sc->jobs);
    init_list(sc->jobs);

	sc->thread = g_thread_try_new("sc_main", sc_main, (gpointer)sc, NULL);
    //dbg("sc created thread %p\n", sc->thread);
}

struct sdev *sd_open_udp(char *hostname, int udpport){
    return NULL;    
}

struct sdev *sd_open_tcp(char *hostname, int tcpport){
    
    return NULL;
}

int free_sd(struct sdev *sd){
    struct sconn *sc;
    
/*    dbg(" ------\n");*/
    dbg("free_sd(%p)\n", sd);

    if (!sd) return -1;

    sc=sd->sconn;
   /* dbg("  refcnt=%d\n", sc->refcnt);*/
    sc->give_me_chance = 1;
    MUTEX_LOCK(sc->sdevs);
    sc->give_me_chance = 0;
    del_from_list(sd);
    MUTEX_UNLOCK(sc->sdevs);
    
	sc->refcnt--;

	if (!sc->refcnt){
        
        /*dbg("  freeing sc %p\n", sc);*/
        sc->freeing = 1;
        if (sc->thread){
            sc->thread_break = 1;
            dbg("join sconn...\n");
            g_thread_join(sc->thread);
            dbg("done\n");
            sc->thread = NULL;
        }
        
        sc->give_me_chance = 1;
        MUTEX_LOCK(sc->sdevs);
        sc->give_me_chance = 0;
        del_from_list(sd);
        MUTEX_UNLOCK(sc->sdevs);

        zg_free0(sc->ttys_filename);
        zg_free0(sc->ip_hostname);
#ifdef Z_HAVE_LIBFTDI
        zg_free0(sc->ftdi_serial);
#endif
        MUTEX_LOCK(sconns);
        del_from_list(sc);
        MUTEX_UNLOCK(sconns);
        g_free(sc);
    }else{
        /*dbg("  sc %p already active\n", sc);*/
    }
    g_free(sd);
    return (0);
}

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

int sconn_open(struct sconn *sconn, int verbose){
    if (sconn->freeing) return -1;
    if (sconn->opened) return 0;

	zserial_set_line(sconn->zser, 9600, 8, 'E', 1);

	if (zserial_open(sconn->zser)){
		if (verbose) zselect_msg_send(zsel, "SC;!;%s", zserial_errorstr(sconn->zser));
		return -1;
	}
//	zserial_rts(sconn->zser, 1);
//    zserial_dtr(sconn->zser, 1);
	zserial_rts(sconn->zser, 0); /* clear PSEN */ /* clear = log.1 */
    zserial_dtr(sconn->zser, 0); /* clear reset*/ /* clear */

    sconn->opened = 1;
    return 0;
}

int sconn_close(struct sconn *sconn){
	zserial_close(sconn->zser);
    sconn->opened = 0;
    return 0;
}


int sd_prot(struct sconn *sconn, char saddr, char fce, char *data, int *len, int timeout){
	unsigned char rawdata[550];
	int rawlen,written,rawi;
    int ret, i;
    static unsigned int sernr = 0;

    ret = sconn_open(sconn, 0);
    if (ret) return ret;
    
    /* clearing queue, filedescriptor is non-blocking */
/*    for (i=0; i<10; i++){
        if (sconn->read(sconn, 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]=sd_chk(rawdata+STARTB, rawlen-STARTB);
    rawlen++;
	memset(rawdata+rawlen, 0xff, STOPB); rawlen+=STOPB;
	if (cfg->trace_sdev){
		dbg("\n%5d sconn->write(", sernr++ % 100000);
		for (i=0; i<rawlen; i++) dbg("%02x ", (unsigned char)rawdata[i]);
		dbg("\n");
	}
	written=zserial_write(sconn->zser, rawdata, rawlen);
	trace(cfg->trace_sdev, "sd_send: written=%d\n", written);
    if (written<0) return written;

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

        
		ret = zserial_read(sconn->zser, rawdata+rawi, sizeof(rawdata)-rawi, timeout);
//        dbg("read=%d\n", ret);
        if (ret<0) return -3;
        if (ret==0) return -4;
        rawi += ret;
		if (cfg->trace_sdev)        
        {
            int j;
            dbg("read=");
            for (j=0; j<rawi; j++) dbg("%02x ", (unsigned char)rawdata[j]);
            dbg("\n");
        }
        
        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 (sd_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;
}



char *sd_err(int err){
    switch(err){
        case 0:
            return "OK";
        case 11:
            return VTEXT(T_BAD_CHECKSUM);
        case 13:
            return VTEXT(T_TIMEOUT2);
        case 14:
            return VTEXT(T_ERROR);
        case 16:
            return VTEXT(T_BAD_RESPONSE);
        case 17:
            return VTEXT(T_UNKNOWN_FUNCTION);
        default:
            return VTEXT(T_UNKNOWN_ERROR_CODE);
    }

}


gpointer sc_main(gpointer xxx){
	char data[256];
	int len;
//    struct sslave *sptr; 
    struct sconn *sconn = (struct sconn *)xxx;
         
    dbg("sc_main\n");
	zg_thread_set_name("Tucnak sc_main");

        
    sconn_open(sconn, 1);
    
    while(!sconn->thread_break){
        struct sconn_job *job;
        struct sdev *sdev;
        int i, chrapej = 0;

        if (sconn->give_me_chance) {
            usleep(10000);
            continue;
        }
        MUTEX_LOCK(sconn->sdevs);
        job = sconn_job_get(sconn);
        //dbg("job=%p\n", job);
        if (!job){
            i = 0;
            foreach (sdev, sconn->sdevs) i++; // number of sdevs
            if (i==0) {
                error("sc_main: sconn->sdevs is empty");
                sleep(1);
            }
            if (sconn->sdevi >= i) sconn->sdevi = 0;
            i = sconn->sdevi;
            sdev = NULL;
            foreach (sdev, sconn->sdevs) {
                if (!i--) break;
            }
           //  dbg("sdevi=%d sdev=%p main=%p\n", sconn->sdevi, sdev, sdev->sdev_main);
            if (sdev->sdev_main) {
                if (sdev->sdev_main(sdev)) chrapej++;
            }
            sconn->sdevi++;     
            MUTEX_UNLOCK(sconn->sdevs);
            if (chrapej) goto zzzzz;
            continue;
        }
        switch(job->cmd){
            case SCONN_ROT_AZIM:
                data[0] = job->azim & 0xff;
                data[1] = (job->azim >> 8) & 0xff;
                len=2;
				dbg("sd_prot(%d, %d)\n", job->sdev->saddr, job->azim);
                sd_prot(sconn, job->sdev->saddr, 65, data, &len, job->sdev->timeout_ms);
                break;
        }
        MUTEX_UNLOCK(sconn->sdevs);
        continue;
zzzzz:;
        //for (i=0; i<20;i++){
          //  if (sconn->thread_break) break;
            usleep(100000);
        //}
    }
    dbg("sc_main exiting\n");
    sconn_close(sconn);
    return NULL;
}

void sconn_job_add(struct sconn *sconn, struct sconn_job *job){
    MUTEX_LOCK(sconn->jobs);
    add_to_list(sconn->jobs, job);
    MUTEX_UNLOCK(sconn->jobs);
}

struct sconn_job *sconn_job_get(struct sconn *sconn){
    struct sconn_job *job;

    MUTEX_LOCK(sconn->jobs);
    if (list_empty(sconn->jobs)){
        MUTEX_UNLOCK(sconn->jobs);
        return NULL;
    }
    job = sconn->jobs.prev; 
    del_from_list(job);
    MUTEX_UNLOCK(sconn->jobs);
    return job;
}
