/*
    Tucnak - VHF contest log
    Copyright (C) 2013-2020 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 "voip.h"

#include <libzia.h>

#include "bfu.h"
#include "dsp.h"
#include "fifo.h"
#include "main.h"
#include "net.h"
#include "rc.h"
#include "session.h"
#include "subwin.h"
#include "ssbd.h"
#include "tsdl.h"

#ifdef VOIP
#ifdef HAVE_SNDFILE

struct voip *init_voip(void){
    int port;
	struct voip *voip = g_new0(struct voip, 1);

	voip->jitterbuf = g_list_alloc();
	voip->seq = 1;
    voip->spying = g_ptr_array_new();
    MUTEX_INIT(voip->spying);
    
    voip->sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (voip->sock == -1) goto err;

    if (z_sock_reuse(voip->sock, 0)){
        error("Can't clear SO_REUSEADDR\n");
        goto err;
    }
    
    if (z_sock_nonblock(voip->sock, 1)){
        error("Can't set O_NONBLOCK\n");
        goto err;
    }
    
    for (port=7300; port<65536; port++){
        struct sockaddr_in sin;
        memset(&sin, 0, sizeof(struct sockaddr_in));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(port);
        sin.sin_addr.s_addr = INADDR_ANY;
        if (bind(voip->sock, (struct sockaddr *)&sin, sizeof(sin))==0) break;

        if (port==65535) goto err;
        continue;
    }  
    voip->udpport = port;
	return voip;
err:;
    free_voip(voip);
    log_addf(VTEXT(T_VOIP_NO_INITIALIZED));
    return NULL;
}

void free_voip(struct voip *voip){
    int i;
	if (!voip) return;
    //voip_thread_join(voip);
    if (voip->sock != -1) closesocket(voip->sock);
	g_list_free(voip->jitterbuf);
    for (i = 0; i < voip->spying->len; i++){
        struct voippeer *p = (struct voippeer *)g_ptr_array_index(voip->spying, i);
        voip_spy_free(p);
    }
    MUTEX_FREE(voip->spying);
    g_ptr_array_free(voip->spying, TRUE);
	g_free(voip);
}

struct voippacket *init_voippacket(void *srcbuf, int buflen){ // len is in bytes
	struct voippacket *p;
	int i;
	short *s, *d;

	size_t toalloc = buflen;//sizeof(uint32_t) + sizeof(uint16_t) + len * sizeof(short);
	p = (struct voippacket *)g_malloc(toalloc);
	p->len = (buflen - sizeof(struct voippacket) + sizeof(short)) / sizeof(short);
	s = (short *)srcbuf;
	d = p->buf;
	for (i = 0; i < p->len; i++){
		*d = htons(*s);
		d++;
		s++;
	}
	return p;	
}

void free_voippacket(struct voippacket *p){
	g_free(p);
}

static int voip_compare_data(gconstpointer a, gconstpointer b, gpointer data){
	struct voippacket *voipa = (struct voippacket *)a;
	struct voippacket *voipb = (struct voippacket *)b;
	int ret;

	if (voipa == NULL) return -1;
	if (voipb == NULL) return +1;
	ret = voipa->seq - voipb->seq;
	if (ret == 0) ((struct voip *)data)->dupe = 1;
	return ret;
} 

int voip_add(struct voip *voip, struct voippacket *vp){
    if (!voip) return -1;
	voip->dupe = 0;
	voip->jitterbuf = g_list_insert_sorted_with_data(voip->jitterbuf, vp, voip_compare_data, voip);
	if (voip->dupe){
		voip->jitterbuf = g_list_remove(voip->jitterbuf, vp);
		free_voippacket(vp);
	}else{
		voip->len++;
	}

	return 0;
}

struct voippacket *voip_get(struct voip *voip){
	struct voippacket *p;

    if (!voip) return NULL;
	if (!voip->jitterbuf->next) return NULL;

	p = (struct voippacket *)voip->jitterbuf->next->data;
	if (p != NULL){
		voip->jitterbuf = g_list_delete_link(voip->jitterbuf, voip->jitterbuf->next);
		voip->len--;
	}
	return p;
}

int voip_play(struct voippacket *p, short *buf, int len){ // len in samples
	short *s, *d;
	int i, l;

	if (!p){
		d = buf;
		for (i = 0; i < len; i++) *d++ = 0;
		return len;
	}

	s = p->buf;
	d = buf;
	l = Z_MIN(len, p->len);
	for (i = 0; i < l; i++) *d++ = ntohs(*s++);
	return l;
}

void voip_test(struct voip *voip){
	struct voippacket *p;
	
	p = g_new0(struct voippacket, 1);
	p->seq = 1;
	voip_add(voip, p);

	p = g_new0(struct voippacket, 1);
	p->seq = 2;
	voip_add(voip, p);

	p = voip_get(voip);

	p = g_new0(struct voippacket, 1);
	p->seq = 3;
	voip_add(voip, p);

	p = g_new0(struct voippacket, 1);
	p->seq = 2;
	voip_add(voip, p);

	p = voip_get(voip);
	p = voip_get(voip);
	p = voip_get(voip);
	p = voip_get(voip);


}


//int voip_beep_thread

void voip_spy_add(struct voip *voip, char *id, char *voipaddr){
    struct voippeer *p;
    int i;
    char *colon, *c;;
    
    if (!voip) return;

    MUTEX_LOCK(voip->spying);
    for (i = 0; i < voip->spying->len; i++){
        struct voippeer *pp = (struct voippeer *)g_ptr_array_index(voip->spying, i);
        if (strcmp(pp->peerid, id) == 0) goto x;
    }
   
    p = g_new0(struct voippeer, 1);
    p->peerid = g_strdup(id);
    colon = strchr(voipaddr, ':');
    p->sin.sin_family = AF_INET;
    p->sin.sin_port = htons(atoi(colon+1));
    c = g_strdup(voipaddr);
    z_strip_from(c, ':');    
    inet_aton(c, &p->sin.sin_addr);
    g_ptr_array_add(voip->spying, p);
x:;
    MUTEX_UNLOCK(voip->spying);
}

void voip_spy_update(struct voip *voip, char *id, char *voipaddr){
    struct voippeer *p;
    char *colon, *c;;
    
    if (!voip || !voip->spied) return;
    p = voip->spied;

    if (strcmp(p->peerid, id) != 0) return;
   
    colon = strchr(voipaddr, ':');
    p->sin.sin_port = htons(atoi(colon+1));
    c = g_strdup(voipaddr);
    z_strip_from(c, ':');    
    inet_aton(c, &p->sin.sin_addr);
}

void voip_spy_remove(struct voip *voip, char *id){
    struct voippeer *p;
    int i;
    
    if (!voip) return;

    MUTEX_LOCK(voip->spying);
    for (i = voip->spying->len - 1; i >= 0; i--){
        p = (struct voippeer *)g_ptr_array_index(voip->spying, i);
        log_addf("voip_spy_remove: i=%d peerid='%s'", i, p->peerid);
        if (strcmp(id, p->peerid) != 0) continue;
        g_ptr_array_remove_index(voip->spying, i);
        voip_spy_free(p);
    }
    MUTEX_UNLOCK(voip->spying);
}

void voip_spy_free(struct voippeer *p){
    if (!p) return;
    g_free(p->peerid);
    g_free(p);
}

void voip_spy_dump(struct voip *voip){
    int i;
    struct voippeer *p;
    GString *gs = g_string_new("voip_spy_dump: ");
    if (!voip) {
        g_string_append(gs, "voip==NULL");
        return;
    }

    if (voip->spied){
        p = voip->spied;
        g_string_append_printf(gs, "%s(%s:%d)", p->peerid, inet_ntoa(p->sin.sin_addr), ntohs(p->sin.sin_port));
    }
    g_string_append(gs, " [");
    MUTEX_LOCK(voip->spying);
    for (i = 0; i < voip->spying->len; i++){
        p = (struct voippeer *)g_ptr_array_index(voip->spying, i);
        if (i > 0) g_string_append(gs, ", ");
        g_string_append_printf(gs, "%s(%s:%d)", p->peerid, inet_ntoa(p->sin.sin_addr), ntohs(p->sin.sin_port));
    }
    MUTEX_UNLOCK(voip->spying);
    g_string_append(gs, "]");
    log_adds(gs->str);
    dbg("%s\n", gs->str);
    g_string_free(gs, TRUE);
}


static void voip_select_spy(void *itarg, void *menuarg){
    char *peers3, *peer, *id;
    int tok = 0, menui = 0, no = GPOINTER_TO_INT(itarg);
    struct voip *voip = gssbd->voip;

    if (!voip) return;
    dbg("select_spy3(%p, %p)\n", itarg, menuarg);
	
    peers3 = g_strdup(gnet->peers3->str);
	while ((peer = z_tokenize(peers3, &tok)) != NULL){
		int tok2 = 0;
		id = z_tokenize(peer, &tok2);
		/*op =*/ z_tokenize(peer, &tok2);
		/*rwbands =*/ z_tokenize(peer, &tok2);

        if (menui == no){
            char *c;
            if (voip->spied){
                c = g_strdup_printf("EV %s;%s\n", voip->spied->peerid, gnet->myid);
                rel_write_all(c);
                g_free(c);
                voip->spied = NULL;
            }
            c = g_strdup_printf("VS %s;%s;%s:%d\n", id, gnet->myid, inet_ntoa(gnet->my.sin_addr), voip->udpport);
            rel_write_all(c);
            g_free(c);

            voip->spied = g_new0(struct voippeer, 1);
            voip->spied->peerid = g_strdup(id);
            //voip->spied->sin // unknown now
            break;
        }
	}
    g_free(peers3);
    voip_spy_dump(voip);
}

void voip_spy(void *xxx){
	int tok = 0, max = 0, menui = 0;
	char *peer, *id, *op, *rwbands, *s;
	struct menu_item *mi = NULL;
	char *peers3;
    
    if (!gssbd->voip){
        log_addf(VTEXT(T_VOIP_NO_INITIALIZED));
        return;
    }

	progress(NULL);
	peers3 = g_strdup(gnet->peers3->str);
	while ((peer = z_tokenize(peers3, &tok)) != NULL){
		int tok2 = 0;
		id = z_tokenize(peer, &tok2);
		op = z_tokenize(peer, &tok2);
		rwbands = z_tokenize(peer, &tok2);

        if (!mi) mi = new_menu(3);

        s = g_strdup_printf("%s %s %s", id, op, rwbands);
        if (strlen(s) > max) max = strlen(s);
        add_to_menu(&mi, s, "", "", voip_select_spy, GINT_TO_POINTER(menui), 0);
        menui++;
	}
    g_free(peers3);
    dbg("menui=%d\n");
	if (mi){
        set_window_ptr(gses->win, (term->x-max)/2,(term->y-2-menui)/2);
        do_menu(mi, NULL);
    }else
        errbox(VTEXT(T_NO_PEERS),0);
}

void voip_end_spy(void *xxx, void *yyy){
    char *c;
    struct voip *voip = gssbd->voip;

    if (!voip){
        log_addf(VTEXT(T_VOIP_NO_INITIALIZED));
        return;
    }
    if (!voip->spied) return;

    c = g_strdup_printf("EV %s;%s\n", voip->spied->peerid, gnet->myid);
    rel_write_all(c);
    g_free(c);
    voip_spy_free(voip->spied);
    voip->spied = NULL;
    voip_spy_dump(voip);
}

void voip_distribute(struct voip *voip, struct voippacket *vp){
    int i;

    MUTEX_LOCK(voip->spying);
    for (i= 0; i< voip->spying->len; i++){
        struct voippeer *p = (struct voippeer *)g_ptr_array_index(voip->spying, i);
        sendto(voip->sock, (const char *)&vp, sizeof(struct voippacket) + (vp->len - 1) * sizeof(short), 0, (struct sockaddr *)&p->sin, sizeof(p->sin)); 
    }
    MUTEX_UNLOCK(voip->spying);
}

void *voip_thread_func(void *arg){
    char buf[2048];
    int ret;
    struct sockaddr_in sin;
    socklen_t slen;
    struct voippacket *vp;
    struct voip *voip = (struct voip *)arg;
    struct ssbd *ssbd = gssbd;


	zg_thread_set_name("Tucnak voip receiver");
    while (!voip->thread_break){
        while(1){
            ret = recvfrom(voip->sock, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &slen);
            if (ret < 0) break;
            log_addf("voip from %s:%d, %d bytes", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), ret);
            vp = init_voippacket(buf, ret);
            voip_add(voip, vp);
        }
        usleep(20000);
        vp = voip_get(voip);
        if (vp){
            ret = dsp->write (dsp, vp->buf, vp->len * sizeof(short));
        }else{
            //ret = dsp->write (dsp, ssbd->buffer, dsp->bufsize);
			ret = -1;
        }
        if (ret < 0){
            int err = errno;
            GString *gs = g_string_new("");
            //MUTEX_LOCK(ssbd);
            g_string_append_printf(gs, "!%s %s ", VTEXT(T_CANT_WRITE_TO), 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);
            voip->thread_break = 1;
       }
    }
    dbg("voip_thread_func exited\n");
    return NULL;
}

int voip_receive(struct ssbd *ssbd){
    SF_INFO sfinfo;
    struct voip *voip = ssbd->voip;

    ssbd_abort(ssbd, 1);
    
    
    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=22050;
    
    
    dsp->set_format(dsp, &sfinfo, 0);
    dsp->set_plevel(dsp);
    if (dsp->open(dsp, 0)<0) {
        log_addf(VTEXT(T_ANT_OPEN_DSP_PLAY), dsp->name);
        sf_close (ssbd->sndfile);
        ssbd->sndfile = NULL;
        return -1;
    };
    dbg("receive opened: channels=%d speed=%d\n", dsp->channels, dsp->speed);
    
    g_free(ssbd->buffer);
    ssbd->buffer = g_new0(short, dsp->bufsize/sizeof(short));
    ssbd->bytes = dsp->bufsize;
    ssbd->samples = ssbd->bytes / sizeof(short);
    ssbd->frames = ssbd->samples / dsp->channels;
    
    //gfft_start(gfft, gssbd->frames);
    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;
        ssbd->scope_i = 0; 
    }
    if (sdl && gses) gses->icon = sdl->xfer;
#endif

    ssbd_thread_create(ssbd, voip_thread_func);
	
    return 0;
}

/*void voip_thread_create(struct voip *voip){
    dbg("voip_thread_create\n");
    voip->thread_break=0;
    if (voip->thread) return;
	voip->thread = g_thread_try_new("voip", voip_thread_func, (gpointer)voip, NULL);
}

void voip_thread_join(struct voip *voip){
    if (!voip->thread) return;
    voip->thread_break=1;
    dbg("join voip...\n");
    g_thread_join(voip->thread);
    dbg("done\n");
	voip->thread = NULL;
} */


#endif
#endif
