/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2022  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 "bfu.h"
#include "control.h"
#include "cwdaemon.h"
#include "dsp.h"
#include "fft.h"
#include "fifo.h"
#include "icons.h"
#include "language2.h"
#include "main.h"
#include "net.h"
#include "player.h"
#include "qsodb.h"
#include "rc.h"
#include "scope.h"
#include "sdr.h"
#include "tsdl.h"
#include "sdr.h"
#include "session.h"
#include "ssbd.h"
#include "subwin.h"
#include "tregex.h"



struct ssbd *gssbd;
MUTEX_DEFINE(ssbd);


#define MAXCNTLEVEL 1

struct ssbd *init_ssbd(){
    struct ssbd *ssbd;

	progress(VTEXT(T_INIT_SSB_CQ));		

    ssbd = g_new0(struct ssbd, 1);
#ifdef HAVE_SNDFILE
    MUTEX_INIT(ssbd);    
#ifdef VOIP
	ssbd->voip = init_voip();
    voip_receive(ssbd);
	//voip_test(ssbd->voip);
#endif

    
#if defined(Z_HAVE_SDL) && defined(Z_HAVE_LIBPNG)    
    if (sdl){
        ssbd->norecicon = zpng_create(icon_norec, sizeof(icon_norec));
        if (!ssbd->norecicon) zinternal("Can't create norec icon, corrupted executable?");
    
        ssbd->recicon = zpng_create(icon_record, sizeof(icon_record));
        if (!ssbd->recicon) zinternal("Can't create record icon, corrupted executable?");
    
        ssbd->playicon = zpng_create(icon_play, sizeof(icon_play));
        if (!ssbd->playicon) zinternal("Can't create play icon, corrupted executable?");
    }
#endif
    ssbd->loglevel = -1;
    ssbd->oldloglevel = -2;
    MUTEX_INIT(ssbd->loglevel);
    MUTEX_INIT(ssbd->seek);
	ssbd->dsp = init_dsp((enum dsp_type)cfg->ssbd_type, DCG_SSBD);
#endif

    return ssbd;
}

void free_ssbd(struct ssbd *ssbd, int terminate){
	progress(VTEXT(T_FREE_SSB_CQ));		
    cq_abort(1);
    ssbd_abort(ssbd,1); 
#ifdef HAVE_SNDFILE
    ssbd_thread_join(ssbd); /* killed before but... */
    g_free(ssbd->buffer);
    free_dsp(ssbd->dsp);
#ifdef VOIP
	free_voip(ssbd->voip);
#endif
    zg_free0(ssbd->callsign);
    zg_free0(ssbd->pfilename);
    zg_free0(ssbd->rfilename);
#ifdef Z_HAVE_SDL    
    if (sdl){
        if (ssbd->playicon) SDL_FreeSurface(ssbd->playicon);
        if (ssbd->recicon) SDL_FreeSurface(ssbd->recicon);
        if (ssbd->norecicon) SDL_FreeSurface(ssbd->norecicon);
	    g_free(ssbd->scope_buf);
    }
#endif
    if (ssbd->pl_pcmfile){
        unlink(ssbd->pl_pcmfile);
        g_free(ssbd->pl_pcmfile);
    }
    MUTEX_FREE(ssbd);
    MUTEX_FREE(ssbd->loglevel);
    MUTEX_FREE(ssbd->seek);
#endif // HAVE_SNDFILE
#ifdef HAVE_PORTAUDIO
    if (terminate) Pa_Terminate();
#endif
    g_free(ssbd);
}


void ssbd_abort(struct ssbd *ssbd, int abort_rec){
    gchar *tmpfile;
    int recording;
    
    if (!ssbd) return;
    dbg("ssbd_abort recording=%d abort_rec=%d\n", ssbd->recording, abort_rec);
	recording = ssbd->recording;

#ifdef HAVE_SNDFILE    
    ssbd->channels = 0;
    ssbd->playedf = 0;
    if (gses) gses->icon=NULL;
    ssbd_thread_join(ssbd);
        
    if (ssbd->sndfile && recording) { 
        sf_close (ssbd->sndfile);
        ssbd->sndfile = NULL;
    
        if (!cfg->ssbd_template || !ssbd->rfilename) goto x;
        tmpfile=convert_esc(cfg->ssbd_template, NULL, CE_NONE, time(NULL));
        /*dbg("cfg->template='%s' cfg->callsign='%s' \n", cfg->ssbd_template, ssbd->callsign);
        dbg("tmpfile='%s'  cfg->filename='%s'\n", tmpfile, ssbd->rfilename);*/
        if (strcmp(tmpfile, ssbd->rfilename)!=0){
            dbg("renaming '%s' to '%s'\n", ssbd->rfilename, tmpfile);         
            rename(ssbd->rfilename, tmpfile);
            g_free(ssbd->rfilename);
            ssbd->rfilename=g_strdup(tmpfile);
            
        }
        g_free(tmpfile);
    }
x:;    
    ssbd->dsp->close2(ssbd->dsp);
 
    ssbd->recording=0;
    if (!abort_rec) {
#ifdef USE_SDR
		sdr_start(gsdr);
#endif
		ssbd_rec_file(ssbd);
	}else{
#ifdef USE_SDR
		sdr_stop(gsdr);
#endif
	}
#endif
#ifdef Z_ANDROID
    zandroid_ssbd_abort();
#endif
}

int ssbd_recording(struct ssbd *ssbd){
    //dbg("ssbd_recording=%d\n", ssbd->recording);
    return ssbd->recording;
}


#ifdef HAVE_SNDFILE

void ssbd_thread_create(struct ssbd *ssbd, GThreadFunc thread_func){
#if 0
    dbg("ssbd_thread_create");
    if (thread_func==ssbd_play_thread_func) dbg(" play\n"); 
    else if (thread_func==ssbd_rec_thread_func) dbg(" rec\n");
#ifdef VOIP
    else if (thread_func==voip_thread_func) dbg(" voip\n");
#endif
    else dbg(" ???");                          
#endif
    ssbd->proc_break=0;
    if (ssbd->thread) return;
	ssbd->thread = g_thread_try_new("ssbd", thread_func, (gpointer)ssbd, NULL);
    dbg("ssbd created thread %p\n", ssbd->thread);
}

void ssbd_thread_join(struct ssbd *ssbd){
    dbg("ssbd thread join %p\n", ssbd->thread);
    if (!ssbd->thread) return;
    ssbd->proc_break=1;
    dbg("join ssbd...\n");
    g_thread_join(ssbd->thread);
    dbg("done\n");
	ssbd->thread = NULL;
}


/* ------- playing -------------------------------------------------- */

int ssbd_play_file(struct ssbd *ssbd, gchar *pfilename){
    SF_INFO sfinfo;
    int subformat;
	char *wfilename;
    
    dbg("ssbd_play_file('%s')\n", pfilename);
    ssbd_abort(ssbd,1); /*aborts playing or recording */

    memset (&sfinfo, 0, sizeof (sfinfo));

    if (!pfilename || strlen(pfilename)==0) {
        log_adds(VTEXT(T_NO_FILE));
        return -1;
    }


    dbg ("Playing %s\n", pfilename);
	wfilename = g_strdup(pfilename);
	z_wokna(wfilename);										   
    if (! (ssbd->sndfile = sf_open (wfilename, SFM_READ, &sfinfo))){   
		char *sferr = z_1250_to_8859_2(g_strdup(sf_strerror (NULL)));
        log_addf(TRANSLATE("Can't play %s - %s"), wfilename, sferr);
		g_free(sferr);
		g_free(wfilename);
        return -1;
    };
	g_free(wfilename);

    if (sfinfo.channels < 1 || sfinfo.channels > 2){   
        log_addf (VTEXT(T_UNSUP_CHANNELS), sfinfo.channels);
        sf_close (ssbd->sndfile);
        ssbd->sndfile = NULL;
        return -1;
    };
    
    subformat = sfinfo.format & SF_FORMAT_SUBMASK;
    
    if (subformat == SF_FORMAT_FLOAT || subformat == SF_FORMAT_DOUBLE){   
        log_addf(VTEXT(T_FLOAT_UNSUPP));
        sf_close (ssbd->sndfile);
        ssbd->sndfile = NULL;
        return -1;
    }

    dbg("sfinfo: format=0x%x channels=%d speed=%d   frames=%d\n", sfinfo.format, sfinfo.channels, sfinfo.samplerate, sfinfo.frames);
    ssbd->dsp->set_format(ssbd->dsp, &sfinfo, 0);
    ssbd->dsp->set_plevel(ssbd->dsp);
    if (ssbd->dsp->open(ssbd->dsp, 0)<0) {
        log_addf(VTEXT(T_CANT_OPEN_DSP_PLAY), ssbd->dsp->name);
        sf_close (ssbd->sndfile);
        ssbd->sndfile = NULL;
        return -1;
    };
    //dbg("play opened: channels=%d speed=%d\n", gdsp->channels, gdsp->speed);

    g_free(ssbd->buffer);
    ssbd->buffer = g_new0(short, ssbd->dsp->samples);

#ifdef USE_FFT
    fft_start(gfft, ssbd->dsp->frames, ssbd->dsp->speed);
#endif
	sw_scope_clear_scopes();
    ssbd->channels = sfinfo.channels;
    ssbd->seek = 0;
#ifdef Z_HAVE_SDL
    if (zsdl && gses){
        ssbd->scope_w = gses->ontop->w * zsdl->font_w;
        ssbd->scope_samples = ssbd->channels * ssbd->scope_w;
        ssbd->scope_buf = g_new0(short, ssbd->scope_samples);
        ssbd->scope_show = 0;
		//dbg("gssbd->scope_show = 0   %s:%d\n", __FUNCTION__, __LINE__);
        ssbd->scope_i = 0;
    }
#endif
    ssbd_thread_create(ssbd, ssbd_play_thread_func);
	
    if (gses) gses->icon=ssbd->playicon;
    return 0;
}


gpointer ssbd_play_thread_func(gpointer data){
    int frames, samples, writtenf, writtens;
    int err;
    short *wrptr;
    int xxx = -1;
	sf_count_t cnt;
	int tocopy; // in samples
    
	zg_thread_set_name("Tucnak ssbd play");

    /*dbg("play thread started\n");
    for (i=0;i<6;i++){
        if (ssbd->thread_break)  goto x; 
        dbg("kdĂĄk\n");
        sleep(1);
    }
    */
    while(1){
		int seek;

        xxx++;
        if (gssbd->proc_break) {
            gssbd->dsp->reset(gssbd->dsp);
            goto x; 
        }
        MUTEX_LOCK(gssbd->seek);
        seek = gssbd->seek * gssbd->channels * gssbd->dsp->speed / 1000;
        gssbd->seek = 0;
        MUTEX_UNLOCK(gssbd->seek);
        
        if (seek != 0){
            //dbg("seek(%d)\n", seek);
            cnt = sf_seek(gssbd->sndfile, 0, SEEK_CUR);
            cnt += seek;
            if (cnt < 0) cnt = 0;
            cnt = sf_seek(gssbd->sndfile, cnt, SEEK_SET);
            if (cnt >= 0) gssbd->playedf = cnt;
            gssbd->cntlevel = 0;
        }
        frames = sf_readf_short (gssbd->sndfile, gssbd->buffer, gssbd->dsp->frames);
        if (frames <= 0) break;            

		samples = (frames * gssbd->dsp->samples) / gssbd->dsp->frames;
        gssbd->playedf += frames;

        /* todo playmax & etc */

        if (gssbd->proc_break)  {
            gssbd->dsp->reset(gssbd->dsp);
            goto x; 
        }


        /*if (gssbd->cntlevel--==0){
            gssbd->cntlevel=MAXCNTLEVEL;

			zselect_msg_send(zsel, "%s;%s", "SSBP", "L");
		} */
#ifdef Z_HAVE_SDL				   
		if (gssbd->scope_show == 0){
			tocopy = Z_MIN(samples, gssbd->scope_samples - gssbd->scope_i);
			if (tocopy > 0) {
				memcpy(gssbd->scope_buf + gssbd->scope_i, gssbd->buffer, tocopy * sizeof(short));
				gssbd->scope_i += tocopy;

				if (gssbd->scope_i >= gssbd->scope_samples){
					gssbd->scope_show = 1;
					gssbd->scope_i = 0;
					zselect_msg_send(zsel, "%s;%s", "SSBP", "L");
				}
			}
		}


#endif

        
#ifdef USE_FFT
        if (gfft){
			int i, j;
			short *p = gssbd->buffer;
            for (i=0; i<frames; i++){
                double v = 0;
                for (j=0; j<gssbd->dsp->channels; j++){
                    v += (double)*p++;
                }
                v /= gssbd->dsp->channels;
                v /= 32768;
				if (i >= gfft->lenrin) zinternal("gfft->dynrin[%d] access (rinlen=%d)", i, gfft->lenrin);
                gfft->dynrin[i] = v;
            }

            fft_do(gfft);
			zselect_msg_send(zsel, "SSBP;D");
        }
#endif

        wrptr = gssbd->buffer;

        while(1){
            writtenf = gssbd->dsp->write (gssbd->dsp, wrptr, frames);
            if (writtenf < 0){
                GString *gs = g_string_new("");
                err=errno;
                MUTEX_LOCK(ssbd);
                g_string_append_printf(gs, "!%s %s ", VTEXT(T_CANT_WRITE_TO), gssbd->dsp->name);
                z_strerror(gs, err);
                g_string_append_c(gs, '\n');
				MUTEX_UNLOCK(ssbd);
                zselect_msg_send(zsel, "%s;%s", "SSBP", gs->str);
                g_string_free(gs, TRUE);
                goto x;
            }
            if (writtenf < frames){
                dbg("write(%s) interrupted after %d frames\n", gssbd->dsp->name, writtenf);
				writtens = (writtenf * gssbd->dsp->samples) / gssbd->dsp->frames;
                wrptr += writtens;
                frames -= writtenf;
            }
            break;
        }
    }
    gssbd->dsp->sync(gssbd->dsp);
    //sprintf(s, "SSBP;e\n");
    //ret = write(tpipe->threadpipe_write, s, strlen(s));
	zselect_msg_send(zsel, "%s;%s", "SSBP", "e");
x:;    
	memset(gssbd->buffer, 0, gssbd->dsp->bytes);
    dbg("ssbd_play_thread_func exited\n");
    return NULL;
}
#endif

#if defined(HAVE_SNDFILE) || defined(Z_ANDROID)
void ssbd_play_read_handler(struct ssbd *ssbd, gchar *str){

    /*dbg("ssbd_read_handler\n");*/
    
    /* this breaks cw wait timer when L is received
	if (gses && 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;
    } */

    switch(str[0]){
        case '!':  /* error */
            cq_abort(ssbd->recording); /*abort recording only if it is in progress */
            
            log_addf("ssbd: %s", str+1);
            if (gses) gses->icon=NULL;
            redraw_later();
            cq_abort(ssbd->recording);
            peer_tx(aband, 0);
            break;
        case 'e':  /* sample played */
    	    if (!gses) break;
#ifdef HAVE_SNDFILE
			if (ssbd->sndfile != NULL){
				sf_close(ssbd->sndfile);
				ssbd->sndfile = NULL;
			}
#endif
            gses->icon=NULL;
            if (!gses->last_cq) { /* last sample played */
                cq_abort(ssbd->recording);
                break; 
            }
            if (gses->last_cq->ssb_repeat)
                cq_ssb_wait(gses->last_cq);
            else{
                gses->last_cq->mode=MOD_NONE;
                cq_abort(ssbd->recording);
            }
            peer_tx(aband, 0);
            redraw_later();
            break;
        case 'L':  /* play level */
            //ssbd->loglevel=atoi(str+1);
#ifdef Z_HAVE_SDL
			if (gses && gses->ontop->type == SWT_SCOPE){
				gses->ontop->gdirty = 1;
				//dbg("set gdirty\n");
			}
#endif
            redraw_later();
            /*dbg("level=%d\n", ssbd->loglevel);*/
            break;
		case 'D':
#ifdef Z_HAVE_SDL
			if (gses && gses->ontop->type == SWT_SCOPE){
				gses->ontop->gdirty = 1;
			}
#endif
            redraw_later();
    }
}
#endif

/* ------- recording -------------------------------------------------- */

#ifdef HAVE_SNDFILE
int ssbd_rec_file(struct ssbd *ssbd){
    int subformat;
    SF_INFO sfinfo;	char errbuf[1024];
    double df;
	char *wfilename;
    
    /*dbg("ssbd_rec_file  cfg->ssbd_record=%d  ssbd->recording=%d  ctest->recording=%d\n", cfg->ssbd_record, ssbd->recording, ctest?ctest->recording:-123); */

    if (!cfg->ssbd_record) {
        strcpy(errbuf, VTEXT(T_REC_DISABLED));
        goto norec;
    }
    if (ssbd->recording) return 0;
    
    if (gses) gses->icon=ssbd->norecicon;
    if (ctest && ctest->oldcontest) {
        strcpy(errbuf, VTEXT(T_CONTEST_TOO_OLD));
        goto norec;
    }
    
	if (ssbd->thread != NULL) zinternal("ssbd_rec_file: ssbd->thread != NULL");
    //ssbd_abort(ssbd,1); /*aborts playing or recording */
    ssbd_watchdog(ssbd, 0);

    zg_free0(ssbd->rfilename);
    ssbd->serno++;
    ssbd->rfilename=convert_esc(cfg->ssbd_template, NULL, CE_NONE, time(NULL));
    
    /*dbg ("Playing %s\n", ssbd->rfilename);*/
    
    memset (&sfinfo, 0, sizeof (sfinfo));

    sfinfo.format     = cfg->ssbd_format;
    sfinfo.channels   = cfg->ssbd_channels;
    sfinfo.samplerate = cfg->ssbd_samplerate;
    
    if (!sfinfo.format)     sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
    if (!sfinfo.channels)   sfinfo.channels = 1;
    if (!sfinfo.samplerate) sfinfo.samplerate = 8000;

	if (ssbd->dsp->type == DSPT_SNDPIPE) {
		sfinfo.format     = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
		sfinfo.channels   = 2;
		sfinfo.samplerate = cfg->sdr_af_speed;
	}

	ssbd->code = 0;
	wfilename = g_strdup(ssbd->rfilename);
	z_wokna(wfilename);
    z_fmkdir_p(wfilename,0777);


    df=z_df(wfilename);
    if (df>=0.0 && cfg->ssbd_diskfree>0){
        if (df < (cfg->ssbd_diskfree*1058576.0)){
            //dbg("Not enough free disk space for %s: %d<%d (MiB)\n", wfilename, (int)(df/1048576), cfg->ssbd_diskfree);
            log_addf (VTEXT(T_NO_FREE_DISK_SPACE), wfilename, (int)(df/1048576), cfg->ssbd_diskfree);
			g_free(wfilename);
            return -1;
        }
    }

    /*dbg("check:%d\n", sf_format_check(&sfinfo));*/
    if (! (ssbd->sndfile = sf_open(wfilename, SFM_WRITE, &sfinfo))){   
		char *sferr = z_1250_to_8859_2(g_strdup(sf_strerror (NULL)));
        log_addf (VTEXT(T_ERROR_WRITING_FILE), wfilename, sferr);
		g_free(sferr);
		g_free(wfilename);
        return -1;
    };
	g_free(wfilename);	    
    subformat = sfinfo.format & SF_FORMAT_SUBMASK;

    if (subformat == SF_FORMAT_FLOAT || subformat == SF_FORMAT_DOUBLE){   
        log_addf(VTEXT(T_FLOAT_UNSUPP));
        sf_close (ssbd->sndfile);
        ssbd->sndfile = NULL;
        return -1;
    }

    /*dbg("sfinfo: format=%x channels=%d speed=%d   frames=%d\n", sfinfo.format, sfinfo.channels, sfinfo.samplerate, sfinfo.frames);*/
	if (ssbd->dsp->type == DSPT_SNDPIPE) {
		ssbd->dsp->set_sdr_format(ssbd->dsp, cfg->sdr_block / 2, cfg->sdr_af_speed, 1);
		sfinfo.format     = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
		sfinfo.channels   = ssbd->dsp->channels;
		sfinfo.samplerate = ssbd->dsp->speed;
	}else{
		ssbd->dsp->set_format(gssbd->dsp, &sfinfo, 1);
	}
	ssbd->dsp->set_source(gssbd->dsp);
	if (ssbd->dsp->open(ssbd->dsp, 1)<0){
		log_addf(VTEXT(T_CANT_OPEN_DSP_REC), ssbd->dsp->name);
		sf_close (ssbd->sndfile);
		ssbd->sndfile = NULL;
		return -1;
	};

    g_free(ssbd->buffer);
    ssbd->buffer = g_new0(short, ssbd->dsp->samples);
    
#ifdef USE_FFT
	fft_start(gfft, ssbd->dsp->frames, ssbd->dsp->speed);
#endif
    ssbd->channels = gssbd->dsp->channels;
#ifdef Z_HAVE_SDL
    if (zsdl && gses){
	    sw_scope_clear_scopes();
        ssbd->scope_w = gses->ontop->w * zsdl->font_w;								 
        ssbd->scope_samples = ssbd->channels * ssbd->scope_w;
        ssbd->scope_buf = g_new0(short, ssbd->scope_samples);
        ssbd->scope_show = 0;
        ssbd->scope_i = 0;
    }
#endif
    ssbd_thread_create(ssbd, ssbd_rec_thread_func);
    if (gses) gses->icon=ssbd->recicon;
    
    ssbd->recording=1;
	return 0;

norec:;    
    if (ssbd->norecshowed) return 0;
    log_addf(VTEXT(T_NOT_RECORDING_S), errbuf); 
    ssbd->norecshowed=1;
    return 0;  
}

void rec_thread_sigint(int a){
    /*dbg("rec_thread_sigint\n");*/
    gssbd->proc_break=1;
}

gpointer ssbd_rec_thread_func(gpointer data){
    gchar *c;
    int frames, samples, writtenframes=0, err, i;
    int max, loglevel, avg;
    unsigned long sum;
    time_t now;
    double df;
	struct timeval tv;
#ifdef VOIP
	struct voippacket vp;
	struct sockaddr_in sin;
	short val = 0;
#endif
#ifdef Z_HAVE_SDL
	int tocopy;
#endif
		

	zg_thread_set_name("Tucnak ssbd rec");

#ifdef VOIP
	memset(&sin, 0, sizeof(struct sockaddr_in));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(7300);
	//sin.sin_addr.s_addr = 
	inet_aton("192.168.1.255", &sin.sin_addr);
#endif
    
    while(1){
        if (gssbd->proc_break) break;
        
        frames = gssbd->dsp->read(gssbd->dsp, gssbd->buffer, gssbd->dsp->frames);
		if (frames == -EWOULDBLOCK) {
			usleep(20000);
			continue;
		}
        if (frames<=0) {
            GString *gs = g_string_new("");
            err=errno;
            MUTEX_LOCK(ssbd);
            g_string_append_printf(gs, "!%s %s ", VTEXT(T_CANT_READ_FROM), gssbd->dsp->name);
            z_strerror(gs, err);
            g_string_append_printf(gs, " %d %d\n", frames, err);
            MUTEX_UNLOCK(ssbd);
			zselect_msg_send(zsel, "%s;%s", "SSBR", gs->str);
            g_string_free(gs, TRUE);
            break;
        }
		samples = (frames * gssbd->dsp->samples) / gssbd->dsp->frames;

        gettimeofday(&tv, NULL);
		//dbg("%02d.%03d readed=%d  readed/%d=%d \n", tv.tv_sec % 100, tv.tv_usec / 1000, readed, sizeof(short), readed/sizeof(short));

//		for (i=0; i<512;i++) ssbd->buffer[i]=rand(); // FIXME
		//for (i=0; i<readeds;i++) gssbd->buffer[i]=1000 * ((2 *(i % 2)) - 1);
		//dbg("ssbd readed=%d readeds=%d [%d %d %d %d %d %d %d %d]\n", readed, readeds, ssbd->buffer[0], ssbd->buffer[1], ssbd->buffer[2], ssbd->buffer[3], ssbd->buffer[4], ssbd->buffer[5], ssbd->buffer[6], ssbd->buffer[7]);
		//for (i = 0; i < samples; i++) gssbd->buffer[i] = -i;
        
#if 0
		for (i=0; i<readeds;i++){
			gssbd->buffer[i] = 32767 * (
					0.6 * cos((ix* 1000.0 *2*M_PI)/cfg->ssbd_samplerate) +
                    /*0.1 * cos((ix* 2000.0 *2*M_PI)/cfg->ssbd_samplerate) +
                    0.1 * cos((ix* 3000.0 *2*M_PI)/cfg->ssbd_samplerate) + */
					0);
			ix++;
		}
#endif
        if (gssbd->proc_break) break;
		

#ifdef Z_HAVE_SDL
		if (gssbd->scope_show == 0){
			tocopy = Z_MIN(samples, gssbd->scope_samples - gssbd->scope_i);
			if (tocopy > 0) {
				memcpy(gssbd->scope_buf + gssbd->scope_i, gssbd->buffer, tocopy * sizeof(short));
				gssbd->scope_i += tocopy;

				if (gssbd->scope_i >= gssbd->scope_samples){
					sum=0;
					max=0;
					avg=0;
					for (i = 0; i < samples; i++){
						short sample = gssbd->buffer[i];
						avg+=sample;
					}

					avg /= gssbd->dsp->channels;
					avg /= samples;
					for (i = 0; i < samples; i++){
						short sample=gssbd->buffer[i] - avg;
						if (sample < 0) sample = -sample;
						sum += sample;
						if (sample > max) max = sample;
					}
					
					gssbd->midlevel=sum/samples;
					gssbd->maxlevel=max;
				//	dbg("avg=%d midlevel=%d maxlevel=%d\n", avg, ssbd->midlevel, ssbd->maxlevel);
					if (max<32) loglevel=0;
					else loglevel=(log((double)max)-3.465735903)*14;
					if (loglevel>95) loglevel=95;
					MUTEX_LOCK(gssbd->loglevel);
					gssbd->loglevel = loglevel;
					MUTEX_UNLOCK(gssbd->loglevel);

					gssbd->scope_show = 1;
					//dbg("send SSBP;L  scope_i=%d  scope_samples=%d  scope_show=%d\n", gssbd->scope_i, gssbd->scope_samples, gssbd->scope_show);
					gssbd->scope_i = 0;
					zselect_msg_send(zsel, "%s;%s", "SSBR", "L");
				}
			}
		}


#endif

		
		df=z_df(gssbd->rfilename);
/*        dbg("df=%f df=%d diskfree=%d\n", df, (int)(df/1048576), cfg->ssbd_diskfree);*/
        if (df>=0.0 && cfg->ssbd_diskfree>0){
            if (df < (cfg->ssbd_diskfree*1048576.0)){
                MUTEX_LOCK(ssbd);
                //dbg("Not enough free disk space for %s: %d<%d (MiB)\n",gssbd->rfilename, (int)(df/1048576), cfg->ssbd_diskfree);
                c=g_strdup_printf("!%s %s %f<%d (MiB)\n", VTEXT(T_NO_DISK_SPACE), gssbd->rfilename, df/1048576.0, cfg->ssbd_diskfree);
                MUTEX_UNLOCK(ssbd);
                //ret = write(tpipe->threadpipe_write, c, strlen(c));
                zselect_msg_send(zsel, "%s;%s", "SSBR", c);
                g_free(c);
                break;
            }
        }

#ifdef USE_FFT
        if (gfft){
			short *p = gssbd->buffer;
            for (i=0; i<frames; i++){
                double v = 0;
                int j;
                for (j=0; j<gssbd->dsp->channels; j++){
                    v += (double)*p++;
                }
                v /= gssbd->dsp->channels;
                v /= 32768;
				if (i >= gfft->lenrin) zinternal("gfft->rin[%i] lenrin=%d frames=%d", i, gfft->lenrin, frames);
                gfft->dynrin[i] = v;
                //gfft->dynrin[i] = 0;
            }
            fft_do(gfft);
			zselect_msg_send(zsel, "SSBR;D");
//            dbg(" done i=%d\n", i);
//            dbg("vali=%d .. %d \t", mini, maxi);
            //RST_STOP;
        }
#endif

        writtenframes = sf_writef_short (gssbd->sndfile, gssbd->buffer, frames);
        if (writtenframes <=0) {
            GString *gs = g_string_new("");
            dbg("ssbd_rec_func write<=0\n"); 
            MUTEX_LOCK(ssbd);
            g_string_append_printf(gs, "!%s %s ", VTEXT(T_CANT_WRITE), gssbd->rfilename);
            z_strerror(gs, errno);
            g_string_append(gs, "\n");
            MUTEX_UNLOCK(ssbd);
            zselect_msg_send(zsel, "%s;%s", "SSBR", gs->str);
            g_string_free(gs, TRUE);
            break;
        }

#ifdef VOIP
		for (i = 0; i < readed / sizeof(short); i++) gssbd->buffer[i] = val++;


        if (gssbd->voip){
            for (i = 0; i < readed; i += sizeof(vp.buf)){ // i is in bytes
                int r;
                vp.seq = htonl(gssbd->voip->seq++);
                r = readed - i;
                if (r > sizeof(vp.buf)) r = sizeof(vp.buf);
                vp.len = r;
                memcpy(&vp.buf, ((char *)gssbd->buffer) + i, vp.len);
                voip_distribute(gssbd->voip, &vp);
            }
        }
#endif



        now=time(NULL);
        MUTEX_LOCK(ssbd);
        if (gssbd->recstop && now > gssbd->recstop){
            dbg("ssbd_rec_func: timeout\n");
            //ret = write(tpipe->threadpipe_write, c, strlen(c));
            zselect_msg_send(zsel, "%s;%s", "SSBR", "Q");
            //g_free(c);
            MUTEX_UNLOCK(ssbd);
            break;
        }
        MUTEX_UNLOCK(ssbd);
    }
    
	memset(gssbd->buffer, 0, gssbd->dsp->bytes);
    dbg("ssbd_rec_thread_func exited\n");
	return 0;
}

void ssbd_rec_read_handler(struct ssbd *ssbd, gchar *str){
    /*dbg("ssbd_read_handler\n");*/
    
    switch(str[0]){
        case '!':  /* error */
            cq_abort(1); 
            log_addf("ssbd: %s", str+1);
            if (gses) gses->icon=NULL;
            redraw_later();
            break;
        case 'L':  /* recording level */
            //ssbd->loglevel=atoi(str+1);
#ifdef Z_HAVE_SDL
			
			if (gses && gses->ontop->type == SWT_SCOPE){
				gses->ontop->gdirty = 1;
			}else{
				gssbd->scope_show = 0;
			}
#endif
			//sw_scope_redraw(gses->ontop, aband, 0);
            redraw_later();
            /*dbg("level=%d\n", ssbd->loglevel);*/
            break;
        case 'Q':
            ssbd_thread_join(ssbd);
            ssbd_abort(ssbd, 1);
            redraw_later(); // to clean record level bar
            break;
		case 'D':
#ifdef Z_HAVE_SDL
			if (gses && gses->ontop->type == SWT_SCOPE){
				gses->ontop->gdirty = 1;
			}
#endif
            redraw_later();
    }
}



/* ------- misc -------------------------------------------------- */

int ssbd_callsign(struct ssbd *ssbd, char *call){
    
    if (!ssbd) return 0;

    MUTEX_LOCK(ssbd);
    zg_free0(ssbd->callsign);
    ssbd->callsign=g_strdup(call);
    MUTEX_UNLOCK(ssbd);
	return 0;
}



gchar *unique_filename(gchar *filename){
    struct stat st;
    int ser;
    gchar *file, *ext, *c;
    char *c1, *c2, *c3;

    c1=c2=c3=NULL;
    if (stat(filename, &st)) return filename; /* filename doesn't exist */
    
    if (regmatch(filename, "(.*)(\\..*)", &c1, &c2, &c3, NULL)==0){
        file=g_strdup(c2);
        ext=g_strdup(c3);
        g_free(filename);
    }else{
        file=filename;
        ext=g_strdup("");
    }
    if (c1) g_free(c1);
    if (c2) g_free(c2);
    if (c3) g_free(c3);
    
    c=NULL;
    for (ser=1; ;ser++){
        /*dbg("file='%s'  ext='%s'\n", file, ext);*/
        c=g_strdup_printf("%s%d%s", file, ser, ext);
/*        dbg("c='%s'\n", c);*/
        if (stat(c, &st)){ /* c doesn't exist */
            break;
        }
        g_free(c);
    }
    return c;
}

void ssbd_watchdog(struct ssbd *ssbd, int start_rec){
    time_t t;

//    dbg("ssbd_watchdog\n");
    if (!cfg->ssbd_maxmin) {
        ssbd->recstop=0;
        return;
    }
    t=time(NULL);
    t+=60*cfg->ssbd_maxmin;
    ssbd->recstop=t;
    if (!start_rec) return;

    if (ssbd->recording) return;
    if (gses->tx) return;
    if (gses->last_cq) return;
    
    //ssbd_rec_file(ssbd);
	ssbd_abort(ssbd, 0);
    
}

void menu_ssbd_play(void *arg, void *unused){
    SF_INFO iin, iout;
    SNDFILE *fin, *fout;
    short *buf;
    int l, r, w;
    char *filename = (char *)arg;

	//filename = "e:\\home\\tucnak\\cq\\OK1ZIA_OK1ZIA2ch.wav";

    dbg("menu_ssbd_play('%s')\n", filename);
    memset(&iin, 0, sizeof(SF_INFO));
    fin = sf_open(filename, SFM_READ, &iin);
    if (!fin){
		char *sferr = z_1250_to_8859_2(g_strdup(sf_strerror (NULL)));
        log_addf(VTEXT(T_CANT_PLAY_FILE), filename, sferr);
		g_free(sferr);
        return;
    }
    if (iin.seekable){
        sf_close(fin);
		fin = NULL;
        player_play(filename);
		return;
    }

    // convert to PCM16
    memcpy(&iout, &iin, sizeof(SF_INFO));
    iout.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;

    zg_free0(gssbd->pl_pcmfile);
    gssbd->pl_pcmfile = g_strconcat(tucnak_dir, "/tmp.wav", NULL);
    fout = sf_open(gssbd->pl_pcmfile, SFM_WRITE, &iout);
    if (!fout){
		char *sferr = z_1250_to_8859_2(g_strdup(sf_strerror (NULL)));
        log_addf(VTEXT(T_CANT_WRITE_SS), gssbd->pl_pcmfile, sferr);
		g_free(sferr);
        return;
    }

    l = 65536;
    buf = g_new0(short, l);

    while(1){
        r = sf_read_short(fin, buf, l);
        if  (r <= 0) break;
        w = sf_write_short(fout, buf, r);
        if (w <= 0) break;
        if (r != w) break;
    }

    sf_close(fin);
	fin = NULL;
	sf_close(fout);
	fout = NULL;

    player_play(gssbd->pl_pcmfile);
    g_free(buf);

}


void scan_dir(char *base, ZPtrArray *ia){
    DIR *d;
    struct dirent *de;
    struct stat st;
    char *full = NULL;

    d = opendir(base);
    while ((de = readdir(d)) != NULL){
        if (strcmp(de->d_name, ".") == 0) continue;
        if (strcmp(de->d_name, "..") == 0) continue;
//        dbg("de='%s'\n", de->d_name);
        zg_free0(full);
        full = g_strconcat(base, "/", de->d_name, NULL);
        if (stat(full, &st)) continue;
//        dbg("full='%s'\n", full);
        if (S_ISDIR(st.st_mode)) scan_dir(full, ia);
        if (S_ISREG(st.st_mode)) z_ptr_array_add(ia, g_strdup(full));
    }
    closedir(d);
    zg_free0(full);
}

void ssbd_play_last_sample(struct ssbd *ssbd, struct qso *qso){
    ZPtrArray *ia;
    struct tm tm;
    int i, act, first, len, d, t;
    struct menu_item *mi = NULL;
	char *c;
	time_t qt;
	char *template_;

    //log_addf("qso=%p", qso);
    if (ssbd->recording){
        cq_abort(1);   /* abort recording */
    }else{
        rx();
    }

    if (!qso){
        if (ssbd->rfilename && *ssbd->rfilename){
            //dbg("rfilename=%s gses->last_cq=%p\n", ssbd->rfilename, gses->last_cq);
            //ssbd_play_file(ssbd, ssbd->rfilename);
			menu_ssbd_play(ssbd->rfilename, NULL);
            //gses->icon=ssbd->playicon;
        }else{
            log_adds(VTEXT(T_NO_LAST_SAMPLE_RECORDED));
        }

        redraw_later();
        return;
    }

    template_ = g_strdup(cfg->ssbd_template);
   // log_addf("template='%s'", template);
    c = strchr(template_, '%');
    if (!c){
        log_adds(VTEXT(T_NO_MACRO_IN_TEMPLATE));
        goto x;
    }
    *c='\0';
    if (strlen(template_) && template_[strlen(template_)-1] == '/') template_[strlen(template_)-1] = '\0';
   // log_addf("template='%s'", template);
    ia = z_ptr_array_new();
    c = convert_esc(template_, NULL, 0, 0); 
    scan_dir(c, ia);
    g_free(c);

    d = atoi(qso->date_str);
    t = atoi(qso->time_str);
    tm.tm_sec = 0;
    tm.tm_min = t % 100;
    tm.tm_hour = t / 100;
    tm.tm_mday = d % 100;
    tm.tm_mon = (d / 100 ) % 100 - 1;
    tm.tm_year = d / 10000 - 1900;
    qt = timegm(&tm);
    dbg("qt=%d d=%d t=%d\n", qt, d, t);
    c = convert_esc(cfg->ssbd_template, NULL, 0, qt);
//    dbg("   c='%s'\n", c);
    z_ptr_array_add(ia, c);
    z_ptr_array_qsort(ia, z_compare_string);

    act=-1;
    for (i=0; i<ia->len;i++){
        if (strcmp((char*)z_ptr_array_index(ia, i), c)!=0) continue;
        act=i;
        break;
    }
    if (act < 0) goto x;
    z_ptr_array_remove_index(ia, act);

//    for (i=0; i<ia->len;i++) dbg("%3d '%s'\n", i, z_ptr_array_index(ia, i));
    dbg("act=%d\n", act);
    first = act - term->y / 2 - 2;
    len  =  term->y - 2;
    dbg("first=%d len=%d\n", first, len);
    if (first < 0) {
        first = 0;
    }
    if (first + len - 1 >= ia->len){
        len = ia->len - first; 
    }
    dbg("first=%d len=%d\n", first, len);
    if (!(mi = new_menu(3))) return;
    for (i=0; i<len;i++){
        char *f = (char *)z_ptr_array_index(ia, i+first);
        dbg("%3d '%s'\n", i+first, f);
            
        add_to_menu(&mi, 
                g_strdup(f), 
                "", "", 
                menu_ssbd_play, f, 1);    
    }

// FIXME    z_ptr_array_free_all(ia);
    
    do_menu_selected(mi, NULL, act - first);
x:;    
    g_free(template_);
}



#endif



