/*
    Tucnak - VHF contest log
    Copyright (C) 2009-2015 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.

    UTF-8 detect: ěščřžýáíé

*/

#include "header.h"

#include "bfu.h"
#include "cwdb.h"
#include "dwdb.h"
#include "edi.h"
#include "fifo.h"
#include "hf.h"
#include "inputln.h"
#include "kbd.h"
#include "kbdbind.h"
#include "kst.h"
#include "main.h"
#include "menu.h"
#include "qrvdb.h"
#include "qsodb.h"
#include "rc.h"
#include "rotar.h"
#include "tsdl.h"
#include "session.h"
#include "subwin.h"
#include "tregex.h"
#include "zstring.h"

struct qrvdb *qrv;

int xyz=1;
struct qrvdb *init_qrv(void){
    struct qrvdb *qrvdb;
    int i;

	progress(VTEXT(T_INIT_QRVDB));		

    qrvdb = g_new0(struct qrvdb, 1);
    qrvdb->qrvs = z_ptr_array_new();
    MUTEX_INIT(qrvdb->qrvs);
    qrvdb->hash = g_hash_table_new(g_str_hash, g_str_equal);
    qrvdb->def_bands = 0;
	qrvdb->sortdir = 1;

    for (i=0; i<cfg->bands->len; i++){
        struct config_band *confb = (struct config_band *)z_ptr_array_index(cfg->bands, i);
        if (confb->qrv || confb->qrvnow) qrvdb->def_bands |= 1 << (confb->bandchar-'A');
    }    
    /*qrv->sort = qrv_compare_call;
    qrv->sort2 = NULL;*/
    //qrvdb->sort = qrv_compare_wkd;
    //qrvdb->sort2 = qrv_compare_qrb;
	qrvdb->sort = qrv_compare_qrb;
	qrvdb->sort2 = qrv_compare_qrb;
	qrvdb->showall = 0;
    strcpy(qrvdb->search, "");
    return qrvdb;
}

void free_qrv_item(struct qrv_item *qi){
    zg_free0(qi->call);
    zg_free0(qi->wwl);
    zg_free0(qi->text);
    g_free(qi);
}

gboolean qrv_free_hash_item(gpointer key, gpointer value, gpointer user_data){
    //dbg("%s: %d\n", __FUNCTION__, xyz++);
    g_free(key);
    //dbg("%d\n", xyz++);
    // value is already freeed
    return TRUE;

}
void clear_qrv(struct qrvdb *qrvdb){
    int i;
    struct subwin *sw;
    //dbg("%s: a%d\n", __FUNCTION__, xyz++);
    if (gses && gses->subwins){  // gses==NULL when terminating
        for (i = 0; i < gses->subwins->len; i++){
            sw = (struct subwin *)z_ptr_array_index(gses->subwins, i);
#ifdef Z_HAVE_SDL            
//            if (sw->type == SWT_MAP) zg_free0(sw->minqrvcall);
#endif
			if (sw->type == SWT_KST) kst_clear_users(sw);
        }
    }

    MUTEX_LOCK(qrvdb->qrvs);
    for (i = qrvdb->qrvs->len - 1; i >= 0; i--){
        struct qrv_item *qi = (struct qrv_item*)z_ptr_array_index(qrvdb->qrvs, i);
        z_ptr_array_remove_index(qrvdb->qrvs, i);
    //dbg("%s: d%d\n", __FUNCTION__, xyz++);
        free_qrv_item(qi);
    //dbg("%s: e%d\n", __FUNCTION__, xyz++);
    }
    //dbg("%s: f%d\n", __FUNCTION__, xyz++);
    g_hash_table_foreach_remove(qrvdb->hash, qrv_free_hash_item, NULL);
    //dbg("%s: g%d\n", __FUNCTION__, xyz++);
    MUTEX_UNLOCK(qrvdb->qrvs);
}


void free_qrv(struct qrvdb *qrvdb){
    xyz = 1;

	progress(VTEXT(T_FREE_QRVDB));		

    //dbg("%s: u%d\n", __FUNCTION__, xyz++);
    clear_qrv(qrvdb);
    //dbg("%s: v%d\n", __FUNCTION__, xyz++);

    MUTEX_FREE(qrvdb->qrvs);
    z_ptr_array_free(qrvdb->qrvs, 1);
    //dbg("%s: w%d\n", __FUNCTION__, xyz++);
    g_hash_table_destroy(qrvdb->hash);
    //dbg("%s: x%d\n", __FUNCTION__, xyz++);
    g_free(qrvdb);
    //dbg("%s: y%d\n", __FUNCTION__, xyz++);
}


struct qrv_item *qrv_add(gchar *call, gchar *wwl, int qrv_int, int wkd[32], const gchar *text, time_t kst_time){
    struct qrv_item *qi;
    //char raw[20];
     

    if (!wwl || !*wwl){
        wwl = find_wwl_by_call(cw, call);
        if (!wwl) wwl="";
    }
    //dbg("qrv_add('%s', '%s', %d, (%d;%d;%d), '%s', %d)\n", call, wwl, qrv_int, wkd[2], wkd[4], wkd[6], text, (unsigned int)kst_time);
    

    qi = qrv_get(qrv, call);
    if (qi && strcasecmp(qi->call, call) == 0){
		int wi;
 //       dbg("qrv_update(%s)\n", call);
        if (wwl && *wwl) {
            zg_free0(qi->wwl);
            qi->wwl = z_str_uc(g_strdup(wwl)); 
            qrv_compute_qrbqtf(qi);
        }
		qi->bands_qrv |= qrv_int;
        if (text && strlen(text) > strlen(qi->text)){
            zg_free0(qi->text);
            qi->text = g_strdup(text);
        }
        qi->kst_time = Z_MAX(qi->kst_time, kst_time);
		for (wi = 0; wi < 32; wi++) qi->wkd[wi] += wkd[wi];
        return qi;
    }
    
//    dbg("qrv_new(%s)\n", call);
    qi = g_new0(struct qrv_item, 1);
    qi->call  = z_str_uc(g_strdup(call));
    qi->wwl   = z_str_uc(g_strdup(wwl));
    qi->bands_qrv = qrv_int;
    memcpy(qi->wkd, wkd, sizeof(qi->wkd));
    qi->text  = g_strdup(text);
    qi->kst_time = kst_time;
    qrv_compute_qrbqtf(qi);
    MUTEX_LOCK(qrv->qrvs);
    z_ptr_array_add(qrv->qrvs, qi);
    MUTEX_UNLOCK(qrv->qrvs);

    //z_get_raw_call(raw, call);
    g_hash_table_insert(qrv->hash, g_strdup(call), qi); 
//    dbg("add_to_list(%s %s %s)\r\n", qi->call, qi->wwl, qi->text);
    return qi;
}

#define QFL_BREAK 1

struct qrvbandregex{
	int qrv_int;
	char *regstr;
	int flags;
	regex_t *reg; // never freed
};

#define QB(b)  (1 << (b - 'A'))

struct qrvbandregex qrvbr[] = {
	// !important
	{ QB('C') | QB('E') | QB('G') | QB('H') | QB('I') | QB('J'), "\\<2m {0,}- {0,}6[^0-9]", QFL_BREAK },
	{ QB('E') | QB('G') | QB('H') | QB('I') | QB('J'), "\\<70 {0,}- {0,}6[^0-9]", QFL_BREAK },
	{ QB('E') | QB('G') | QB('H') | QB('I') | QB('J') | QB('K'), "\\<23 {0,}- {0,}3[^0-9]", QFL_BREAK },
	{ QB('G') | QB('H') | QB('I') | QB('J') | QB('K'), "\\<23 {0,}- {0,}3[^0-9]", QFL_BREAK },
	{ QB('G') | QB('H') | QB('I'), "\\<23 {0,}- {0,}9[^0-9]", QFL_BREAK },

	// normal
	{ QB('A'), "\\<6m[^0-9]" },
	{ QB('B'), "\\<4[^0-9]" },
	{ QB('B'), "\\<70m[^0-9]" },

	{ QB('C'), "\\<2[^0-9]" },
	{ QB('C'), "\\<144[^0-9]" },
	{ QB('C'), "\\<144mhz\\>" },

	{ QB('E'), "\\<70[^0-9]" },
	{ QB('E'), "\\<432[^0-9]" },
	{ QB('E'), "\\<432mhz\\>" },
	{ QB('E'), "\\<433[^0-9]" },
	{ QB('E'), "\\<433mhz\\>" },

	{ QB('G'), "\\<23[^0-9]" },
	{ QB('G'), "\\<23cm\\>" },
	{ QB('G'), "\\<1296[^0-9]" },
	{ QB('G'), "\\<1\\.2[^0-9]" },
	{ QB('G'), "\\<1\\.3[^0-9]" },

	{ QB('H'), "\\<13[^0-9]" },
	{ QB('H'), "\\<2\\.3[^0-9]" },
	{ QB('H'), "\\<2320[^0-9]" },

	{ QB('I'), "\\<9[^0-9]" },
	{ QB('I'), "\\<3\\.4[^0-9]" },
	{ QB('I'), "\\<3,4[^0-9]" },

	{ QB('J'), "\\<6[^0-9]" },
	{ QB('J'), "\\<5\\.6[^0-9]" },
	{ QB('J'), "\\<5,6[^0-9]" },
	{ QB('J'), "\\<5\\.7[^0-9]" },
	{ QB('J'), "\\<5,7[^0-9]" },
	{ QB('J'), "\\<5760[^0-9]" },

	{ QB('K'), "\\<3[^0-9]" },
	{ QB('K'), "\\<10368[^0-9]" },

	{ QB('L'), "\\<1\\.2[^0-9]" },
	{ QB('L'), "\\<24[^0-9]" },

	{ QB('M'), "\\<47[^0-9]" },

	{ QB('N'), "\\<76[^0-9]" },

	{ QB('O'), "\\<122[^0-9]" },

	{ QB('P'), "\\<134[^0-9]" },

	{ QB('Q'), "\\<248[^0-9]" },


	{ -1, NULL }
};

struct qrv_item *qrv_kst_add(char *call, char *wwl, char *fullname){
	int i, qrv_int = 0, wkd[32];
	struct qrv_item *ret;

	memset(wkd, 0, sizeof(wkd));

	if (0 && fullname != NULL){ // QRV from full name (Dan 23/13/6/3/24)
		struct qrvbandregex *reg;
		for (reg = qrvbr; reg->regstr != NULL; reg++){
			if (reg->reg == NULL){
				reg->reg = g_new(regex_t, 1);
				i= regcomp(reg->reg, reg->regstr, REG_EXTENDED | REG_NOSUB | REG_ICASE);
				if (i) zinternal("regcomp failed for %s", reg->regstr);
			}
			int match = regexec(reg->reg, fullname, 0, 0, 0);
			if (match == 0) {
				qrv_int |= reg->qrv_int;
				if (reg->flags & QFL_BREAK) break;
			}
		}
#if 1
		dbg("%s    ", fullname);
		for (i = 0; i < 32; i++)
		{
			if (qrv_int & (1 << i)) dbg("%c", 'A' + i);
		}
		dbg("\n");
#endif
	}else{ // QRV from QRV bands
//fromqrv:;
		if (ctest != NULL){
			for (i = 0; i < ctest->bands->len; i++){
				struct band *b = (struct band *)g_ptr_array_index(ctest->bands, i);
				if (b->readonly == 0) qrv_int |= (1 << b->bi);
			}
		}
		else{
			for (i = 0; i < cfg->bands->len; i++){
				int bi;
				struct config_band *cb = (struct config_band *)g_ptr_array_index(cfg->bands, i);
				if (cb->qrvnow == 0) continue;
				bi = toupper(cb->bandchar) - 'A';
				qrv_int |= (1 << bi);
			}
		}
	}

	ret = qrv_add(call, wwl, qrv_int, wkd, fullname ? fullname : "@KST", 0/*time(NULL)*/);
	sw_qrv_sort(qrv);
	return ret;
}

void qrv_delete(char *call, int bi){
	struct qrv_item *qi;

    if (qrv->qrvs->len != 0) return;
    qi = (struct qrv_item*)g_hash_table_lookup(qrv->hash, call);
    if (!qi) return;
    qi->bands_qrv &= ~(1<<bi);
}

int is_qrv(char *s){
    char *c, d, last = '\0';

    if (!s) return 0;
    if (strlen(s)==0) return 0;
    for (c=s; *c!='\0'; c++){
        d = toupper(*c);
        if (d < 'A' || d > 'Z') return 0;
        if (d < last) return 0;
        last = d;
    }
    return 1;
}

#define QRV_DELIM " \t\r\n"
void load_one_qrv(struct qrvdb *qrvdb, gchar *s){
	struct zstring *zs;
    gchar *call, *wwl, *text, *str, *c;
    int qrv_int = 0, wkd[32];
    time_t kst_time = 0;

	zs = zstrdup(s);
	//dbg("ztokens=%d '%s'\n", ztokens(zs), s);
	if (ztokens(zs) >= 6){ 
		ztokenize(zs, 1);  // first ';'
		
        call = ztokenize(zs, 0);
		if (call[0] == '#') return;

		wwl = ztokenize(zs, 0);

		str = ztokenize(zs, 0);
		for (c = str; *c != '\0'; c++) qrv_int |= 1 << (toupper(*c) - 'A');

		str = ztokenize(zs, 0);
        memset(wkd, 0, sizeof(wkd));
		for (c = str; *c != '\0'; c++) {
            if (*c < 'A' || *c > 'Z') continue;
            wkd[*c - 'A'] = atoi(c+1);
            if (c[1] == '\0' || c[1] > '9') wkd[*c - 'A'] = 1; // for compatibility, 'ABC' means A=1, B=1, C=1 
            // number will be skipped by c++ and next condition
        }

		text = ztokenize(zs, 0);

		str = ztokenize(zs, 0);
        if (str) kst_time = atoi(zs->str);
        
		qrv_add(call, wwl, qrv_int, wkd, text, kst_time);
	}
    zfree(zs);
}

void old_load_one_qrv(struct qrvdb *qrvdb, gchar *s){
    gchar *call, *wwl, *qrv_str, *text;
    int qrv_int, free_text = 0, wkd[32];
    char *token_ptr, *c;

   // dbg("load_one_qrv('%s')\n", s);
    call = strtok_r(s, QRV_DELIM, &token_ptr);
    if (!call) return;

    wwl = strtok_r(NULL, QRV_DELIM, &token_ptr);
    if (!wwl) return;

    qrv_str = strtok_r(NULL, QRV_DELIM, &token_ptr);
    if (!qrv_str) qrv_str = "";

    text = token_ptr;

    memset(wkd, 0, sizeof(wkd));
    if (!is_qrv(qrv_str)){
//        dbg("!isqrv(%s)\n", qrv_str);
        qrv_int = qrv->def_bands;
        //bands_wkd_me = qrv->def_bands;
        text = g_strconcat(qrv_str, " ", text, NULL);
        free_text = 1;
    }else{
        qrv_int = 0;
        for (c=qrv_str;*c!='\0';c++){
            if (isupper(*c)) {
                //dbg("wkd[%d]=1\n", *c-'A');
                wkd[*c-'A'] = 1;
            }
            *c=z_char_uc(*c);
            qrv_int|=1<<(*c-'A');
        }
    }

    qrv_add(call, wwl, qrv_int, wkd, text, 0);
    if (free_text) g_free(text);
}


int load_qrv_from_file(struct qrvdb *qrvdb, gchar *filename){
    FILE *f;
    char s[202];

	clear_qrv(qrvdb);

    f = fopen(filename, "rt");
    if (!f){
        return -1;
    }

    while((fgets(s, 200, f))!=NULL){
        chomp(s);
		if (s[0]=='#') continue;
		if (s[0]==';')
			load_one_qrv(qrvdb,s);
		else
			old_load_one_qrv(qrvdb,s);
    }
    fclose(f);

	if (ctest->wwlmult > 0) qrv->sort = qrv_compare_wwl;

    z_ptr_array_qsort(qrv->qrvs, qrv->sort);
    return 0;
}

struct zstring *qrv_format(struct qrv_item *qi){
    char qrv_str[32], wkd_str[32*30], kst_str[20];
    char  x[2], d;
	struct zstring *zs;

	x[1]='\0';
	strcpy(qrv_str, "");
    for (x[0]='A'; x[0]<'Z'; x[0]++) if (qi->bands_qrv & ( 1 << (x[0] - 'A'))) strcat(qrv_str, x);

	strcpy(wkd_str, "");
    for (d='A'; d<'Z'; d++) {
//            dbg("band=%c i=%d wkd=%d \n", d, d - 'A', qi->wkd[d - 'A']);
        if (qi->wkd[d - 'A'] == 0) continue;
//            dbg("%c=%d\n", d, qi->wkd[d - 'A']);
        sprintf(wkd_str+strlen(wkd_str), "%c%d", d, qi->wkd[d - 'A']);
    }

    sprintf(kst_str, "%d", (int)qi->kst_time);

	zs = zconcatesc("", qi->call, qi->wwl, qrv_str, wkd_str, qi->text, kst_str, NULL);
	return zs;
}

int save_qrv_to_file(char *filename){
    FILE *f;
    int i;
	
    f = fopen(filename, "wt");
    if (!f){
        log_addf(VTEXT(T_CANT_WRITE_S), filename);
        return errno;
    }

    for (i=0; i<qrv->qrvs->len; i++){
		struct zstring *zs;
	    struct qrv_item *qi = (struct qrv_item*)z_ptr_array_index(qrv->qrvs, i);

		zs = qrv_format(qi);
		fprintf(f, "%s\n", zs->str);
		//dbg("zs->str='%s'\n", zs->str);
		zfree(zs);
    }

    fclose(f);
    return 0;
}

int qrv_skip(struct qrv_item *qi, int bi, int setdrawn){
    //if (get_qso_by_callsign(aband, qi->call)!=NULL) return 1;
    int ret = 0;

    if ((qi->bands_wkd & (1<<bi))!=0) ret = 2;
    if (qrv->showall && !*qrv->search) {
		//qi->ac_drawn = 1;
		return ret;
	}
    if ((qi->bands_qrv & (1<<bi))==0) ret = 1;
    if (strlen(qrv->search) > 1){// here's leading /
        ret = 3;
        if (z_strcasestr(qi->call, qrv->search + 1)) ret = 0;
        if (z_strcasestr(qi->wwl,  qrv->search + 1)) ret = 0;
        if (z_strcasestr(qi->text, qrv->search + 1)) ret = 0;
    }
    //dbg("qrv_skip(%s, %d)=%d  qrv=0x%x  wkd=0x%x\n", qi->call, bi, ret, qi->bands_qrv, qi->bands_wkd);
	//qi->ac_drawn = (ret == 0);
	//if (qi->ac_drawn == 0) qi->ac_start = (time_t)0;
    return ret;
}


int qrv_compare_call(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	int ret;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
	if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    ret = strcmp((*qa)->call, (*qb)->call);
	if (ret) return ret * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_call) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_wwl(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	int ret;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    ret = strcmp((*qa)->wwl, (*qb)->wwl);
    if (ret) return ret * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_wwl) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_wkd(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	int ret;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    if (!aband) return 0;
    ret = (*qa)->wkd[aband->bi] - (*qb)->wkd[aband->bi]; 
    if (ret) return (-ret) * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_wkd) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_qrb(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	double d;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    d = (*qa)->qrb - (*qb)->qrb; 
	if (d != 0) return d < 0 ? qrv->sortdir : -qrv->sortdir; // odx on top
    if (qrv->sort2 == qrv_compare_qrb) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_qtf(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	int ret;
    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    if (!aband) return 0;
    ret = (*qa)->qtf - (*qb)->qtf; 
    if (ret) return ret * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_qtf) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_kst_time(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	long int ret;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    if (!aband) return 0;
    ret = (int)((*qa)->kst_time - (*qb)->kst_time); 
    if (ret) return ret * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_kst_time) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_ac_start(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	long int ret;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
	//if (!(*qa)->ac_start) return -qrv->sortdir;
	//if (!(*qb)->ac_start) return +qrv->sortdir;
	if (!aband) return 0;
    ret = (int)((*qa)->ac_start - (*qb)->ac_start); 
    if (ret) return ret * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_ac_start) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_ac_int(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	int ret;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    if (!aband) return 0;
    ret = (*qa)->ac_interval - (*qb)->ac_interval; 
    if (ret) return (-ret) * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_ac_int) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_ac_n(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	int ret;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    if (!aband) return 0;
    ret = (*qa)->ac_n - (*qb)->ac_n; 
    if (ret) return (-ret) * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_ac_n) return 0;
    return qrv->sort2(a, b);
}

int qrv_compare_text(const void *a, const void *b){
    struct qrv_item **qa, **qb;
	int ret;

    qa = (struct qrv_item **)a;
    qb = (struct qrv_item **)b;
    
    if (!*qa && !*qb) return 0;
    if (!*qa) return -qrv->sortdir;
    if (!*qb) return +qrv->sortdir;
    ret = strcmp((*qa)->text, (*qb)->text);
    if (ret) return ret * qrv->sortdir;
    if (qrv->sort2 == qrv_compare_text) return 0;
    return qrv->sort2(a, b);
}


void sw_qrv_seek(struct subwin *sw, int value){
    struct qrv_item *qi;

    while (value > 0){
        sw->cur++;
        if (sw->cur >= qrv->qrvs->len) break;
		if (sw->cur >= 0){
			qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
			if (aband && qrv_skip(qi, aband->bi, 0)) continue;
		}
        value--;
    }
    
    while (value < 0){
        sw->cur--;
        if (sw->cur < 0) break;
		if (sw->cur < qrv->qrvs->len){
			qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
			if (aband && qrv_skip(qi, aband->bi, 0)) continue;
		}
        value++;
    }
}

   
void qrv_sort(int (*fce)(const void*, const void *)){
	if (qrv->sort == fce){
		qrv->sortdir = -qrv->sortdir;
	}else{
		qrv->sort2 = qrv->sort;
		qrv->sort = fce;
	}
    z_ptr_array_qsort(qrv->qrvs, qrv->sort);
	redraw_later();
}

int sw_qrv_kbd_func(struct subwin *sw, struct event *ev, int fw){
    int len;
    struct qrv_item *qi;
    
    switch(kbd_action(KM_MAIN,ev)){
        case ACT_ESC:
            return 0;
            break;
        case ACT_DOWN:
            sw_qrv_seek(sw, 1);
            sw_qrv_check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_UP:
            sw_qrv_seek(sw, -1);
            sw_qrv_check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_PAGE_DOWN:
            sw_qrv_seek(sw, sw->h - 1);
            sw_qrv_check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_PAGE_UP:
            sw_qrv_seek(sw, -sw->h + 1);
            sw_qrv_check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_HOME:
            sw->cur = 0;
            sw_qrv_check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_END:
            sw->cur = qrv->qrvs->len - 1;
            sw_qrv_check_bounds(sw);
            redraw_later();
            return 1;
		case ACT_SCROLL_LEFT:
    		if (sw->ho>0) sw->ho--;
            redraw_later();
            return 1;
		case ACT_SCROLL_RIGHT:
			sw->ho++;
            redraw_later();
            return 1;
        case ACT_ENTER:
			if (aband == NULL) break;
            if (*qrv->search){
                strcpy(qrv->search, "");
                redraw_later();
                if (sw->cur < 0 || sw->cur >= qrv->qrvs->len) return 1;
                qi = (struct qrv_item*)z_ptr_array_index(qrv->qrvs, sw->cur);
                if (!qi) return 1;
                qi->bands_qrv |= 1<<aband->bi;
                return 1;
            }
            sw_unset_focus();
            il_set_focus(aband->il);
            if (sw->cur < 0 || sw->cur >= qrv->qrvs->len) break;
            qi = (struct qrv_item*)z_ptr_array_index(qrv->qrvs, sw->cur);
            if (!qi) break;
            clear_tmpqsos(aband,1);
            process_input(aband, qi->call, 1);
			il_add_to_history(INPUTLN(aband), qi->call);
            redraw_later();
/*#ifdef HAVE_HAMLIB                
            if (trig){
                trig_set_qrg(trig, qi->qrg*1000.0);
#endif                */
            return 1;
        case ACT_BACKSPACE:
            len = strlen(qrv->search);
            if (len == 0) break;
            qrv->search[len - 1] = '\0';
            redraw_later();
            return 1;
        case ACT_DELETE:
			if (!aband) break;
			if (sw->cur < 0 || sw->cur >= qrv->qrvs->len) break;
		    qi = (struct qrv_item*)z_ptr_array_index(qrv->qrvs, sw->cur);
			if (!qi) break;
			qi->bands_qrv &= ~(1<<aband->bi);
			sw_kst_toggle_highlight(NULL, qi->call, -2);
            redraw_later();
            return 1;
        case ACT_INSERT:
            edit_qrv(NULL);
            return 1;
        default:
            //dbg("default: ev->x='%c' search='%s'\n", ev->x, qrv->search);
            if (cfg->slashkey && *cfg->slashkey){
                if (ev->x == '/') ev->x = *cfg->slashkey;
                else if (ev->x == *cfg->slashkey) ev->x = '/';
            }

            if (*qrv->search && ev->y == 0){
                if (!isprint((unsigned char)ev->x)) break;
                len = strlen(qrv->search);
                if (len >= QRVSSIZE - 2) break;
                qrv->search[len] = toupper(ev->x);
                qrv->search[len+1] = '\0';
                //dbg("search='%s'\n", qrv->search);
                redraw_later();
                break;
            }

            switch(ev->x){
                case ' ':
					if (!aband) break;
					if (sw->cur < 0 || sw->cur >= qrv->qrvs->len) break;
                    qi = (struct qrv_item*)z_ptr_array_index(qrv->qrvs, sw->cur);
                    if (!qi) break;
                    clear_tmpqsos(aband,1);
                    process_input(aband, qi->call, 0);
					il_add_to_history(INPUTLN(aband), qi->call);
                    redraw_later();
                    break;
                case '1':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
					qrv_sort(qrv_compare_call);
                    return 1;
                case '2':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
					qrv_sort(qrv_compare_wwl);
                    return 1;
                case '3':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
					qrv_sort(qrv_compare_wkd);
                    return 1;
                case '4':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
					qrv_sort(qrv_compare_qrb);
                    return 1;
                case '5':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
					qrv_sort(qrv_compare_qtf);
                    return 1;
                case '6':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
					qrv_sort(qrv_compare_kst_time);
                    return 1;
                case '7':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
                    if (!gacs) goto sort_text;
					qrv_sort(qrv_compare_ac_start);
                    return 1;
                case '8':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
                    if (!gacs) return 0;
					qrv_sort(qrv_compare_ac_int);
                    return 1;
                case '9':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
                    if (!gacs) return 0;
					qrv_sort(qrv_compare_ac_n);
                    return 1;
                case '0':
					if (ev->y & (KBD_CTRL | KBD_ALT)) return 0;
sort_text:
					qrv_sort(qrv_compare_text);
                    return 1;
                case 'a':
                case 'A':
                    qrv->showall = !qrv->showall;
                    redraw_later();
                    return 1;
                case 'e':
                case 'E':
                    {
						if (sw->cur < 0 || sw->cur >= qrv->qrvs->len) return 1;

						qi = (struct qrv_item*)z_ptr_array_index(qrv->qrvs, sw->cur);
						if (!qi) break;
                    
						edit_qrv(qi);
	                    return 1;
					}
                case 'u':
                case 'U':
					if (!aband) break;
					if (sw->cur < 0 || sw->cur >= qrv->qrvs->len) return 1;
                    qi = (struct qrv_item*)z_ptr_array_index(qrv->qrvs, sw->cur);
                    if (!qi) break;
                    qi->bands_qrv |= 1<<aband->bi;
                    redraw_later();
                    return 1;
                case '/':
                    strcpy(qrv->search, "/");
                    redraw_later();
                    return 1;
            }
    }
    return 0;
}

char *sw_qrv_call_under(struct subwin *sw, int x, int y0){
	int y, i;
	struct qrv_item *qi;

	if (sw->cur < 0 || sw->cur >= qrv->qrvs->len) return NULL;

    y = sw->h / 2;
	if (y == y0) {
        qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
		return g_strdup(qi->call);
	}
	y = sw->h / 2;
    for(i = sw->cur - 1; i >= 0; i--){
        if (y <= 0) break;
        qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
        if (aband != NULL && qrv_skip(qi, aband->bi, 0)) continue;
		y--;
		if (y == y0) return g_strdup(qi->call);
    } 

    y = sw->h / 2;
    for(i = sw->cur + 1; i < qrv->qrvs->len; i++){
        if (y >= sw->h - 1) break;
        qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
		if (aband != NULL && qrv_skip(qi, aband->bi, 0)) continue;
		y++;
		if (y == y0) return g_strdup(qi->call);
    } 
	return NULL;
}

// value: -2 hide, -1=hide+qrv 1=highlight
void sw_qrv_toggle_highlight(struct subwin *sw_unused, char *call, int val){
    char t[25];
    gchar *key;
	int *value;
    
    z_get_raw_call(t, call);

    if (g_hash_table_lookup_extended(gses->hicalls, (gpointer)t, (gpointer*)&key, (gpointer*)&value)){
		if (*value != val){
			*value = val;
			redraw_later();
			return;
		}
        g_hash_table_remove(gses->hicalls, key);
        g_free(key);
        dbg("call='%s' removed\n", t);
    }else{
		value = g_new(int, 1);
		*value = val;
        g_hash_table_insert(gses->hicalls, g_strdup(t), value);
        if (aband){
            int wkd[32];
			char *wwl = NULL;

			if (val == -1){
				qrv_delete(t, aband->bi);
			}
			if (val >= 0){
			    wwl = find_wwl_by_call(cw, t);
		        if (wwl == NULL) wwl = "";
	            memset(wkd, 0, sizeof(wkd));
				qrv_add(t, wwl, (1<<aband->bi), wkd, "@DXC", time(NULL));  
			}
        }
    	dbg("call='%s' inserted\n", t);
    }
    
/*    dbg("%d\n", g_hash_table_size(gses->hicalls));*/
    redraw_later();
}


void sw_qrv_select(void *itdata, void *menudata){
	struct subwin *sw = (struct subwin*)menudata;
	sw_qrv_toggle_highlight(sw, sw->callunder, 1);
}

void sw_qrv_hide(void *itdata, void *menudata){
	struct subwin *sw = (struct subwin*)menudata;
	sw_qrv_toggle_highlight(sw, sw->callunder, -1);
}

void sw_qrv_info(void *itdata, void *menudata){
	struct subwin *sw = (struct subwin*)menudata;
	struct qso *q = g_new0(struct qso, 1);
	q->callsign = sw->callunder;
	call_info(q);
	g_free(q);
}

void sw_qrv_use(void *itdata, void *menudata){
	struct subwin *sw = (struct subwin*)menudata;
	if (!aband) return;
	if (aband->readonly) return;
	process_input(aband, sw->callunder, 0);
	il_add_to_history(INPUTLN(aband), sw->callunder);
	sw_unset_focus();
    il_set_focus(INPUTLN(aband));
}



int sw_qrv_mouse_func(struct subwin *sw, struct event *ev, int fw){
    struct menu_item *mi = NULL;
	struct qrv_item *qi;
	int i;

	if (ev->b & B_DRAG && aband != NULL){
		int dy = sw_accel_dy(sw, ev);
		if (dy == 0) return 1;
		dy = -dy;	
#ifdef Z_HAVE_SDL
		//if (zsdl && ztimeout_occured(sw->olddrag)) dy /= zsdl->font_h;
#endif
		if (dy < 0){
			sw->cur--;
			while (sw->cur >= 0){
				qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
				if (!(aband != NULL && qrv_skip(qi, aband->bi, 0))) break;
				sw->cur--;
			}
		}else{
			sw->cur++;
			while (sw->cur < qrv->qrvs->len){
				qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
				if (!(aband != NULL && qrv_skip(qi, aband->bi, 0))) break;
				sw->cur++;
			}
		}
        
        sw->check_bounds(sw);
        redraw_later();

		sw->olddragx = ev->x;
		sw->olddragy = ev->my;

		return 1;
	}
    
    //if ((ev->b & BM_ACT)!=B_DOWN) return 0;
//    dbg("ev->b=%x BM_ACT=%x B_DOWN=%x BM_EBUTT=%x  \n", ev->b, BM_ACT, B_DOWN, BM_EBUTT);
	if (cfg->usetouch && (ev->b & BM_EBUTT) == B_LEFT) ev->b = (ev->b & ~BM_EBUTT) | B_RIGHT;
    switch (ev->b & BM_BUTCL){
		case B_LEFT:
		case B_RIGHT:
			sw->olddragx = ev->x;
			sw->olddragy = ev->my;
			break;
        case B_LEFT | B_CLICK:
//            if (!sw->il) break;
            /*if (ev->y==sw->y+sw->hh){
                inputln_func(sw->il, ev);
                return 1;
            } */
			if (ev->y == sw->y){
				int xrel = ev->x - sw->x - sw->ho;
				if (xrel > 71){
					qrv_sort(qrv_compare_text);
				}else if (xrel >= 67){
					qrv_sort(qrv_compare_ac_n);
				}else if (xrel >= 63){
					qrv_sort(qrv_compare_ac_int);
				}else if (xrel >= 50){
					qrv_sort(qrv_compare_ac_start);
				}else if (xrel >= 42){
					qrv_sort(qrv_compare_kst_time);
				}else if (xrel >= 36){
					qrv_sort(qrv_compare_qtf);
				}else if (xrel >= 29){
					qrv_sort(qrv_compare_qrb);
				}else if (xrel >= 23){
					qrv_sort(qrv_compare_wkd);
				}else if (xrel >= 16){
					qrv_sort(qrv_compare_wwl);
				}else if (xrel >= 2){
					qrv_sort(qrv_compare_call);
				}
				redraw_later();
				return 1;
			}
			g_free(sw->callunder);
			sw->callunder = sw_qrv_call_under(sw, ev->x - sw->x + sw->ho, ev->y - sw->y);
			dbg("callunder='%s'\n", sw->callunder);
			if (!sw->callunder) break;
			sw_qrv_toggle_highlight(sw, sw->callunder, 1);
            return 1;
        case B_MIDDLE:
/*            dbg("middle\n");*/
            break;
        case B_RIGHT | B_CLICK:
			g_free(sw->callunder);
			sw->callunder = sw_qrv_call_under(sw, ev->x - sw->x + sw->ho, ev->y - sw->y);
			dbg("callunder='%s'\n", sw->callunder);
			if (!sw->callunder) break;
			mi = new_menu(1);
			mi->rtext = sw->callunder;
	        add_to_menu(&mi, CTEXT(T_SELECT), "", CTEXT(T_HK_SELECT), sw_qrv_select, NULL, 0);    
	        add_to_menu(&mi, CTEXT(T_HIDE),   "", CTEXT(T_HK_HIDE), sw_qrv_hide, NULL, 0);    
	        add_to_menu(&mi, CTEXT(T_INFO),   "", CTEXT(T_HK_INFO), sw_qrv_info, NULL, 0);    
	        if (aband) add_to_menu(&mi, CTEXT(T_USE),    "", CTEXT(T_HK_USE), sw_qrv_use, NULL, 0);    
		    set_window_ptr(gses->win, ev->x - 3, ev->y - 1);
		    do_menu(mi, sw);

            break;
        case B_WHUP:
/*            dbg("wheel up\n");*/
			for (i = 0; i < 3; i++){
				sw->cur--;
				while (sw->cur >= 0){
					if (sw->cur >= 0 && sw->cur < qrv->qrvs->len) {
						qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
						if (!(aband != NULL && qrv_skip(qi, aband->bi, 0))) break;
					}
					sw->cur--;
				}
			}
            sw->check_bounds(sw);
            redraw_later();
            return 1;
        case B_WHDOWN:
/*            dbg("wheel down\n");  */
			for (i = 0; i < 3; i++){
				sw->cur++;
				while (sw->cur < qrv->qrvs->len){
					if (sw->cur >= 0 && sw->cur < qrv->qrvs->len) {
						qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
						if (!(aband != NULL && qrv_skip(qi, aband->bi, 0))) break;
					}
					sw->cur++;
				}
			}
            sw->check_bounds(sw);
            redraw_later();
            return 1;
	}
	return 0;
}

void sw_qrv_draw_qrv(struct subwin *sw, int i, struct qrv_item *qi, int color){
    gchar *c;
    char degree=' ';
    struct tm utc;
    char kst_time_str[200];
    char qrb_str[400];
    char qtf_str[20];
    char acstart[30];
    char acint[30];

#if defined(Z_HAVE_SDL) && !defined(Z_ANDROID)     
    //if (sdl) degree='°';
    if (sdl) degree=0xb0;
#endif

    if (!qi || !qi->call){
        c = g_strdup("~");
    }else{
        
        strcpy(qrb_str, "");
        if (qi->qrb >= 0) sprintf(qrb_str, "%5.0fkm", qi->qrb);

        strcpy(qtf_str, "");
        if (qi->qtf >= 0) sprintf(qtf_str, "%3d%c", qi->qtf, degree);

        strcpy(kst_time_str, "");
        if (qi->kst_time != 0){
            gmtime_r(&qi->kst_time, &utc);
            sprintf(kst_time_str, "%2d:%02d", utc.tm_hour, utc.tm_min);
        }

        ac_format(qi, acstart, acint, 1);


        c = g_strdup_printf("%-12s  %6s %4d %-7s  "
                            "%-4s  %5s  %s %s %s", 
                qi->call, qi->wwl, aband?qi->wkd[aband->bi]:0, qrb_str, 
                qtf_str, kst_time_str, acstart, acint, qi->text);
    }

    if (c && strlen(c)>sw->ho){
        //print_text(sw->x+1, sw->y+i, sw->w, c + sw->ho, color);
		clip_printf(sw, 1, i, color, "%s", c + sw->ho);
    }
	if (i == sw->h / 2){
		clip_printf(sw, 0, i, COL_NORM, "[");
		clip_printf(sw, 40, i, COL_NORM, "]");
	}
    g_free(c);
}


int sw_qrv_color(char *call){
	int *pval, ret;
    char *c;

	if (!call) return COL_NORM;

	pval = (int *)g_hash_table_lookup(gses->hicalls, call);
	if (!pval) return COL_NORM;

    switch (*pval){
		case -2:
        case -1:
            return COL_DARKGREY;
		case 0:
			return COL_NORM;
    }

	if (!aband) return COL_YELLOW;
	c = g_strdup(call);
	z_strip_from(c, '-');
    if (worked_on_all_rw(c)) 
	    ret = COL_DARKYELLOW;
	else
		ret = COL_YELLOW;
	g_free(c);
	return ret;
}

void sw_qrv_redraw(struct subwin *sw, struct band *band, int flags){
    struct qrv_item *qi;
    int i, y, len, color;
    char *c, raw[30];

   // if (!aband) return;
    
    //fill_area(sw->x, sw->y+sw->h/2, sw->w, 1, COL_INV);
    clip_printf(sw, -sw->ho +  2, 0,  qrv->sort==qrv_compare_call?COL_WHITE:COL_NORM, VTEXT(T_CALLSIGN3));
    clip_printf(sw, -sw->ho + 16, 0,  qrv->sort==qrv_compare_wwl?COL_WHITE:COL_NORM,  VTEXT(T_WWL3));
    clip_printf(sw, -sw->ho + 23, 0,  qrv->sort==qrv_compare_wkd?COL_WHITE:COL_NORM,   VTEXT(T_WKD2));
    clip_printf(sw, -sw->ho + 29, 0,  qrv->sort==qrv_compare_qrb?COL_WHITE:COL_NORM,  VTEXT(T_QRB));
    clip_printf(sw, -sw->ho + 36, 0,  qrv->sort==qrv_compare_qtf?COL_WHITE:COL_NORM,  VTEXT(T_QTF));
    clip_printf(sw, -sw->ho + 42, 0,  qrv->sort==qrv_compare_kst_time?COL_WHITE:COL_NORM, VTEXT(T_ACKST2));
    if (gacs){
        clip_printf(sw, -sw->ho + 50, 0,  qrv->sort==qrv_compare_ac_start?COL_WHITE:COL_NORM, VTEXT(T_START));
        clip_printf(sw, -sw->ho + 63, 0,  qrv->sort==qrv_compare_ac_int?COL_WHITE:COL_NORM, VTEXT(T_DUR));
        clip_printf(sw, -sw->ho + 67, 0,  qrv->sort==qrv_compare_ac_n?COL_WHITE:COL_NORM, VTEXT(T_NR));
    }
    clip_printf(sw, -sw->ho + 71, 0,  qrv->sort==qrv_compare_text?COL_WHITE:COL_NORM, VTEXT(T_TEXT2));

    len = strlen(qrv->search);
    if (len > 0){
        clip_printf(sw, 0, sw->h - 1, COL_NORM, "%s", qrv->search);
        if (gses->focused) set_cursor(sw->x + len,  sw->y + sw->h - 1, sw->x + len,  sw->y + sw->h - 1); 
    }else{
        int x = 0;
        c = VTEXT(T_16_SORT);
        clip_printf(sw, x, sw->h - 1, COL_NORM, c); 
        x += strlen(c);
        c = VTEXT(T_A_ALL);
        clip_printf(sw, x, sw->h - 1, qrv->showall?COL_WHITE:COL_NORM, c);
        x += strlen(c);
        c = VTEXT(T_ENTER_SPACE_EDIT);
        clip_printf(sw, x, sw->h - 1, COL_NORM, c);
        x += strlen(c);
    }

    if (!qrv->qrvs->len) return;
    sw_qrv_check_bounds(sw);
	

	qi = NULL;
	if (sw->cur >= 0 && sw->cur < qrv->qrvs->len) qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
//    dbg("%s: cur=%d qi=%p\n", __FUNCTION__, sw->cur, qi);
    if (!qi || (aband != NULL && qrv_skip(qi, aband->bi, 1))) {
        print_text(sw->x, sw->y + sw->h/2, sw->w, "~", COL_INV);
        return;
    }

	z_get_raw_call(raw, qi->call);
	color = sw_qrv_color(raw);
	/*if (color != COL_NORM) color |= COL_INV;
	else color = COL_INV;*/
    sw_qrv_draw_qrv(sw, sw->h/2, qi, color);
    
    y = sw->h / 2 - 1;
    for(i = sw->cur - 1; i >= 0; i--){
        if (y <= 0) break;
        qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
        if (aband != NULL && qrv_skip(qi, aband->bi, 1)) continue;
		z_get_raw_call(raw, qi->call);
		color = sw_qrv_color(raw);
        sw_qrv_draw_qrv(sw, y--, qi, color);
    } 
	for (; i >= 0; i--){
		struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
		//qi->ac_drawn = 0;
		qi->ac_start = (time_t)0;
	}

    y = sw->h / 2 + 1;
    for(i = sw->cur + 1; i < qrv->qrvs->len; i++){
        if (y >= sw->h - 1) break;
        qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
		if (aband != NULL && qrv_skip(qi, aband->bi, 1)) continue;
		z_get_raw_call(raw, qi->call);
		color = sw_kst_color(raw);
        sw_qrv_draw_qrv(sw, y++, qi, color);
    } 
	for (; i < qrv->qrvs->len; i++){// ((struct qrv_item *)z_ptr_array_index(qrv->qrvs, i))->ac_drawn = 0;
		struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
		//qi->ac_drawn = 0;
		qi->ac_start = (time_t)0;
	}

}

void sw_qrv_check_bounds(struct subwin *sw){
    int len;
    struct qrv_item *qi;

    if (!aband) return;

    len = qrv->qrvs->len;
    if (len == 0) {
		sw->cur = 0;
		return;
	}

    if (sw->cur < 0) {
		sw->cur = 0;
		sw_shake(sw, 1);
	}
    if (sw->cur >= qrv->qrvs->len) {
		sw->cur = qrv->qrvs->len - 1;;
		sw_shake(sw, 0);
	}

    while (sw->cur < qrv->qrvs->len){
        qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
		if (!(aband != NULL && qrv_skip(qi, aband->bi, 1))) break;
        sw->cur++; 
    }
    if (sw->cur >= qrv->qrvs->len) {
		sw->cur = qrv->qrvs->len - 1;
		//sw_shake(sw, 0);
	}
             
    while (sw->cur >= 0){
        qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, sw->cur);
		if (!(aband != NULL && qrv_skip(qi, aband->bi, 0))) break;
        sw->cur--; 
    }
    if (sw->cur < 0) {
		sw->cur = 0;
	}
}

void sw_qrv_raise(struct subwin *sw){
    sw_qrv_sort(qrv);
}

void sw_qrv_sort(struct qrvdb *qrvdb){
    z_ptr_array_qsort(qrv->qrvs, qrv->sort);
}
    
void qrv_compute_qrbqtf(struct qrv_item *q){
    double qtf, qtfrad;

    if (!q) return;
    if (!q->wwl || !*q->wwl){
        q->qrb = -1;
        q->qtf = -1;
        return;
    }
    /* CHANGE look at add_tmpqso_locator */    
    qrbqtf(ctest?ctest->pwwlo:cfg->pwwlo, q->wwl, &q->qrb, &qtf, NULL, 2);
    q->qtf = (int) (qtf+0.5);
    if (q->qrb < 0.1) {
        q->qrb = 0;
        q->qtf = 0;
    } 
    qtfrad=qtf*M_PI/180.0;
    q->kx = (int)(  q->qrb*sin(qtfrad));
    q->ky = (int)(- q->qrb*cos(qtfrad));

    if (q->qrb < 600){
        q->weight = q->qrb;
    }else{
        q->weight= 1800 - 2 * q->qrb;
        if (q->weight < 0) q->weight = 0;
    }

    if (aband){
        if (q->wkd[aband->bi] > 1) q->weight *= q->wkd[aband->bi];
        if (!(q->bands_qrv & (1 << aband->bi))) q->weight = 0;
    }
    /*dbg("compute_qrbqtf(%s) %s->%s qrb=%f kx=%d ky=%d\n", 
            q->call, ctest->pwwlo, q->wwl, q->qrb, q->kx, q->ky); */
}


void qrv_recalc_qrbqtf(struct qrvdb *qrvdb){
    int i;

    for (i = 0; i < qrv->qrvs->len; i++){
        struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
        qrv_compute_qrbqtf(qi);
    }
}

void qrv_recalc_gst(struct qrvdb *qrvdb){
    int i, j;
    int qtf2;
	double max;

    if (!qrvdb) return;
                    
    qrv->beamwidth = rot_beamwidth();// / 2;
    for (i=0;i<=180;i++){
        qrv->antchar[i]=cos(M_PI/180.0*(double)i*120.0/qrv->beamwidth);
       /* dbg("i=%3d %5.3f\n", i, antchar[i]);*/
        if (qrv->antchar[i]<=0) break;
    }
    for (;i<=180;i++) qrv->antchar[i]=0;
    
    for (i=0; i<360; i++) qrv->gst[i]=0.0;

    for (i = 0; i < qrv->qrvs->len; i++){
        struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
        if (qi->qtf<=0 || qi->qtf>=360) continue;
        
        if (aband && qrv_skip(qi, aband->bi, 0)) continue;
        
        for (j=0;j<=180;j++){
            qtf2 = qi->qtf + j;
            if (qrv->antchar[j] == 0) break;
            if (qtf2<0) qtf2 += 360;
            qrv->gst[qtf2%360] += qi->weight * qrv->antchar[j];
        }
        for (j=1;j<180;j++){
            qtf2 = qi->qtf - j;
            if (qrv->antchar[j] == 0) break;
            if (qtf2<0) qtf2 += 360;
            qrv->gst[qtf2%360] += qi->weight * qrv->antchar[j];
        }
    }
    max = 0;
    for (i=0; i<360; i++) if (qrv->gst[i]>max) max = qrv->gst[i];
    if (!max) return;
    for (i=0; i<360; i++) qrv->gst[i] = qrv->gst[i] / max; 
} 


void qrv_set_wkd(struct qrvdb *qrvdb, struct qso *qso){
    char raw[20];
    struct qrv_item *qi;

    if (!qso) return;
    if (!qso->band) return;
            
    z_get_raw_call(raw, qso->callsign);
    qi = (struct qrv_item *)g_hash_table_lookup(qrv->hash, raw);

    if (!qi) return;
    qi->bands_wkd |= 1 << qso->band->bi;
}

void qrv_recalc_wkd(struct qrvdb *qrvdb){
    struct qrv_item *qi;
    struct band *b;
    struct qso *qso;
    int i, j;
    char raw[20];


    for (i = 0; i < qrv->qrvs->len; i++){
        struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
        qi->bands_wkd = 0;
    }

    if (!ctest) return;
    for (i=0; i<ctest->bands->len; i++){
        b = (struct band *)z_ptr_array_index(ctest->bands, i);
        for (j=0; j<b->qsos->len; j++){
            qso = (struct qso *)z_ptr_array_index(b->qsos, j);
            if (qso->dupe) continue;
            if (qso->error) continue;
            if (qso->phase != ctest->phase) continue;
            z_get_raw_call(raw, qso->callsign);
            qi = (struct qrv_item *)g_hash_table_lookup(qrv->hash, raw);
            //dbg("recalc_qrv_wkd(%s) qi=%p\n", raw, qi);
            if (!qi) continue;
            qi->bands_wkd |= 1 << b->bi;
        }
    }


}

struct qrv_item *qrv_get(struct qrvdb *qrvdb, char *call){
    char raw[20];
	struct qrv_item *qi;

	if (call == NULL) return NULL;
	qi = (struct qrv_item *)g_hash_table_lookup(qrv->hash, call);
	if (qi) return qi;

    z_get_raw_call(raw, call);
    qi = (struct qrv_item *)g_hash_table_lookup(qrv->hash, raw);
    return qi;
}

void qrv_read_line(char *str){
	char *c1=NULL, *c2=NULL;
    struct qrv_item *qi;
   
	if (regmatch(str, "^[0-9]{4}Z ([^ ]+)", &c1, &c2, NULL)==0){
//		log_addf("KST '%s'", c2);
    
		if (strstr(str, "chat>") == NULL){
			qi = qrv_get(qrv, c2);
			if (qi){
				qi->kst_time = time(NULL);
			}
			redraw_later();
		}
    }

	if (c1) g_free(c1);
	if (c2) g_free(c2);
}


/* -------------- dialog edit qrv ------------------ */

char qrv_call[EQSO_LEN];
char qrv_wwl[EQSO_LEN];
char qrv_qrv[32];
char qrv_wkd[EQSO_LEN];
char qrv_text[MAX_STR_LEN];

void refresh_edit_qrv(void *arg){
    char *c;
    char raw[20];
    struct qrv_item *qi = (struct qrv_item*)arg;

    if (!qi){
        int wkd[32];
        memset(wkd, 0, sizeof(wkd));
        qi = qrv_add(z_str_uc(qrv_call), "", 0, wkd, "", (time_t)0);
    }else{
        z_get_raw_call(raw, qi->call);
        g_hash_table_remove(qrv->hash, qi->call);
        zg_free0(qi->call);
        qi->call = g_strdup(qrv_call);
        z_str_uc(qi->call); 
        z_get_raw_call(raw, qi->call);
        g_hash_table_insert(qrv->hash, g_strdup(raw), qi); 
    }

    zg_free0(qi->wwl);
    qi->wwl = g_strdup(qrv_wwl);
    z_str_uc(qi->wwl);

    qi->bands_qrv = 0;
    for (c = qrv_qrv; *c != '\0'; c++){
        *c = toupper(*c);
        if (*c < 'A' || *c > 'Z') continue;
        qi->bands_qrv |= (1 << (*c - 'A'));
    }

    qi->wkd[aband->bi] = atoi(qrv_wkd);
    
    zg_free0(qi->text);
    qi->text = g_strdup(qrv_text);

    qrv_compute_qrbqtf(qi);
    sw_qrv_sort(qrv);
}

void edit_qrv(void *arg){
    struct dialog *d;
    int i;
    struct qrv_item *qi;
    char x[2];


    if (!aband) return;
	qi = (struct qrv_item*)arg;
    if (qi){
        safe_strncpy0(qrv_call, qi->call, EQSO_LEN);
        safe_strncpy0(qrv_wwl, qi->wwl, EQSO_LEN);
        strcpy(qrv_qrv, "");
        x[1] = '\0';
        for (x[0] = 'A'; x[0] <= 'Z'; x[0]++){
            if (qi->bands_qrv & (1 << (x[0] - 'A'))) strcat(qrv_qrv, x);
        }
        g_snprintf(qrv_wkd, EQSO_LEN, "%d", qi->wkd[aband->bi]);
        safe_strncpy0(qrv_text, qi->text, MAX_STR_LEN);
    }else{
        strcpy(qrv_call, "");
        strcpy(qrv_wwl, "");
        strcpy(qrv_qrv, "");
        if (aband) sprintf(qrv_qrv, "%c", aband->bandchar);
        strcpy(qrv_wkd, "");
        strcpy(qrv_text, "@OP");
    }
    
    if (!(d = (struct dialog *)g_malloc(sizeof(struct dialog) + 20 * sizeof(struct dialog_item)))) return;
    memset(d, 0, sizeof(struct dialog) + 20 * sizeof(struct dialog_item));
    d->title = VTEXT(T_QRV); 
    d->fn = dlg_pf_fn;
    d->refresh = (void (*)(void *))refresh_edit_qrv;
    d->refresh_data = qi;
    d->y0 = 1;
    
    d->items[i=0].type = D_FIELD;
    d->items[i].dlen = EQSO_LEN;
    d->items[i].data = qrv_call;
    d->items[i].msg  = CTEXT(T_CALLSIGN2);
    d->items[i].wrap = 1;

    d->items[++i].type = D_FIELD;
    d->items[i].dlen = EQSO_LEN;
    d->items[i].data = qrv_wwl;
    d->items[i].msg  = CTEXT(T_WWL);
    d->items[i].wrap = 1;

    d->items[++i].type = D_FIELD;
    d->items[i].dlen = EQSO_LEN;
    d->items[i].data = qrv_qrv;
    d->items[i].msg  = CTEXT(T_QRV2);
    d->items[i].wrap = 1;

    d->items[++i].type = D_FIELD;
    d->items[i].dlen = EQSO_LEN;
    d->items[i].data = qrv_wkd;
    d->items[i].msg  = CTEXT(T_WKD);
    d->items[i].wrap = 1;

    d->items[++i].type = D_FIELD;
    d->items[i].dlen = MAX_STR_LEN;
    d->items[i].maxl = EQSO_LEN;
    d->items[i].data = qrv_text;
    d->items[i].msg  = CTEXT(T_REMARK);
    d->items[i].wrap = 2;


    d->items[++i].type = D_BUTTON;   
    d->items[i].gid = B_ENTER;
    d->items[i].fn = ok_dialog;
    d->items[i].text = VTEXT(T_OK);
    
    d->items[++i].type = D_BUTTON;
    d->items[i].gid = B_ESC;
    d->items[i].fn = cancel_dialog;
    d->items[i].text = VTEXT(T_CANCEL);
    d->items[i].align = AL_BUTTONS;
    d->items[i].wrap = 1;
    d->items[++i].type = D_END;
    do_dialog(d, getml(d, NULL));
    
}


int sw_qrv_load_file(struct subwin *sw, char *filename, int quiet){
    int j;
    FILE *f;
    GString *gs;
    char *val;
    gchar **items;
    int firstdate = -1;
    GHashTable *hash;
	int qrv_int = 0;
	int wkd[32];

//    if (!sdl) return;
	memset(wkd, 0, sizeof(wkd));
    
    f = fopen(filename, "rt");
    if (!f){
        if (!quiet) log_addf(VTEXT(T_CANT_READ_S), filename);
        return -1;
    }
    hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
    gs = g_string_sized_new(100);
    
    zfile_fgets(gs, f, 0);
    while(zfile_fgets(gs, f, 0) != NULL){
        if (gs->str[0]=='[') break;
        val = strchr(gs->str, '=');
        *val='\0';
        val++;
//        dbg("key='%s'\n", gs->str);
		if (strcasecmp(gs->str, "pband")==0){
            struct band *b = find_band_by_pband(val);
			if (b != NULL){
				qrv_int = 1 << (z_char_uc(b->bandchar) - 'A');
				wkd[z_char_uc(b->bandchar) - 'A'] = 1;
			}
        }
    }
    while(strncasecmp(gs->str, "[qsorecords", 11)!=0){
        if (zfile_fgets(gs, f, 0) == NULL) goto x;
    }
    while(zfile_fgets(gs, f, 0) != NULL){
        char *call, *wwl;

        if (gs->str[0]=='[') break;
//        dbg("qso='%s'\n", gs->str);
        items = g_strsplit(gs->str, ";", 0);
        for (j = 0; j <= 11; j++) if (!items[j]) goto badq;

		call = items[2];
		wwl = items[9];

		/*if (strcmp(call, "OK7RB") == 0) {
			int a  = 1;
		}*/

        if (firstdate < 0) firstdate = atoi(items[0]);
        if (!strcasecmp(call, "ERROR")) continue;

		qrv_add(call, wwl, qrv_int, wkd, "@edi", (time_t)0); 
badq:;
    }
    fclose(f);

x:;
    g_string_free(gs, TRUE);
    g_hash_table_destroy(hash);

    return 0;


}


static void qrv_import_edi(void *xxx, char *filename){
	char *fname;
	fname = g_strdup(filename);
	z_filename(fname);
	if (z_strcasestr(fname, "TXT")){
		zg_ptr_array_foreach(struct band *, b, ctest->bands){
			char *c, *path, *f;
			c = g_strdup(filename);
			path = z_dirname(c);
			f = g_strdup_printf("%s/%c.txt", path, z_char_lc(b->bandchar));
			z_wokna(f);
			sw_qrv_load_file(gses->ontop, f, 1);
			g_free(c);
			g_free(f);
		}
	}else{
		sw_qrv_load_file(gses->ontop, filename, 0);
	}
	g_free(fname);
    
    //sw_qrv_recalc_extremes(gses->ontop, aband);
}

void sw_qrv_choose(struct menu *menu){
	int i;
	char *filename;

	progress(VTEXT(T_LOAD_QRVS));
	for (i = 0; i < menu->ni; i++){
		struct menu_item *mi = menu->items + i;
		if (!mi->checked) continue;
		filename = g_strdup_printf("%s/%s/c.txt", logs_dir, (char *)mi->data);
		qrv_import_edi(NULL, filename);
		g_free(filename);
	}
    save_all_bands_txt(0);    	
	progress("NULL");
}

void menu_qrv_add_contest(void *menudata, void *itdata){
	contest_choose(sw_qrv_choose);
}

