/*
    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 "control.h"
#include "cwdaemon.h"
#include "davac4.h"
#include "davac5.h"
#include "dsp.h"
#include "edi.h"
#include "fifo.h"
#include "inpout.h"
#include "inputln.h"
#include "main.h"
#include "namedb.h"
#include "ppdev.h"
#include "qsodb.h"
#include "rc.h"
#include "session.h"
#include "ssbd.h"
#include "terminal.h"
#include "trig.h"
#include "tsdl.h"
#include "ttys.h"
#include "winkey.h"

struct cwdaemon *cwda;

struct cwdaemon *init_cwdaemon(void){
    struct cwdaemon *cwda;
    int fds[2];

	progress(VTEXT(T_INITIALIZING_CWPTT_CONTROL));		

  /*  sound(880);
    sleep(1);
    sound(0);
    sleep(1);
    sound(1760);
    sleep(1);
    sound(0);
    sleep(1);  */
    cwda = g_new0(struct cwdaemon, 1);
    
    switch(cfg->cwda_type){
        case CWD_PARPORT:
#ifdef HAVE_LINUX_PPDEV_H
            /* ppdev.h */
            cwda->init    = parport_init;
            cwda->free    = parport_free;
            cwda->reset   = parport_reset;
            cwda->text    = cwdaemon_text;
            cwda->sspeed  = cwdaemon_speed;
            cwda->sweight = cwdaemon_weight;
            cwda->cw      = parport_cw;
            cwda->ptt     = parport_ptt;
            cwda->ssbway  = parport_ssbway;
            cwda->back    = cwdaemon_back;
            cwda->band    = parport_band;
#elif defined(Z_MSC_MINGW_CYGWIN)
            /* inpout.h */
            cwda->init    = parport_init;
            cwda->free    = parport_free;
            cwda->reset   = parport_reset;
            cwda->text    = cwdaemon_text;
            cwda->sspeed  = cwdaemon_speed;
            cwda->sweight = cwdaemon_weight;
            cwda->cw      = parport_cw;
            cwda->ptt     = parport_ptt;
            cwda->ssbway  = parport_ssbway;
            cwda->back    = cwdaemon_back;
            cwda->band    = parport_band;
#else
            log_addf(VTEXT(T_NO_PPDEV));
            dbg("Support for ppdev/inpout isn't compiled in\n");
#endif            
            break;
            
        case CWD_TTYS:
            cwda->init    = ttys_init;
            cwda->free    = ttys_free;
            cwda->reset   = ttys_reset;
            cwda->text    = cwdaemon_text;
            cwda->sspeed  = cwdaemon_speed;
            cwda->sweight = cwdaemon_weight;
            cwda->cw      = ttys_cw;
            cwda->ptt     = ttys_ptt;
            cwda->ssbway  = ttys_ssbway;
            cwda->back    = cwdaemon_back;
            break;

		case CWD_TTYS_SINGLE:
			cwda->init = ttys_init;
			cwda->free = ttys_free;
			cwda->reset = ttys_reset;
			cwda->text = cwdaemon_text;
			cwda->sspeed = cwdaemon_speed;
			cwda->sweight = cwdaemon_weight;
			cwda->cw = ttys_cw_single;
			cwda->ptt = ttys_ptt_single;
			cwda->ssbway = ttys_ssbway;
			cwda->back = cwdaemon_back;
			break;
		case CWD_DAVAC4:
#ifdef Z_HAVE_LIBFTDI
            cwda->init    = davac4_init;
            cwda->free    = davac4_free;
            cwda->reset   = davac4_reset;
            cwda->text    = cwdaemon_text;
            cwda->sspeed  = cwdaemon_speed;
            cwda->sweight = cwdaemon_weight;
            cwda->cw      = davac4_cw;
            cwda->ptt     = davac4_ptt;
            cwda->ssbway  = davac4_ssbway;
            cwda->monitor = davac4_monitor;
            cwda->back    = cwdaemon_back;
            cwda->band    = davac4_band;
            MUTEX_INIT(cwda->ftdi);
#else			
            log_addf(VTEXT(T_NO_DAVAC4_SUPPORT));
            dbg("Support for davac4 isn't compiled in, check for libftdi");
#endif
            break;
        case CWD_DAVAC5:
#ifdef HAVE_HIDAPI
            cwda->init = davac5_init;
            cwda->free = davac5_free;
            cwda->reset = davac5_reset;
            cwda->text = cwdaemon_text;
            cwda->sspeed = cwdaemon_speed;
            cwda->sweight = cwdaemon_weight;
            cwda->cw = davac5_cw;
            cwda->ptt = davac5_ptt;
            cwda->ssbway = davac5_ssbway;
            cwda->back = cwdaemon_back;
            MUTEX_INIT(cwda->hid);
#else			
            log_addf(VTEXT(T_NO_DAVAC5_SUPPORT));
            dbg("Support for davac5 isn't compiled in, check for hidapi");
#endif
            break;
            
        case CWD_CWD:
            cwda->init    = cwd_init;
            cwda->free    = cwd_free;
            cwda->reset   = cwd_reset;
            cwda->text    = cwd_text;
            cwda->sspeed  = cwd_speed;
            cwda->sweight = cwd_weight;
            cwda->cw      = cwd_cw;
            cwda->ptt     = cwd_ptt;
            cwda->ssbway  = cwd_ssbway;
            cwda->echo    = cwd_echo;
            cwda->band    = cwd_band;
//            cwda->dtune   = cwd_tune;
            break;
			
		case CWD_WINKEY:
		case CWD_WINKEYTCP:
            if (cfg->cwda_type == CWD_WINKEY) 
				cwda->init = winkey_init_serial;
			else
			   	cwda->init = winkey_init_tcp;
            cwda->free    = winkey_free;
            cwda->reset   = winkey_reset;
            cwda->text    = winkey_text;
            cwda->sspeed  = winkey_speed;
            cwda->sweight = winkey_weight;
            cwda->cw      = winkey_cw;
            cwda->ptt     = winkey_ptt;
            cwda->ssbway  = winkey_ssbway;
            cwda->dtune   = winkey_tune;
            cwda->back    = winkey_back;
            break;
		case CWD_HAMLIB:
#ifdef HAVE_HAMLIB
			cwda->text    = cwd_hamlib_text;
			cwda->reset   = cwd_hamlib_reset;
#else
			log_addf("No support for hamlib");
#endif
/*        case CWD_WINKEY4:
            cwda->init    = winkey4_init;
            cwda->free    = winkey4_free;
            cwda->reset   = winkey4_reset;
            cwda->text    = winkey_text;
            cwda->sspeed  = winkey_speed;
            cwda->sweight = winkey_weight;
            cwda->cw      = winkey_cw;
            cwda->ptt     = winkey_ptt;
            cwda->ssbway  = winkey_ssbway;
            cwda->dtune   = winkey_tune;
            cwda->back    = winkey_back;
#ifdef Z_HAVE_LIBFTDI
            cwda->ptt     = davac4_ptt;
            cwda->ssbway  = davac4_ssbway;
            cwda->monitor = davac4_monitor;
            cwda->band    = davac4_band;
            MUTEX_INIT(cwda->ftdi);
#endif
            break; */
                
    }
    cwda->speed=cfg->cwda_speed;
    cwda->weight=cfg->cwda_weight;
    
    
    /*dbg("init_cwdaemon\n"); */
    if (z_pipe(fds)) zinternal(VTEXT(T_CANT_CREATE_PIPE));
    cwda->pipe_read=fds[0];
    cwda->pipe_write=fds[1];
    cwda->code=g_string_sized_new(100);
    cwda->played=g_string_sized_new(100);
	cwda->thread=g_thread_try_new("cwdaemon", cwdaemon_thread_func, (gpointer)cwda, NULL);
	if (cwda->init) cwda->init(cwda);
	if (cwda->reset) cwda->reset(cwda);
/*    cwdaemon_cw_string(cwda, "cq cq de ok1zia"); */
    /*cwdaemon_cw_string(cwda, "cq"); */
    return cwda;
}

void free_cwdaemon(struct cwdaemon *cwda){
    char c;

    if (!cwda) return;
	progress(VTEXT(T_TERMINATING_CWDAEMON));		

    cwda->freeing = 1;
    cq_abort(1);

    c=0;
    z_pipe_write(cwda->pipe_write, &c, 1);
    dbg("join cwda...\n");
    g_thread_join(cwda->thread);
    dbg("done\n");
    z_pipe_close(cwda->pipe_write);
    z_pipe_close(cwda->pipe_read);
    g_string_free(cwda->code, 1);
    g_string_free(cwda->played, 1);
    if (cwda->free) cwda->free(cwda);
    g_free(cwda);
    cwda=NULL;
}


struct cwelem{
    struct timeval tv;
    int on,last,ch;
};
#define MAXCWQUEUE 20
static struct cwelem cwqueue[MAXCWQUEUE+1]; 
static int qi;

static void cwenqueue(struct cwdaemon *cwda){
    char s[256], *c, ch;
    int i;
    struct timeval tv;
    
    if (cwda->tune > 0){
        gettimeofday(&tv, NULL);
        i = 0;
        cwqueue[i].tv.tv_sec=tv.tv_sec;       
        cwqueue[i].tv.tv_usec=tv.tv_usec;       
        cwqueue[i].on=1;
        cwqueue[i].last=0;
        cwqueue[i].ch=cwda->tune==2?'-':'.';
        tv.tv_usec+=(cwda->tune==2?3:1)*(1200000/cwda->speed+cwda->weight*500);
        while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
        i++;
        cwqueue[i].tv.tv_sec=tv.tv_sec;       
        cwqueue[i].tv.tv_usec=tv.tv_usec;       
        cwqueue[i].on=0;
        cwqueue[i].last=0;
        cwqueue[i].ch=' ';
        tv.tv_usec+=1200000/cwda->speed-cwda->weight*500;
        while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
        i++;

        cwqueue[i].tv.tv_sec=tv.tv_sec;
        cwqueue[i].tv.tv_usec=tv.tv_usec;
        cwqueue[i].on=0;
        cwqueue[i].last=1;
        cwqueue[i].ch='$';
        qi=0;
        return;
    }
    
    while(1){
        ch=*cwda->code->str;
        
        if (!ch) {
            qi=-1;
            /*c = g_strdup_printf("CW;e;%s\n", cwda->played->str);
            ret = write(tpipe->threadpipe_write, c, strlen(c));
            g_free(c);*/
			zselect_msg_send(zsel, "%s;%s;%s", "CW", "e", cwda->played->str);
            g_string_truncate(cwda->played, 0);
            if (cwda->ptt && !cwda->hold_ptt) cwda->ptt(cwda, 0);
            return;
        }
        g_string_erase(cwda->code, 0, 1);
        if (ch != '\x09' && ch != '^') g_string_append_c(cwda->played, ch);
        switch(ch){
            case '\x09': strcpy(s, "\x09"); break;
            case '^': strcpy(s, "^"); break;
            case ' ': strcpy(s, " ");      break;
            case 'A': strcpy(s, ".-");     break;
            case 'B': strcpy(s, "-...");   break;          
            case 'C': strcpy(s, "-.-.");   break;          
            case 'D': strcpy(s, "-..");    break;          
            case 'E': strcpy(s, ".");      break;          
            case 'F': strcpy(s, "..-.");   break;          
            case 'G': strcpy(s, "--.");    break;          
            case 'H': strcpy(s, "....");   break;          
            case 'I': strcpy(s, "..");     break;          
            case 'J': strcpy(s, ".---");   break;          
            case 'K': strcpy(s, "-.-");    break;          
            case 'L': strcpy(s, ".-..");   break;          
            case 'M': strcpy(s, "--");     break;          
            case 'N': strcpy(s, "-.");     break;          
            case 'O': strcpy(s, "---");    break;          
            case 'P': strcpy(s, ".--.");   break;          
            case 'Q': strcpy(s, "--.-");   break;          
            case 'R': strcpy(s, ".-.");    break;          
            case 'S': strcpy(s, "...");    break;          
            case 'T': strcpy(s, "-");      break;          
            case 'U': strcpy(s, "..-");    break;          
            case 'V': strcpy(s, "...-");   break;          
            case 'W': strcpy(s, ".--");    break;          
            case 'X': strcpy(s, "-..-");   break;          
            case 'Y': strcpy(s, "-.--");   break;          
            case 'Z': strcpy(s, "--..");   break;          
            case '0': strcpy(s, "-----");  break;          
            case '1': strcpy(s, ".----");  break;          
            case '2': strcpy(s, "..---");  break;          
            case '3': strcpy(s, "...--");  break;          
            case '4': strcpy(s, "....-");  break;          
            case '5': strcpy(s, ".....");  break;          
            case '6': strcpy(s, "-....");  break;          
            case '7': strcpy(s, "--...");  break;          
            case '8': strcpy(s, "---..");  break;          
            case '9': strcpy(s, "----.");  break;          
            case '?': strcpy(s, "..--.."); break;          
            case '/': strcpy(s, "-..-.");  break;          
            case ',': strcpy(s, "--..--"); break;          
            case '=': strcpy(s, "-...-");  break;          
            case '@': strcpy(s, ".--.-."); break;          
            case '-': strcpy(s, "-....-"); break;          
            case '+': strcpy(s, ".-.-.");  break;          
            case '.': strcpy(s, ".-.-.-"); break;          
            case ';': strcpy(s, "-.-.-."); break;          
            case '!': strcpy(s, ".-...");  break;          
            default: continue;
        }
        break;        
    }
    
    dbg("enqueue %c '%s'  played='%s'\n", ch, s, cwda->played->str);
    
    gettimeofday(&tv, NULL);
    i=0;
    for (c=s; ; c++){
        if (*c=='.'){
            cwqueue[i].tv.tv_sec=tv.tv_sec;       
            cwqueue[i].tv.tv_usec=tv.tv_usec;       
            cwqueue[i].on=1;
            cwqueue[i].last=0;
            cwqueue[i].ch='.';
            tv.tv_usec+=(24000 * cwda->weight) / cwda->speed;
            while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
            i++;
            cwqueue[i].tv.tv_sec=tv.tv_sec;       
            cwqueue[i].tv.tv_usec=tv.tv_usec;       
            cwqueue[i].on=0;
            cwqueue[i].last=0;
            cwqueue[i].ch=' ';
            tv.tv_usec+=(24000 * (100 - cwda->weight)) / cwda->speed;
            while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
            i++;
        }
        if (*c=='-'){
            cwqueue[i].tv.tv_sec=tv.tv_sec;       
            cwqueue[i].tv.tv_usec=tv.tv_usec;       
            cwqueue[i].on=1;
            cwqueue[i].last=0;
            cwqueue[i].ch='-';
            tv.tv_usec+=(3 * 24000 * cwda->weight) / cwda->speed;
            while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
            i++;
            cwqueue[i].tv.tv_sec=tv.tv_sec;       
            cwqueue[i].tv.tv_usec=tv.tv_usec;       
            cwqueue[i].on=0;
            cwqueue[i].last=0;
            cwqueue[i].ch=' ';
            tv.tv_usec+=(24000 * (100 - cwda->weight)) / cwda->speed;
            while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
            i++;
        }
        if (*c=='\0'){
            cwqueue[i].tv.tv_sec=tv.tv_sec;       
            cwqueue[i].tv.tv_usec=tv.tv_usec;       
            cwqueue[i].on=2;
            cwqueue[i].last=0;
            cwqueue[i].ch='0';
            tv.tv_usec+=(2 * 24000 * cwda->weight) / cwda->speed;
            while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
            i++;
            break;
        }
        if (*c==' '){
            cwqueue[i].tv.tv_sec=tv.tv_sec;       
            cwqueue[i].tv.tv_usec=tv.tv_usec;       
            cwqueue[i].on=0;
            cwqueue[i].last=0;
            cwqueue[i].ch='_';
            tv.tv_usec+=(5 * 24000 * cwda->weight) / cwda->speed;
            while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
            i++;
        }
        if (*c=='\x09'){
            cwqueue[i].tv.tv_sec=tv.tv_sec;       
            cwqueue[i].tv.tv_usec=tv.tv_usec;       
            cwqueue[i].on=0;
            cwqueue[i].last=0;
            cwqueue[i].ch=*c;
            tv.tv_usec+=1000*cfg->cwda_leadin;
            while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
            i++;
        }
		if (*c=='^'){
            cwqueue[i].tv.tv_sec=tv.tv_sec;       
            cwqueue[i].tv.tv_usec=tv.tv_usec;       
            cwqueue[i].on=0;
            cwqueue[i].last=0;
            cwqueue[i].ch=*c;
            tv.tv_usec+=1000*cfg->cwda_tail;
            while (tv.tv_usec>=1000000) {tv.tv_usec-=1000000;tv.tv_sec++; }
            i++;
        }
    }
    cwqueue[i].tv.tv_sec=tv.tv_sec;
    cwqueue[i].tv.tv_usec=tv.tv_usec;
    cwqueue[i].on=0;
    cwqueue[i].last=1;
    cwqueue[i].ch='$';
	//dbg("ENQ qi=%d\n", qi);
    qi=0;

/*    for(i=0;;i++){
        dbg("    %2d %d %2d.%06d\n", i, cwqueue[i].on, cwqueue[i].tv.tv_sec, cwqueue[i].tv.tv_usec);
        if (cwqueue[i].last) break;
    }*/
}

gpointer cwdaemon_thread_func(gpointer data){
    fd_set rd;
    struct timeval *tv, interval, now;
    int ret, i;
    char buf[1030];
    struct cwdaemon *cwda;

    cwda=(struct cwdaemon*)data;
	zg_thread_set_name("Tucnak cwdaemon");
	zg_thread_set_priority(2);
		
    qi=-1;
    while(1){
        FD_ZERO(&rd);
        FD_SET(cwda->pipe_read, &rd);

        if (qi<0){
            tv=NULL;
            /*dbg("\nselect()\n");*/
        }else{
            /*dbg("\n");*/
            gettimeofday(&interval, NULL);
            /*dbg("now=%02d.%06d  cwqueue[%d]=%02d.%06d\n",interval.tv_sec, interval.tv_usec, qi, cwqueue[qi].tv.tv_sec, cwqueue[qi].tv.tv_usec); */
            interval.tv_usec=cwqueue[qi].tv.tv_usec-interval.tv_usec;
            interval.tv_sec=cwqueue[qi].tv.tv_sec-interval.tv_sec;
            while(interval.tv_usec<0) { interval.tv_usec+=1000000; interval.tv_sec--; }
            if (interval.tv_sec<0) {interval.tv_sec=0;interval.tv_usec=0;}
            tv=&interval;
            /*dbg("select(%2d.%06d)\n", interval.tv_sec%100, interval.tv_usec); */
        }
        
        ret=select(cwda->pipe_read+1, &rd, NULL, NULL, tv);
        if (ret<0){
            if (errno==EINTR) continue;
        }
        gettimeofday(&now, NULL);
        if (!ret){   /* timeout */
           /* dbg("timeout\n");*/
            goto check_queue;
        }
        if (FD_ISSET(cwda->pipe_read, &rd)){
            /*dbg("event on fd\n");*/
            ret=z_pipe_read(cwda->pipe_read, buf, 1024);
            if (ret<=0){
                //strcpy(s, "CW;!\n");
                /*dbg("%s\n", s);*/
                //ret = write(tpipe->threadpipe_write, s, strlen(s));
				zselect_msg_send(zsel, "%s;%s", "CW", "!");
                break;
            }
            buf[ret]='\0';
			
            //dbg("cwdaemon_thread_func: read %d '", ret);
            //for (i=0;i<ret;i++) dbg("\\%02x", buf[i]);
            //dbg("'  qi=%d\n", qi);
			
            for (i=0;i<ret;i++){
                if (buf[i]>=' ') {
                    if (cwda->code->len == 0 && qi<0){
                        if (cwda->ptt && !cwda->hold_ptt) {
							cwda->ptt(cwda, 1);
							zselect_msg_send(zsel, "CW;p"); // Rig PTT ON request
						}
                        if (!cwda->hold_ptt) g_string_append_c(cwda->code, '\x09');
                    }
                    g_string_append_c(cwda->code, z_char_uc(buf[i]));
                    continue;
                }
                switch(buf[i]){	   
                    case '\0':
                        if (cwda->cw) cwda->cw(cwda, 0);
                        if (cfg->cwda_spk) sound(0);
                        return NULL;
                    case 1: 
						//dbg("cwenqueue 3\n");
                        cwenqueue(cwda);
                        break;
                    case 3: 
                        if (cwda->cw) cwda->cw(cwda, 0);
                        if (cfg->cwda_spk) sound(0);
                        qi=-1;
                        g_string_truncate(cwda->code, 0);
                        g_string_truncate(cwda->played, 0);
                        break;
                    case 8:
                        if (cwda->code->len > 0)
                            g_string_truncate(cwda->code, cwda->code->len - 1);
                        break;
                    default:  
                        break;
                }
            }
            //if (!cwda->hold_ptt) g_string_append_c(cwda->code, '^');

            if (qi<0 && *cwda->code->str){
                //dbg("cwenqueue 1 tune=%d qi=%d\n", cwda->tune, qi);
                cwenqueue(cwda);
            }else{
				continue;
			}
        }
check_queue:
        if (qi<0) continue;
        if (cwqueue[qi].last){
            //dbg("cwenqueue 2 qi=%d\n", qi);
            cwenqueue(cwda);
            goto check_queue;
        }
        if (cwqueue[qi].tv.tv_sec>now.tv_sec) continue;
        if (cwqueue[qi].tv.tv_usec==now.tv_sec){
            if (cwqueue[qi].tv.tv_usec>now.tv_usec) continue;
        }
		
        //dbg("playing %d %c\n", qi, cwqueue[qi].ch);

        if (cwqueue[qi].on==1){
            if (cwda->cw) cwda->cw(cwda, 1);
            if (cfg->cwda_spk) sound(800);
        }else{ 
            if (cwda->cw) cwda->cw(cwda, 0);
            if (cfg->cwda_spk) sound(0);
        }
        qi++;
        continue;
        
    }
	if (cwda->cw) cwda->cw(cwda, 0);
	if (cfg->cwda_spk) sound(0);
    return NULL;
}

void cwdaemon_read_handler(struct cwdaemon *cwda, char *s, char *played){
    char *c;                    
    
    if (gses->last_cq_timer_id){ /* CQ was aborted while playing */
        zselect_timer_kill(zsel, gses->last_cq_timer_id);
        gses->last_cq_timer_id = 0;
    }

    dbg("cwdaemon_read_handler   rcvd: '%s'  played='%s'\n", s, played);
    switch(s[0]){
        case '!':  /* error */
            cq_abort(SSBDRECORDING); /*abort recording only if it is in progress */
            log_addf("cwdaemon: %s", s+1);
            break;
		case 'p':  // Rig PTT ON request
#ifdef HAVE_HAMLIB
			trigs_set_ptt(gtrigs, 1);
#endif
			break;
        case 'e':  /* cw text played */
            if (aband){
                dbg("cwdaemon_read_handler: call_played='%s' agcall=%d iscall='%d'\n", aband->call_played, aband->agcall, aband->iscall);
                if (aband->iscall){
                    aband->iscall = 0;
                    dbg("set iscall=%d\n", aband->iscall);
                    if (!aband->call_played){
                        aband->call_played = z_strdup_trim(played);
                        if (aband->call_played){
                            c = strchr(aband->call_played, ' ');
                            if (c) *c = '\0';
                        }
                        dbg("set call_played='%s'\n", aband->call_played);
                        //aband->iscall = 0;
                        //dbg("set iscall=%d\n", aband->iscall);
                        if (ctest->runmode && 
                            gses->mode == MOD_CW_CW &&
                            cfg->cwda_autgive > 0 &&
                            aband->agcall){

                            struct cq *cq = get_cq_by_number(cfg->cqs, 1);
                            if (cq){
                                cq->stripcall = 1;
                                dbg("%s: cq_run_by_number(1)\n", __FUNCTION__);
                                cq_run_by_number(1); // 5NN $MX
                                cq->stripcall = 0;
                                aband->agcall = 0;
                                dbg("cwdaemon_read_handler: set agcall=%d\n", aband->agcall);
                            }
                            break;
                        }
                    }

                }
            }

	        if (!gses || !gses->last_cq) break; /* zdravime uW 2004 :-) */

            if (gses->last_cq->cw_repeat || gses->extcq > 0) 
                cq_cw_wait(gses->last_cq);
            else{
                gses->last_cq->mode=MOD_NONE;
                cq_abort(SSBDRECORDING);
            }
            peer_tx(aband, 0);
            redraw_later();
            break;
        case 'b':  /* paddle break in */
            if (gses->last_cq) cq_abort(SSBDRECORDING);
            break;
    }
}


void cq_cw_wait(struct cq *cq){
    int remains;
//    cba_t cba;

    dbg("cq_cw_wait\n");
#ifdef HAVE_SNDFILE    
    ssbd_abort(gssbd,0);
#endif
    cwdaemon_ptt(cwda, 0, 0);
    cwdaemon_ssbway(cwda, 0); /* microphone */
    peer_tx(aband, 0);
                                                   
    remains = cq->cw_ts*100;
    if (gses->extcq > 0){
        remains = cq_remains_run();
    }

    dbg("installing wait timer (%d ms)\n", remains);
    gses->last_cq_timer_id = zselect_timer_new(zsel, remains, cq_timer_cw2, cq);
}



static void cwdaemon_send_defaults(struct cwdaemon *cwda, int speed){
    if (cwda->sspeed) cwda->sspeed(cwda, cwda->speed);
    if (cwda->sweight) cwda->sweight(cwda, cwda->weight);
    if (cfg->cwda_type == CWD_CWD) cwd_tone(cwda, cfg->cwda_spk?800:0);
}

void cwdaemon_abort(struct cwdaemon *cwda){
    if (!cwda) return;
	//dbg("cwdaemon_abort(cwda=%p)\n", cwda);
    cwda->hold_ptt = 0;
    if (cwda->reset) cwda->reset(cwda);
    cwda->tune = 0;
    z_pipe_write(cwda->pipe_write, "\x03", 1);

#if defined(HAVE_SNDFILE) && defined(HAVE_HAMLIB)
	if (gssbd->dsp->type == DSPT_HAMLIB){
		trigs_send_voice_mem(gtrigs, 0);
	}

	trigs_set_ptt(gtrigs, 0);
#endif
	//dbg("cwdaemon_abort returned\n");
}

/* called from SEGV handler */
void cwdaemon_safe_abort(struct cwdaemon *cwda){
    if (!cwda) return;
    if (cwda->reset) cwda->reset(cwda);
}



int cwdaemon_text(struct cwdaemon *cwda, char *text){
    if (!cwda) return 0;
    
    dbg("cwdaemon_cw_string('%s')\n", text);
    z_pipe_write(cwda->pipe_write, text, strlen(text));
    return 0;
}

void cwdaemon_ptt(struct cwdaemon *cwda, int ptt, int hold_ptt){
	/*dbg("ptt %d\n", ptt);*/
    if (cwda && cwda->ptt){ 
		cwda->hold_ptt = hold_ptt;
		cwda->ptt(cwda, ptt);        
		if (cwda->hold_ptt) g_string_append_c(cwda->code, '\x09');
	}

#ifdef HAVE_HAMLIB
	trigs_set_ptt(gtrigs, ptt);
#endif
}

/* 0=microphone, 1=soundcard */
void cwdaemon_ssbway(struct cwdaemon *cwda, int ssbway){
    dbg("cwdaemon_ssbway(%d %s)\n", ssbway, ssbway ? "soundcard": "microphone");
    
    if (!cwda || !cwda->ssbway) return;
    
    cwda->ssbway(cwda, ssbway);
}

int cwdaemon_speed(struct cwdaemon *cwda, int wpm){
    
    if (!cwda) return 0;
    
    cwda->speed=wpm;
    return 0;
}

void cwdaemon_qrq(struct cwdaemon *cwda, int qrq){
    
    if (!cwda) return;

    if (cwda->speed+qrq > cfg->cwda_maxwpm)
        cwda->speed = cfg->cwda_maxwpm;
    else
        cwda->speed+=qrq;
    if (cwda->sspeed) cwda->sspeed(cwda, cwda->speed);
}

void cwdaemon_qrs(struct cwdaemon *cwda, int qrs){
    
    if (!cwda) return;

    if (cwda->speed-qrs < cfg->cwda_minwpm)
        cwda->speed = cfg->cwda_minwpm;
    else
        cwda->speed-=qrs;
    if (cwda->sspeed) cwda->sspeed(cwda, cwda->speed);
}

int cwdaemon_weight(struct cwdaemon *cwda, int weight){
    
    if (!cwda) return 0;
    
    cwda->weight=weight;
    return 0;
}

void cwdaemon_tune(struct cwdaemon *cwda, int tune){
    if (!cwda) return;
    dbg("cwdaemon_tune(%d)\n", tune);
	
    cwda->tune = tune;
    if (cwda->dtune) {
		cwda->dtune(cwda, tune);
		return;
	}
		
    if (tune>0)
		z_pipe_write(cwda->pipe_write, "\x01", 1);
	else 
        cwdaemon_abort(cwda);
}

int cwdaemon_back(struct cwdaemon *cwda){

    dbg("cwdaemon_back\n");
	z_pipe_write(cwda->pipe_write, "\x08", 1);
    return 0;
}

int cwdaemon_band(struct cwdaemon *cwda, int bandsw){
    if (!cwda || !cwda->band) return 0;
    
    cwda->band(cwda, bandsw);

    return 0;
}


/*********** external cwdaemon ***********************************/
int cwd_init(struct cwdaemon *cwda){
    struct sockaddr_in sin;

    cwda->sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (cwda->sock < 0) goto err;

    if (z_sock_reuse(cwda->sock, 1)){
        dbg("Can't set SO_REUSEADDR\n");
        goto err;
    }
    
    if (z_sock_nonblock(cwda->sock, 1)){
        dbg("Can't set O_NONBLOCK\n");
        goto err;
    }
    
    memset(&sin, 0, sizeof(struct sockaddr_in));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(cfg->cwda_udp_port);
    if (!cfg->cwda_hostname) cfg->cwda_hostname = g_strdup("");
    inet_aton(cfg->cwda_hostname, &sin.sin_addr);
    
    if (connect(cwda->sock, (struct sockaddr *)&sin, sizeof(sin))){
        dbg("Can't connect\n");
        goto err;
    }
    zselect_set(zsel, cwda->sock, cwd_read_handler, NULL, NULL, cwda);

    
    cwdaemon_send_defaults(cwda, cwda->speed);
    return 0;

err:;
    if (cwda->sock>=0) closesocket(cwda->sock);
    cwda->sock = -1;
	return 1;
}

int cwd_free(struct cwdaemon *cwda){
    dbg("cwd_free(%p) sock=%d\n", cwda, cwda?cwda->sock:-1234);
    if (cwda->sock>=0) {
        cwdaemon_abort(cwda);
        zselect_set(zsel, cwda->sock, NULL, NULL, NULL, NULL);
        closesocket(cwda->sock);
    }
	return 0;
}

void cwd_read_handler(void *arg){
    char s[1024];
    struct sockaddr_in sin;
    socklen_t socklen;
    int rcvd;          
    struct cwdaemon *cwda;

    dbg("cwd_read_handler\n");
    cwda = (struct cwdaemon *)arg;
#if 0    
    if (!ctest) {
        /* we must clear kernel queue */
        recvfrom(cwda->sock, s, sizeof(s)-1, 0, 
            (struct sockaddr *)&sin, &socklen);
        return;
    }
#endif    
    memset(s, 0, sizeof(s));
    socklen = sizeof(sin);
    rcvd=recvfrom(cwda->sock, s, sizeof(s)-1, 0, 
            (struct sockaddr *)&sin, &socklen);
    /*dbg("  received '%s' = %d  last_cq_timer_id=%d \n", s, rcvd, ctest->last_cq_timer_id);*/
    if (rcvd<=0) return;

    dbg("   rcvd: '%s'\n", s);
    switch(s[0]){
        case '!':  /* error */
            log_addf("cwdaemon: %s", s+1);
            zselect_msg_send(zsel, "%s;%s", "CW", "!");
				//ret = write(tpipe->threadpipe_write, "CW;!\n", 5);
            break;
        case 'h':  /* cw text played */
            dbg("cwdaemon text played\n");
			zselect_msg_send(zsel, "%s;%s", "CW", "e");
            //ret = write(tpipe->threadpipe_write, "CW;e\n", 5);
            break;
    }
}
int cwd_reset(struct cwdaemon *cwda){
    if (!cwda || cwda->sock<0) return 0;

    send(cwda->sock, "\0330", 3, 0);  /* reset */
	return 0;
}

int cwd_text(struct cwdaemon *cwda, char *text){
    if (!cwda || cwda->sock<0) return 0;

    send(cwda->sock, text, strlen(text)+1, 0);  /* reset */
    return 0;
}

int cwd_cw(struct cwdaemon *cwda, int onoff){
	return 0;
}

int cwd_ptt(struct cwdaemon *cwda, int onoff){
    char s[16];
    
    if (!cwda || cwda->sock<0) return 0;
    sprintf(s,"\033a%d", onoff);
    send(cwda->sock, s, strlen(s)+1, 0);
	return 0;
}

int cwd_ssbway(struct cwdaemon *cwda, int onoff){
    char s[16];
    
    if (!cwda || cwda->sock<0) return 0;
    sprintf(s,"\033b%d", onoff);
    send(cwda->sock, s, strlen(s)+1, 0);
	return 0;
}

int cwd_echo(struct cwdaemon *cwda){
    char s[16];
    
    return 0;   // corruptes playing
    if (!cwda || cwda->sock<0) return 0;

    sprintf(s,"\033h%d", -1);
    send(cwda->sock, s, strlen(s)+1, 0);
    return 0;
}

int cwd_speed(struct cwdaemon *cwda, int wpm){
    char s[16];
    
    if (cwda->sock<0) return 0;

    /*dbg("speed=%d\n", wpm);*/
    sprintf(s,"\0332%d", wpm);
    send(cwda->sock, s, strlen(s)+1, 0);
    return 0;
}

int cwd_weight(struct cwdaemon *cwda, int weight){
    char s[16];
    
    if (cwda->sock<0) return 0;

    // cwdaemon:        Tmark = 1200000/wpm + 500 * weightC
    // tucnak, winkey:  Tmark = 24000 * weightT / wpm
    // ------------------------------------------------------
    // 1200000 / wpm + 500 * weightC = 24000 * weightT / wpm     // *wpm
    // 1200000 + 500 * weightC * wpm = 24000 * weightT           // :500
    // weightC * wpm = 48 * weightT - 24000                      // :wpm
    // weightC = (48 * weightT - 24000 ) / wpm
    // -----------------------------------------------------
    // 24000 * weightT = 1200000 + 500 * weightC * wpm           // :24000
    // weightT = 50 + weightC * wpm / 48

    sprintf(s,"\0337%d", (48 * weight - 24000) / cwda->speed );    
    send(cwda->sock, s, strlen(s)+1, 0);
    return 0;
}

void cwd_tone(struct cwdaemon *cwda, int tone){
    char s[16];
    
    if (cwda->sock<0) return;

    dbg("TONE\n");
    sprintf(s,"\0333%d", tone);
    send(cwda->sock, s, strlen(s)+1, 0);
}

/*int cwd_tune(struct cwdaemon *cwda, int onoff){
    char s[16];
    
    if (!cwda || cwda->sock<0) return 0;
    sprintf(s,"\033a%d", onoff);
    send(cwda->sock, s, strlen(s)+1, 0);
	return 0;
} */

int cwd_band(struct cwdaemon *cwda, int bandsw){
    char s[16];
    
    if (cwda->sock<0) return 0;

    dbg("cwd_band(%d)\n", bandsw);
    sprintf(s,"\033e%d", bandsw);
    send(cwda->sock, s, strlen(s)+1, 0);
    return 0;
}


/*********** CQ ***********************************/

        
struct cq *init_cq(void){
    struct cq *cq;

    cq = g_new0(struct cq, 1);
    g_ptr_array_add(cfg->cqs, cq);
    return cq;
}

struct cq *get_cq_by_number(GPtrArray *cqs, int nr){
    struct cq *cq;
    int i;

    for (i=0; i<cqs->len; i++){
        cq = (struct cq *)g_ptr_array_index(cqs, i);
        if (cq->nr==nr) return cq;
    }
    return NULL;   
}



void free_cq (struct cq *cq){
    if (cq->cw_str)   g_free(cq->cw_str);
    if (cq->ssb_file) g_free(cq->ssb_file);
    g_free(cq);        
}

/**************** CW *************************/
int cq_run_cw(struct cq *cq){
    gchar *raw;
    unsigned int remains, brk;


    dbg("cq_run_cw\n");
#ifdef HAVE_SNDFILE
    ssbd_abort(gssbd,1); /*aborts playing or recording */
#endif

    cq->mode=MOD_CW_CW;
    cwdaemon_ssbway(cwda, 1); /* soundcard but here to allow CQ TX on both bands */
    redraw_later();

    if (!cq->cw_str) {
        return -1;
    }
    raw = convert_cq(cq);
    dbg("\ncw_run_cq '%s' (extcq %d)\n", raw, gses->extcq);
    if (!raw) return -1;

    
    if (gses->extcq > 0){
        remains = cq_remains_run();
        brk = cq_remains_brk();

        if (gses->extcq_break_timer_id) zselect_timer_kill(zsel, gses->extcq_break_timer_id);
        gses->extcq_break_timer_id = zselect_timer_new(zsel, brk, cq_timer_brk, NULL);

        dbg("  remains=%d brk=%d\n", remains, brk);
        if (remains > 0){
            cq_cw_wait(cq);
            goto x;
        }
    }

    if (!cq->stripcall){
        if (cq->cw_speed) 
            cwdaemon_send_defaults(cwda, cq->cw_speed);
        else
            cwdaemon_send_defaults(cwda, cwda->speed);
    }else{
        if (cwda->text) cwda->text(cwda, " ");
    }

    if (cwda->text) cwda->text(cwda, raw);
    if (cwda->echo) cwda->echo(cwda);
    peer_tx(aband, 2);

    gses->last_cq = cq;
    gses->last_cq_timer_id = 0;
x:;    
    g_free(raw);
    return 0;
}

void cq_timer_cw2(void *cq){
    
    dbg("cq_timer_cw2\n");
    cq_run_cw((struct cq *)cq);
}

void cq_timer_brk(void *cq){
    gses->extcq_break_timer_id = 0;
    dbg("cq_timer_brk\n");
    cwdaemon_abort(cwda);
    cq_cw_wait(gses->last_cq);
}


/*************** SSB *************************/
int cq_run_ssb(struct cq *cq){
#if defined(HAVE_SNDFILE) || defined(Z_ANDROID)
    int ret = 0;

    dbg("cq_run_ssb\n");
    cq->mode=MOD_SSB_SSB;
    cwdaemon_ptt(cwda, 1, 0);
    cwdaemon_ssbway(cwda, 1); /* soundcard */

#ifdef HAVE_HAMLIB
	if (gssbd->dsp->type == DSPT_HAMLIB){
		trigs_send_voice_mem(gtrigs, cq->nr + 1);
		if (cq->ssb_repeat){
			gses->last_cq = cq;
			gses->last_cq_timer_id = zselect_timer_new(zsel, cq->ssb_ts * 100, cq_timer_ssb2, cq);
		}
		redraw_later();
		return ret;
	}
#endif

#ifdef Z_ANDROID
    char *filename = convert_esc(cq->ssb_file, NULL, CE_NONE, time(NULL));
    if (!filename) return -1;
    zandroid_play_wav(filename);
#else
    zg_free0(gssbd->pfilename);
    gssbd->pfilename=convert_esc(cq->ssb_file, NULL, CE_NONE, time(NULL));
    if (!gssbd->pfilename) return -1;
    ret=ssbd_play_file(gssbd, gssbd->pfilename);
	if (ret < 0) {
		cq_abort(0);
		return ret;
	}
#endif

    gses->last_cq = cq;
    gses->last_cq_timer_id = 0;
    peer_tx(aband, 2);
    redraw_later();
    return ret;
#else
    log_adds("Tucnak is compiled without libsndfile, no SSB CQ available");
#endif
	return -1;
}

#if defined(HAVE_SNDFILE) || defined(Z_ANDROID)
void cq_ssb_wait(struct cq *cq){
    /*dbg("cq_ssb_wait\n");*/
#ifdef HAVE_SNDFILE
    ssbd_abort(gssbd,0);
#endif
    cwdaemon_ptt(cwda, 0, 0);
    cwdaemon_ssbway(cwda, 0); /* microphone */
    peer_tx(aband, 0);
    gses->last_cq_timer_id = zselect_timer_new(zsel, cq->ssb_ts*100, cq_timer_ssb2, cq);
}

void cq_timer_ssb2(void *cq){
    
    /*dbg("cq_timer_ssb2\n");*/
    cq_run_ssb((struct cq *)cq);
}
#endif


/****************** common **********************/
int cq_run_by_number(int no){
    int ret = 0;
    struct cq *cq;
    
    dbg("cq_run_by_number(%d), mode=%s\n", no, get_mode()==MOD_CW_CW?"CW":"SSB");
    
    if (no == -1 && get_mode() == MOD_CW_CW){
        cq = gses->ac_cq;
    }else{
        if (no<0 || no>=cfg->cqs->len) return -1;
        cq = (struct cq *)g_ptr_array_index(cfg->cqs, no);
    }

    if (!cq->stripcall){
        cq_abort(1);
    }
    
    /*if (gses->last_cq_timer_id0){
        zselect_timer_kill(zsel, gses->last_cq_timer_id);
        gses->last_cq_timer_id = 0;
    } */
    
    
    if (get_mode() == MOD_CW_CW){
        /*dbg(" speed=%d repeat=%d ts=%d '%s'\n", cq->cw_speed, cq->cw_repeat, cq->cw_ts, cq->cw_allowifundef, cq->cw_str);*/
        ret=cq_run_cw(cq);
    }else{
        /*dbg(" repeat=%d ts=%d '%s'\n", cq->ssb_repeat, cq->ssb_ts, cq->ssb_file);*/
        ret=cq_run_ssb(cq);
	}
    if (ret!=0) cq_abort(SSBDRECORDING);

    return ret;
}


int cq_abort(int abort_rec){
/*    dbg(" cq_abort(%d)\n", abort_rec);*/
    
    if (gses) gses->last_cq=NULL;
    if (gses && gses->last_cq_timer_id){
        zselect_timer_kill(zsel, gses->last_cq_timer_id);
        gses->last_cq_timer_id = 0;
    }
    if (gses && gses->extcq_break_timer_id){
        zselect_timer_kill(zsel, gses->extcq_break_timer_id);
        gses->extcq_break_timer_id = 0;
    }
    
    cwdaemon_abort(cwda);
    redraw_later();
    if (!abort_rec && ssbd_recording(gssbd)) return 0;


	ssbd_abort(gssbd,abort_rec);
    return 0;
}

static gchar *conv_nr(gchar *str){
    char *c;
    static char out[20];

    safe_strncpy0(out, str, 20);
    for (c=out; *c=='0'; c++) *c='t';
    return out;
}

static gchar *conv_rst(gchar *str){
    char *c;
    static char out[20];

    safe_strncpy0(out, str, 20);
    for (c=out; *c!='\0';c++){
        if (*c=='9') *c='n';
    }
    return out;
}



/* convert $XX to string */
/* /home/ja/ssbd/tmp/rec/$N_%Y%m%d_%H%M%S.wav */

#define SFT_LEN 1024
gchar *convert_esc(gchar *format, int *undef, int flags, time_t now){ 
    GString *gs;
    char *c, *ret, *c2, *c3;
    int dummyint;
    struct tm utc;
    char sft_buf[SFT_LEN+1];
    
    if (!undef) undef=&dummyint;
    *undef=0;
   
    if (now == 0) time(&now);
    gmtime_r(&now, &utc);
    if (!format) format="";
    strftime(sft_buf, SFT_LEN, format, &utc);    
    
    gs = g_string_new("");
    if (flags&CE_ONLY_STRFTIME){
        g_string_append(gs, sft_buf); 
        goto x;
    }
    
    for (c=sft_buf; *c!='\0'; c++){
        if (*c=='~' && c==sft_buf){
            g_string_append(gs, home_dir);
            continue;
        }
        if (*c!='$') {
            g_string_append_c(gs, *c);
            continue;
        }
        c++;
        switch (z_char_lc(*c)){
            case '\0':
                goto brk2;
                break;
            case '$':
                g_string_append_c(gs, '$');
                break;
            case 'b':
               // dbg("callsign='%s'  call_played='%s'\n", TMPQ.callsign, aband->call_played);
                if (aband) dbg("convert_esc1: call_played='%s'\n", aband->call_played);
                if (ctest && TMPQ.callsign && 
                        aband->call_played != NULL && 
                        strcmp(TMPQ.callsign, aband->call_played) != 0){
                    g_string_append(gs, TMPQ.callsign);
                    //zg_free0(aband->call_played);
                    //aband->call_played = g_strdup(TMPQ.callsign);
                    //dbg("convert_esc2: set call_played='%s'\n", aband->call_played);
                    //aband->iscall = 1;
                    //dbg("convert_esc2: set iscall=%d\n", aband->iscall);
                }
                break;
            case 'c':
                if (ctest && TMPQ.callsign) {
                    g_string_append(gs, TMPQ.callsign);
                    if (!strcasecmp(TMPQ.callsign, "O""K""1""Z""I""A")) g_string_append(gs, " ""a""h""o""j");
                    //zg_free0(aband->call_played);
                    //aband->iscall = 1;
                    //dbg("convert_esc3: set iscall=%d\n", aband->iscall);
                    //aband->call_played = g_strdup(TMPQ.callsign);
                    //dbg("convert_esc3: set call_played='%s'\n", aband->call_played);
                }else{
                    *undef=1;
                }
                break;    
            case 'd':
                if (ctest) g_string_append (gs, ctest->cdate);
				else g_string_append(gs, "00000000");
                break;
            case 'e':
#ifdef HAVE_SNDFILE
                if (gssbd->code) g_string_append(gs, "code");
#endif
                break;
            case 'i':
                c2 = g_strdup(INPUTLN(aband)->cdata);
                for (c3=c2;*c3!='\0';c3++) if (*c3=='.') *c3='?';
                g_string_append(gs, c2);
                g_free(c2);
                break;
            case 'j':
                if (ctest && TMPQ.callsign) {
                    char *name = find_name_by_call(namedb, TMPQ.callsign);
                    if (name) g_string_append(gs, name);
                }else{
                    *undef = 1;
                }
                break;    
                break;
            case 'n':
                if (ctest && TMPQ.qsonrr) g_string_append(gs, conv_nr(TMPQ.qsonrr));
                else *undef=1;
                break;    
            case 'o':
                if (aband && aband->operator_) g_string_append(gs, aband->operator_);
				else if (cfg->pcall) g_string_append(gs, cfg->pcall);
                else *undef=1;
                break;   
            case 'r':
                if (ctest && TMPQ.rstr)     g_string_append(gs, conv_rst(TMPQ.rstr));
                else *undef=1;
                break; 
            case 's': 
#ifdef HAVE_SNDFILE
                g_string_append_printf(gs, "%d", gssbd->serno);
#endif
                break;
            case 't':
                if (ctest) g_string_append(gs, ctest->directory);
				else g_string_append_printf(gs, "%s/00000000", tucnak_dir);
                break;
            case 'v':
#ifdef HAVE_SNDFILE
                if (gssbd->callsign) {
                    char *d;
                    for (d=gssbd->callsign; *d!='\0'; d++){
                        if (*d=='/')
                            g_string_append_c(gs, '_');
                        else
                            g_string_append_c(gs, *d);
                    }
                }
                else *undef=1;
#endif
                break;
            case 'w':
                if (ctest && TMPQ.locator)  g_string_append(gs, TMPQ.locator);
                else *undef=1;
                break;
            case 'x':
                if (ctest && TMPQ.exc)      g_string_append(gs, TMPQ.exc);
                else *undef=1;
                break;    
            case 'm':
                c++;
                switch(z_char_lc(*c)){
                    case '\0':
                        goto brk2;
                        break;
                    case 'c':
                        if (ctest && ctest->pcall) g_string_append(gs, ctest->pcall);
						else if (cfg->pcall) g_string_append(gs, cfg->pcall);
                        else *undef=1;
                        break;    
                    case 'n':
                        if (ctest && TMPQ.qsonrs){
                            if (ctest->qsoglob){
                                gchar *c;
                                c = g_strdup_printf("%03d", ctest->allqsos->len+1);
                                g_string_append(gs, conv_nr(c));
                                g_free(c);
                            }else{
                                g_string_append(gs, conv_nr(TMPQ.qsonrs));
                            }
                        }else{
                            *undef=1;
                        }
                        break;    
                    case 'r':
                        if (ctest && TMPQ.rsts)    g_string_append(gs, conv_rst(TMPQ.rsts));
                        else *undef=1;
                        break;    
                    case 'w':
                        if (ctest && ctest->pwwlo) g_string_append(gs, ctest->pwwlo);
						else if (cfg->pwwlo) g_string_append(gs, cfg->pwwlo);
                        else *undef=1;
                        break;
                    case 'x':
                        if (ctest && ctest->pexch) g_string_append(gs, ctest->pexch);
						else if (cfg->pexch) g_string_append(gs, cfg->pexch);
                        else *undef=1;
                        break;    
                }
                break;
        }        
            
brk2:;        
    }
x:;    
    ret = g_strdup(gs->str);
    ret = z_optimize_path(ret);
    g_string_free(gs, TRUE);
    
    return ret;
}


gchar *convert_cq(struct cq *cq){
    gchar *ret;
    int undef;
    char *c, *d;

    c = cq->cw_str;
//    dbg("stripcall=%d c='%s'\n", cq->stripcall, c);
    if (cq->stripcall){
        d = strstr(c, "$C");
        if (d != NULL) c += strlen("$C"); 
        while (*c==' ') c++;
    }
    ret=convert_esc(c, &undef, CE_NONE, time(NULL));
    
    if (!cq->cw_allowifundef && undef){
        g_free(ret);
        return NULL;
    }
    return(ret);
}
    
#define FREE_Cx if (c1) {mem_free(c1); c1=NULL;}\
                if (c2) {mem_free(c2); c2=NULL;}\
                if (c3) {mem_free(c3); c3=NULL;}\
                if (c4) {mem_free(c4); c4=NULL;}\
                if (c5) {mem_free(c5); c5=NULL;}



void menu_runmode(void *arg){
    runmode(1);
}

void menu_spmode(void *arg){
    runmode(0);
}

void runmode(int run){
    if (!ctest) return;
    ctest->runmode = run;
}

int cq_remains_run(void){
    int remains; 
    struct timeval tv;

    gettimeofday(&tv, NULL);
    switch (gses->extcq){
        case EC_ODD:
            remains = 120000 - ((tv.tv_sec % 120) * 1000 + tv.tv_usec / 1000);
            if (remains < 60000) remains = 0;
            else remains -= 60000;
            break;
        case EC_EVEN:
            remains = 120000 - ((tv.tv_sec % 120) * 1000 + tv.tv_usec / 1000);
            if (remains >= 60000) remains = 0;
            break;
        case EC_1ST:
            remains = 60000 - ((tv.tv_sec % 60) * 1000 + tv.tv_usec / 1000);
            if (remains >= 30000) remains = 0;
            break;
        case EC_2ND:
            remains = 60000 - ((tv.tv_sec % 60) * 1000 + tv.tv_usec / 1000);
            if (remains < 30000) remains = 0;
            else remains -= 30000;
            break;
        default: 
            remains = 0;
            break;
    }
    return remains;
}
int cq_remains_brk(void){
    int remains; 
    struct timeval tv;

    gettimeofday(&tv, NULL);
    switch (gses->extcq){
        case EC_ODD:
            remains = 120000 - ((tv.tv_sec % 120) * 1000 + tv.tv_usec / 1000);
            break;
        case EC_EVEN:
            remains = 120000 - ((tv.tv_sec % 120) * 1000 + tv.tv_usec / 1000);
            if (remains >= 60000) remains -= 60000;
            else remains += 60000;
            break;
        case EC_1ST:
            remains = 60000 - ((tv.tv_sec % 60) * 1000 + tv.tv_usec / 1000);
            if (remains >= 30000) remains -= 30000; 
            else remains += 30000; 
            break;
        case EC_2ND:
            remains = 60000 - ((tv.tv_sec % 60) * 1000 + tv.tv_usec / 1000);
            break;
        default: 
            remains = 0;
            break;
    }
    return remains;   
}


static int ac_cq_i1 = 0;
static int ac_cq_i2 = 0;
static char *ac_txts[] = {
    "$C $MC",
    "$MR",
    "$MN",
    "$MR $MN",
    "$MW",
    "RRRR 73",
    NULL
};

void ac_cq3(void *arg){
    enum extcq conv[4] = {EC_1ST, EC_2ND, EC_ODD, EC_EVEN};

    //log_addf("ac_cq3 %d", cba.int_);
    ac_cq_i2 = GPOINTER_TO_INT(arg);
    
    if (!gses) return;
    gses->extcq = conv[GPOINTER_TO_INT(arg)];
    gses->ac_cq->cw_str = ac_txts[ac_cq_i1];
    cq_run_by_number(-1);
}

void ac_cq2(void *arg){
    struct menu_item *mi = NULL;

    //log_addf("ac_cq2 %d", cba.int_);
    ac_cq_i1 = GPOINTER_TO_INT(arg);
    
    if (!(mi = new_menu(1))) return;

    add_to_menu(&mi,CTEXT(T_1ST),  CTEXT(T_HK_1ST),  CTEXT(T_HK_1ST),  MENU_FUNC ac_cq3, GINT_TO_POINTER(0), 0);    
    add_to_menu(&mi,CTEXT(T_2ND),  CTEXT(T_HK_2ND),  CTEXT(T_HK_2ND),  MENU_FUNC ac_cq3, GINT_TO_POINTER(1), 0);    
    add_to_menu(&mi,CTEXT(T_ODD),  CTEXT(T_HK_ODD),  CTEXT(T_HK_ODD),  MENU_FUNC ac_cq3, GINT_TO_POINTER(2), 0);    
    add_to_menu(&mi,CTEXT(T_EVEN), CTEXT(T_HK_EVEN), CTEXT(T_HK_EVEN), MENU_FUNC ac_cq3, GINT_TO_POINTER(3), 0);    

    do_menu_selected(mi, NULL, ac_cq_i2);

}

void ac_cq(void){
    struct menu_item *mi = NULL;
    int i, max=0;
    
    if (!(mi = new_menu(3))) return;
    for (i=0; ac_txts[i]!=NULL; i++){
        add_to_menu(&mi,g_strdup(ac_txts[i]),"", "", MENU_FUNC ac_cq2, GINT_TO_POINTER(i), 0);    
        if (strlen(ac_txts[i])>max) max = strlen(ac_txts[i]);
    }

    set_window_ptr(gses->win, (term->x-max)/2,(term->y-2-i)/2);
    do_menu_selected(mi, NULL, ac_cq_i1);
    
}


#ifdef HAVE_HAMLIB

int cwd_hamlib_text(struct cwdaemon *cwda, char *text){
	trigs_send_morse(gtrigs, text);
	return 0;
}

int cwd_hamlib_reset(struct cwdaemon *cwda){
	trigs_stop_morse(gtrigs);
	return 0;
}

#endif
