/*
    kst - ON4KST chat
    Copyright (C) 2011-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 "kst.h"

#include "cwdb.h"
#include "dwdb.h"
#include "fifo.h"
#include "kbd.h"
#include "kbdbind.h"
#include "main.h"
#include "map.h"
#include "namedb.h"
#include "inputln.h"
#include "qrvdb.h"
#include "qsodb.h"
#include "tsdl.h"
#include "session.h"
#include "stats.h"
#include "subwin.h"
#include "tregex.h"
#include "zosk.h"

char *kst_servers[] = {"www.on4kst.info", 
                       "on4kst.dyndns.org", 
					   "141.94.171.16", // www.on4kst.info 22.11.2021
					   //"188.165.198.144", // www.on4kst.info 11.3.2015
                       NULL};
int kst_i = 0;

int sw_kst_kbd_func(struct subwin *sw, struct event *ev, int fw){
    if (zselect_profiling(zsel)) zselect_hint(zsel, __FUNCTION__);

    //dbg("sw_kst_kbd_func [%d,%d,%d,%d]\n",ev->ev,ev->x,ev->y,ev->b);
    

    if (sw->il && (
        sw->il->wasctrlv ||    
        (ev->x!='[' && ev->x!=']') )){
            int ret;

            ret = inputln_func(sw->il, ev);

            if (ev->x!=KBD_LEFT &&
                ev->x!=KBD_RIGHT &&
                ev->x!=KBD_HOME &&
                ev->x!=KBD_END){

                
                if (strlen(sw->il->cdata) < 1){
                    zg_free0(sw->pattern);
                }else{
                    char *c;

                    c = strchr(sw->il->cdata, ' ');
                    if (c){
                        zg_free0(sw->pattern);
                    }else{
                        zg_free0(sw->pattern);
                        sw->pattern = g_strdup(sw->il->cdata);
                    }
                    sw->offset = 0;
                }
                redraw_later();
                if (ret) return ret;
            }
            //dbg("sw->pattern('%s')\n", sw->pattern);
        
    }else{
        //dbg("sw->pattern('%s')\n", sw->pattern);
    }

    if (ev->y & KBD_SHIFT){
        switch (ev->x){
            case KBD_UP:
                sw->kst_offset -= 1;
                if (sw->kst_offset < 0) sw->kst_offset = 0;
                redraw_later();
                return 1;
            case KBD_DOWN:
                if (sw->side_bott) return 1;
                sw->kst_offset += 1;
                redraw_later();
                return 1;
            case KBD_PGUP:
                sw->kst_offset -= sw->h / 2;
                if (sw->kst_offset < 0) sw->kst_offset = 0;
                redraw_later();
                return 1;
            case KBD_PGDN:
                sw->kst_offset += sw->h / 2;
                redraw_later();
                return 1;
            case KBD_HOME:
                sw->kst_offset = 0;
                redraw_later();
                return 1;

        }
    }

    switch(kbd_action(KM_MAIN,ev)){
        case ACT_ESC:
            return 0;
            break;
        case ACT_DOWN:
            sw->offset--;
            sw->check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_UP:
            sw->offset++;
            sw->check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_PAGE_DOWN:
            sw->offset -= sw->h - 1;;
            sw->check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_PAGE_UP:
            sw->offset += sw->h - 1;;
            sw->check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_HOME:
            sw->offset = sw->lines->len - sw->h; 
            sw->check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_END:
            sw->offset = 0;
            sw->check_bounds(sw);
            redraw_later();
            return 1;
        case ACT_SCROLL_LEFT:
            if (sw->lines)
                if (sw->ho>0) sw->ho--;
            redraw_later();
            return 1;
        case ACT_SCROLL_RIGHT:
            if (sw->lines)
                sw->ho++;
            redraw_later();
    }
    return 0;
}

void kst_qrv_sort(struct subwin *sw, struct event *ev){
    if (ev->x >= sw->w + 31){
        qrv_sort(qrv_compare_ac_n);
    }else if (ev->x >= sw->w + 27){
        qrv_sort(qrv_compare_ac_int);
    }else if (ev->x >= sw->w + 21){
        qrv_sort(qrv_compare_ac_start);
    }else if (ev->x >= sw->w + 16){
        if (qrv->sort == qrv_compare_qtf || qrv->sort == qrv_compare_kst_time || qrv->sort == qrv_compare_wwl)
            qrv_sort(qrv->sort);
        else
            qrv_sort(qrv_compare_qrb);  
    }else if (ev->x >= sw->w + 10){
        qrv_sort(qrv_compare_wkd);  
    }else if (ev->x >= sw->w + 2){
        qrv_sort(qrv_compare_call); 
    }
}

int sw_kst_mouse_func(struct subwin *sw, struct event *ev, int fw){
    //struct menu_item *mi = NULL;
    int /*y, items = 0, */size;
	struct timeval tv;
	double ts;
    
    if (zselect_profiling(zsel)) zselect_hint(zsel, __FUNCTION__);

#ifdef Z_HAVE_SDL
    if (sw->screen && ev->x < sw->w){
        //dbg("resending to map: %d x=%d y=%d\n", ev->b, ev->x, ev->y);
        return sw_map_mouse_func(sw, ev, fw);
    }
#endif

    if (ev->b & B_DRAG){
        int dy = sw_accel_dy(sw, ev);
        if (dy == 0) return 1;

        if (ev->x > sw->w){
            sw->kst_offset -= dy;
            size = g_hash_table_size(sw->kstusers);
            //if (size > sw->h - 1)

            if (sw->kst_offset < 0) sw->kst_offset = 0;
            if (sw->kst_offset > size - 3) sw->kst_offset = size - 3;
            redraw_later();
            return 1;
        }

        sw->offset += dy;
        sw->check_bounds(sw);
        redraw_later();

        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;
            return 1;
        case B_LEFT | B_CLICK:
            if (!sw->il) break;
            if (ev->y==sw->y+sw->h){
                inputln_func(sw->il, ev);
                return 1;
            }
            if (ev->x > sw->w && ev->y == sw->y){ // QRV title
                kst_qrv_sort(sw, ev);
                return 1;
            }
            g_free(sw->callunder);
            sw->callunder = sw_kst_call_under(sw, ev->x - sw->x + sw->ho, ev->y - sw->y);
            if (!sw->callunder) break;
            sw_kst_toggle_highlight(sw, sw->callunder, 1);
            return 1;
        case B_MIDDLE | B_CLICK:
            dbg("middle click");
            g_free(sw->callunder);
            sw->callunder = sw_kst_call_under(sw, ev->x - sw->x + sw->ho, ev->y - sw->y);
            if (!sw->callunder) break;
            sw_kst_ask_sked(GINT_TO_POINTER(2), sw);
            break;
        case B_RIGHT | B_CLICK:
            if (ev->x > sw->w && ev->y == sw->y){ // QRV title
                struct menu_item *mi = new_menu(3);
                mi->rtext = "Show";
                add_to_menu(&mi, g_strdup(VTEXT(T_QRB)), "", CTEXT(T_HK_QRB), MENU_FUNC qrv_sort, qrv_compare_qrb, 0);
                add_to_menu(&mi, g_strdup(VTEXT(T_QTF)), "", CTEXT(T_HK_QTF), MENU_FUNC qrv_sort, qrv_compare_qtf, 0);
                add_to_menu(&mi, g_strdup(VTEXT(T_ACKST)), "", CTEXT(T_HK_ACKST), MENU_FUNC qrv_sort, qrv_compare_kst_time, 0);
				add_to_menu(&mi, g_strdup(VTEXT(T_WWL3)), "", TRANSLATE("W"), MENU_FUNC qrv_sort, qrv_compare_wwl, 0);
                set_window_ptr(gses->win, ev->x - 3, ev->y);
                do_menu(mi, NULL);
                if (cfg->usetouch) kst_qrv_sort(sw, ev);
                return 1;
            }
            if (ev->y==sw->y+sw->h){
                inputln_func(sw->il, ev);
                return 1;
            }
			sw->callunderx = ev->x;
			sw->callundery = ev->y;
			gettimeofday(&tv, NULL);
			ts = z_difftimeval_double(&tv, &sw->kst_latest_data);
			if (ts < 1.0 && ev->x < sw->w && sw->pattern == NULL){
				// moved
				g_free(sw->callunder);
				sw->callunder = NULL;
				zg_ptr_array_free_all(sw->callsunder);
				sw->callsunder = sw_kst_calls_under(sw, Z_MIN(sw->h, 6), ev->y - sw->y);
				struct menu_item *mi = new_menu(3);
				mi->rtext = "Choose call";
				int i;
				for (i = 0; i < sw->callsunder->len && i < sw->h; i++){
					char *c = (char*)g_ptr_array_index(sw->callsunder, i);
					add_to_menu(&mi, g_strdup(c), "", "", sw_kst_call_choosen, GINT_TO_POINTER(i), 0);
				}
				set_window_ptr(gses->win, ev->x - 8, ev->y - 3);
				do_menu(mi, sw);
			}
			else{
				// steady
				g_free(sw->callunder);
				sw->callunder = sw_kst_call_under(sw, ev->x - sw->x + sw->ho, ev->y - sw->y);

				if (!sw->callunder) break;
				kst_right_mouse_2(sw);
			}
            break;
        case B_WHUP:
/*            dbg("wheel up\n");*/
            if (ev->x > sw->w){
                sw->kst_offset -= 3;
                if (sw->kst_offset < 0) sw->kst_offset = 0;
                //dbg("kst_offset=%d\n", sw->kst_offset);
                redraw_later();
                return 1;
            }
            sw->offset+=3;
            sw->check_bounds(sw);
            redraw_later();
            return 1;
        case B_WHDOWN:
/*            dbg("wheel down\n");  */
            if (ev->x > sw->w){
                sw->kst_offset += 3;
                size = g_hash_table_size(sw->kstusers);
                if (sw->kst_offset > size - 3) sw->kst_offset = size - 3;
                redraw_later();
                return 1;
            }
            sw->offset-=3;
            sw->check_bounds(sw);
            redraw_later();
            return 1;
    }
    return 0;
}


void sw_kst_call_choosen(void *itdata, void *menudata){
	struct subwin *sw = (struct subwin *)menudata;
	char *call = (char *)g_ptr_array_index(sw->callsunder, GPOINTER_TO_INT(itdata));
	g_free(sw->callunder);
	sw->callunder = g_strdup(call);
	kst_right_mouse_2(sw);
}


void kst_right_mouse_2(struct subwin *sw){
	int items = 0;
	struct menu_item *mi = new_menu(1);

	

	mi->rtext = sw->callunder;
	if (!cfg->usetouch){
		add_to_menu(&mi, CTEXT(T_MISTAKE), "", CTEXT(T_HK_MISTAKE), sw_kst_nothing, NULL, 0); items++;
	}
	add_to_menu(&mi, CTEXT(T_ASK_FOR_SKED), "", CTEXT(T_HK_ASK_FOR_SKED), sw_kst_ask_sked, GINT_TO_POINTER(1), 0); items++;
	add_to_menu(&mi, CTEXT(T_ASK_FOR_SKED_B), "", CTEXT(T_HK_ASK_FOR_SKED_B), sw_kst_ask_sked, GINT_TO_POINTER(2), 0); items++;
	add_to_menu(&mi, TRANSLATE("Sked - ant is to you"), "", "T", sw_kst_ask_sked, GINT_TO_POINTER(3), 0); items++;
	add_to_menu(&mi, CTEXT(T_SELECT), "", CTEXT(T_HK_SELECT), sw_kst_select, NULL, 0); items++;
	add_to_menu(&mi, CTEXT(T_HIDE), "", CTEXT(T_HK_HIDE), sw_kst_hide, NULL, 0); items++;
	add_to_menu(&mi, CTEXT(T_MESSAGE), "", CTEXT(T_HK_MESSAGE), sw_kst_message, NULL, 0); items++;
	add_to_menu(&mi, CTEXT(T_INFO), "", CTEXT(T_HK_INFO), sw_kst_info, NULL, 0); items++;
#ifdef Z_HAVE_SDL
	if (gacs) { add_to_menu(&mi, CTEXT(T_AC_INFO), "", CTEXT(T_HK_AC_INFO), sw_kst_ac_info, NULL, 0); items++; }
#endif
	if (aband) {
		add_to_menu(&mi, CTEXT(T_USE), "", CTEXT(T_HK_USE), sw_kst_use, NULL, 0);
		items++;
	}
	int y = sw->callundery - 1;
	if (cfg->usetouch) y = sw->callundery - items - 1;
	set_window_ptr(gses->win, sw->callunderx - 3, y);
	do_menu(mi, sw);


}


static void sw_kst_draw(gpointer key, gpointer value, gpointer user_data){
    //char raw[30];
    char *call = (char*)key;
    struct subwin *sw = (struct subwin *)user_data;
    struct qrv_item *qi = (struct qrv_item*)value;
    int color;
    char degree=' ';
    char kst_time_str[200];
    struct tm utc;
    char acstart[30];
    char acint[30];
#if defined(Z_HAVE_SDL) && !defined(Z_ANDROID)     
    if (sdl) degree=0xb0;
#endif

    if (sw->kst_y >= sw->hh) return;
    
    //z_get_raw_call(raw, (char*)call);
    color = sw_kst_color(call);

    if (qi->new_on_kst) {
        color |= COL(1 * 8); // new call red background
        clip_printf(sw, sw->w + 1, sw->kst_y, color, "                     ");
    }


    clip_printf(sw, sw->w + 2, sw->kst_y, color, "%s", call);
    if (qi && aband) clip_printf(sw, sw->w + 10, sw->kst_y, color, "%4d", qi->wkd[aband->bi]);
    if (qrv->sort == qrv_compare_qtf){
        if (qi) clip_printf(sw, sw->w + 14, sw->kst_y, color, "%4d%c", qi->qtf, degree);
    }else if (qrv->sort == qrv_compare_kst_time){
        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);
        }
        if (qi) clip_printf(sw, sw->w + 14, sw->kst_y, color, "%6s", kst_time_str);
    }else if (qrv->sort == qrv_compare_wwl){	
		if (qi){
			clip_printf(sw, sw->w + 15, sw->kst_y, color , "%6s", qi->wwl);
			if (aband && ctest->wwlmult > 0){
				char wwl4[5];
				g_strlcpy(wwl4, qi->wwl, 5);
			
				if (g_hash_table_lookup(aband->stats->wwls, wwl4) == NULL){
					color &= 0xfffff2ff;   // not worked WWL
				}
				clip_printf(sw, sw->w + 15, sw->kst_y, color , "%4s", wwl4);
			}
		}
    }else{
        if (qi && qi->qrb > 0) clip_printf(sw, sw->w + 14, sw->kst_y, color, "%5.0fkm", qi->qrb);
    }
    ac_format(qi, acstart, acint, 0);
    clip_printf(sw, sw->w + 22, sw->kst_y, color, "%s %s", acstart, acint);
    sw->kst_y++;
}

void sw_kst_array_add(gpointer key, gpointer value, gpointer data){
    g_ptr_array_add((GPtrArray *)data, key);
}


void sw_kst_redraw(struct subwin *sw, struct band *band, int flags){
    int i, index, col;
    gchar *line, *pcall, *ccall;
	gchar rawpcall[20];
    int ccol;
    int ofs = 0;

    sw->side_top = sw->side_bott = 0;
    //dbg("sw->il = %p\n", sw->il);
    if (ctest){
        pcall = ctest->pcall;
    }else{
        pcall = cfg->pcall;
    }
	z_get_raw_call(rawpcall, pcall);

    //dbg("sw_kst_redraw: offset=%d pattern='%s'\n", sw->offset, sw->pattern);
    if (sw->pattern){
        index = sw->lines->len;          
        ofs = sw->offset;
    }else{
        index = sw->lines->len - sw->offset;
    }

    for (i = sw->h - 1; i >= 0; i--){
        if (sw->pattern){
            for (index--; index >= 0; index--){
                char *x;
                if (index < 0 || index >= sw->lines->len)
                   line = "~"; 
                else
                   line = (char *)g_ptr_array_index(sw->lines, index);
                x = z_strcasestr(line, sw->pattern);
                //dbg("i=%d index=%d x=%p line=%s\n", i, index, x, line);
                if (x != 0) {
                    if (ofs > 0) 
                        ofs--;
                    else
                        goto found;
                }

            }
            continue;
        }else{
            index--; 
            if (index < 0 || index >= sw->lines->len)
               line = "~"; 
            else
               line = (char *)g_ptr_array_index(sw->lines, index);
        }
found:;
        if (!line || strlen(line) <= sw->ho) continue;
        
        col = sw_kst_line_color(sw, line);
        ccall = sw_kst_counterp(sw, line, &ccol);
        if (sw->pattern){
            set_color(sw->x - 1, sw->y + i, COL_DARKCYAN * 8 | COL_NORM);
            //print_text(sw->x, sw->y + i, 1, " ", COL(6 * 8));
            print_text(sw->x, sw->y+i, sw->w, line + sw->ho, col);
            if (ccall) sw_highlight(sw, line, ccall, ccol, i, 1);
            sw_highlight(sw, line, sw->pattern, COL_RED, i, 0);
        }else{
            print_text(sw->x, sw->y+i, sw->w, line + sw->ho, col);
            if (ccall) sw_highlight(sw, line, ccall, ccol, i, 1);

            if ((col != COL_DARKGREY && col != COL_DARKYELLOW) || 
                (ccall != NULL && ccol != COL_DARKGREY && ccol != COL_DARKYELLOW)){
                sw_highlight(sw, line, pcall, COL_RED, i, 1);
                sw_highlight(sw, line, rawpcall, COL_RED, i, 1);
				if (cfg->kst_user && *cfg->kst_user) sw_highlight(sw, line, cfg->kst_user, COL_RED, i, 1);
                sw_highlight(sw, line, "73", COL_RED, i, 1);
                sw_highlight(sw, line, "thanks", COL_RED, i, 1);
                sw_highlight(sw, line, "tnx", COL_RED, i, 1);
                sw_highlight(sw, line, "tks", COL_RED, i, 1);
                sw_highlight(sw, line, "later", COL_RED, i, 1);
                sw_highlight(sw, line, "nil", COL_RED, i, 1);
                sw_highlight(sw, line, "any1", COL_RED, i, 1);
                sw_highlight(sw, line, "anyone", COL_RED, i, 1);
                sw_highlight(sw, line, "scp", COL_RED, i, 1);
            }
        }
    }
    //if (aband){
    ofs = sw->kst_offset;
    clip_printf(sw, sw->w + 2, 0, qrv->sort==qrv_compare_call?COL_WHITE:COL_NORM, "CALL");
    clip_printf(sw, sw->w + 10, 0, qrv->sort==qrv_compare_wkd?COL_WHITE:COL_NORM, "WKD");
    if (qrv->sort == qrv_compare_qtf){
        clip_printf(sw, sw->w + 16, 0, COL_WHITE, "QTF");
    }else if (qrv->sort == qrv_compare_kst_time){
        clip_printf(sw, sw->w + 16, 0, COL_WHITE, "AcKST");
    }else if (qrv->sort == qrv_compare_wwl){
        clip_printf(sw, sw->w + 16, 0, COL_WHITE, "WWL");
    }else{
        clip_printf(sw, sw->w + 16, 0, qrv->sort==qrv_compare_qrb?COL_WHITE:COL_NORM, "QRB");
    }
    if (gacs){
        clip_printf(sw, sw->w + 21, 0, qrv->sort==qrv_compare_ac_start?COL_WHITE:COL_NORM, "Start");
        clip_printf(sw, sw->w + 27, 0, qrv->sort==qrv_compare_ac_int?COL_WHITE:COL_NORM, "Dur");
        clip_printf(sw, sw->w + 31, 0, qrv->sort==qrv_compare_ac_n?COL_WHITE:COL_NORM, "NR");
    }

    for (sw->kst_y = 1, i = 0; 
            sw->kst_y < sw->hh && i < qrv->qrvs->len;
        )
    {
        struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i++);
        if (aband && qrv_skip(qi, aband->bi, 1)) continue;
        if (kst_user_skip(sw, qi->call)) continue;
        if (kst_qrb_skip(qi, qi->call)) continue;
		if (kst_wkdwwl_skip(qi)) continue;

        if (ofs) {
            ofs--;
            continue;
        }
        sw_kst_draw(qi->call, qi, sw);  //sw->kst_y++ is inside
    }
    if (i == qrv->qrvs->len) sw->side_bott = 1;
    for (; i < qrv->qrvs->len; 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;
    }
    
#ifdef Z_HAVE_SDL
    if (sw->screen != NULL && gacs != NULL){
        //fill_area(sw->x, sw->y, sw->w, sw->h, 0);
        //map_recalc_gst(sw, band);
        //SDL_SetClipRect(sw->screen, &sw->map);
        sw_map_redraw(sw, aband, 0);
        SDL_SetClipRect(sw->screen, &sw->map);
        plot_qso(sw, sw->screen, band, &gacs->infolocqso);
        plot_path(sw, sw->screen, band, &gacs->infolocqso, &gacs->infoqso);
        return;
    }
#endif
    //clip_printf(sw, 0, 0, COL_INV, "shus=%d  timer=%d    ", sw->shus, zselect_timer_get(zsel, sw->timer_id) / 1000);

}

void sw_kst_check_bounds(struct subwin *sw){
    
    if (sw->offset < 0) {
        sw->offset=0;
        sw_shake(sw, 0);
    }
    if (sw->offset > sw->lines->len - 1) {
        sw->offset = sw->lines->len - 1;
        if (sw->offset >= 0) sw_shake(sw, 1);
    }
    if (sw->offset < 0) {
        sw->offset=0;
        sw_shake(sw, 0);
    }
}

void sw_kst_enter(void *enterdata, gchar *str, int cq){
    struct subwin *sw;

    sw = (struct subwin *)enterdata;

    //log_addf("sw_kst_enter");
    //kst_end_shus(sw);

    if (regcmp(str, "^[0-9]{2,7}$") == 0){
        if (aband) {
            process_input(aband, str, 0);
            sw_unset_focus();
            il_set_focus(INPUTLN(aband));
            sw_printf(sw, VTEXT(T_NO_SEND_NUMS));
            return;
        }
    }

    //dbg("sw_kst_enter('%s', %d)\n", str, sw->sock);
    if (sw->sock < 0){
        if (strcmp(str, "") == 0){
            kst_open_connection(sw, NULL);
            return;
        }
        sw_printf(sw, VTEXT(T_DISCONNECTED), str); 
        return;
    }else{
        if (strcmp(str, "\x03") == 0){
            sw_kst_disconnect(sw);
            return;
        }
		zbinbuf_append(sw->wrbuf, str);
		zbinbuf_append(sw->wrbuf, "\r\n");
		zselect_set_write(zsel, sw->sock, sw_kst_write_handler, sw);

        if (strcasecmp(str, "/sh us") == 0) kst_shus(sw, 2);
    }
}

void sw_kst_addrinfo(struct zasyncdns *adns, int n, int *family, int *socktype, int *protocol, int *addrlen, union zsockaddr *addr, char *errorstr){
    struct subwin *sw;
    int i, port = 23000, ret;
    GString *gs;
    char errbuf[100];

    sw = (struct subwin *)adns->arg;
    if (errorstr != NULL){
        sw_printf(sw, VTEXT(T_CANT_RESOLVE), kst_servers[kst_i], errorstr);
        kst_i++;
        if (kst_servers[kst_i] != NULL){
            zselect_bh_new(zsel, kst_open_bh, sw); 
            return;
        }
        kst_i = 0;
        sw_printf(sw, VTEXT(T_ENTER_TO_CONNECT_KST));
        return;
    }
    kst_i = 0;

    gs = g_string_new(VTEXT(T_RESOLVED));
    for (i = 0; i < n; i++){
        z_sock_ntoa(gs, family[i], addr + i); 
        g_string_append_c(gs, ' ');
    }
    g_string_append(gs, "***\n"); 
    sw_add_block(sw, gs->str);
    g_string_free(gs, TRUE);     

    for (i = 0; i < n; i++){
        //dbg("socket(%d, %d, %d)\n", family[i], socktype[i], protocol[i]);
        //sw->sock = socket(family[i], socktype[i], protocol[i]);
        sw->sock = socket(family[i], SOCK_STREAM, 0);
        int err = z_sock_errno;
        if (sw->sock < 0) {
            sw_printf(sw, VTEXT(T_CANT_CREATE_SOCKET), z_sock_strerror());
            continue;
        }
        if (z_sock_nonblock(sw->sock, 1)) {
            closesocket(sw->sock);
			sw->sock = -1;
            continue;
        }

        switch (family[i]){
            case AF_INET:
                ((struct sockaddr_in *)(addr + i))->sin_port = htons(port);
                break;
#ifdef AF_INET6
            case AF_INET6:
                ((struct sockaddr_in6 *)(addr + i))->sin6_port = htons(port);
                break;
#endif
            default:   
                closesocket(sw->sock);
				sw->sock = -1;
                continue;       // unsupported protocol family
        }
        ret = connect(sw->sock, (struct sockaddr *)(addr + i), addrlen[i]);
        if (ret < 0){
            int err = z_sock_errno;
            GString *gs2 = g_string_new("");
            if (z_sock_wouldblock(err)){
                sw_printf(sw, VTEXT(T_CONNECTING_TO), z_sock_ntoa(gs2, family[i], addr + i));
                zselect_set(zsel, sw->sock, NULL, sw_kst_connected_handler, NULL, sw); 
            }else{
                sw_printf(sw, VTEXT(T_CANT_CONNECT_TO), z_sock_ntoa(gs2, family[i], addr + i), z_sock_strerror());
                closesocket(sw->sock);
				sw->sock = -1;
				dbg("sw_kst_addrinfo -1\n");
            }
            g_string_free(gs2, TRUE);
        }
        else{
            sw_kst_connected_handler(sw);
        }
        break;
    }
}

void sw_kst_connected_handler(void *xxx){
    struct subwin *sw = (struct subwin *)xxx;

    if (z_sock_error(sw->sock)){
        sw_kst_disconnect(sw);
        return;
    }

    sw_printf(sw, VTEXT(T_CONNECTED_SOCKET), sw->sock);
    zselect_set(zsel, sw->sock, sw_kst_read_handler, NULL, NULL, sw); 
    sw->chat = 1;
}

void sw_kst_read_handler(void *xxx){
    char buf[1030], *d, *last, errbuf[100];
    int i, ret;
    struct subwin *sw = (struct subwin *)xxx;

    ret = recv(sw->sock, buf, 1024, 0);
    int err = z_sock_errno;
    if (ret <= 0){
        if (z_sock_errno != 0){ // zero is handled by sw_kst_disconnect()
            sw_printf(sw, VTEXT(T_ERROR_READING_SOCKET), z_sock_strerror());
        }
        sw_kst_disconnect(sw);
        return;
    }

    for (i = 0, d = buf; i < ret; i++){
        if (buf[i] == '\0') continue;
        if (buf[i] == '\r') continue;
        if (buf[i] == '\xff'){
            if (i + 2 < ret){
                if ((buf[i+1] & 0xf0) == 0xf0){
                    i+=2;
                    continue; // i++
                }
            }
        }
        *d = buf[i];
        if ((unsigned char)*d == 0xd8) *d = '0'; // DL0HTW 
        d++;
    }
    *d = '\0';
    buf[ret] = '\0';

    sw_add_block(sw, buf);
    if (!sw->ontop && !sw->unread){
        sw->unread = 1;
        redraw_later();
    }
	gettimeofday(&sw->kst_latest_data, NULL);

    if (!sw->chat) return;
    if (sw->lines->len <= 0) return;
    last = (char *)g_ptr_array_index(sw->lines, sw->lines->len - 1);
    if (last == NULL) return;
    if (!regcmpi(last, "^login:")){
        if (cfg->kst_user && *cfg->kst_user){
            sw_printf(sw,           "%s\r\n", cfg->kst_user);
			zbinbuf_sprintfa(sw->wrbuf, "%s\r\n", cfg->kst_user);
			zselect_set_write(zsel, sw->sock, sw_kst_write_handler, sw);
        }
    }
    if (!regcmpi(last, "^password:")){
        if (cfg->kst_pass && *cfg->kst_pass){
            sw_printf(sw, "********\r\n");
			zbinbuf_sprintfa(sw->wrbuf, "%s\r\n", cfg->kst_pass);
			zselect_set_write(zsel, sw->sock, sw_kst_write_handler, sw);
        }
    } 
    if (!regcmpi(last, "^Your\\ choice.*:")){
        int channel = 0; // 1=50/70, 2=144/432, 3=uW, 5=HF
        if (ctest){
            int i;
            for (i = 0; i < ctest->bands->len; i++){
                int ch;
                struct band *b = (struct band*)g_ptr_array_index(ctest->bands, i);
                if (b->readonly) continue;
                if (b->qrg_min >= 500000) ch = 3;
                if (b->qrg_min < 500000) ch = 2;
                if (b->qrg_min < 100000) ch = 1;
                if (b->qrg_min < 30000) ch = 5;
                if (channel == 0) channel = ch;
                if (ch != channel) {
                    channel = 0;
                    break;
                }
            }
        }
        if (channel > 0){
            sw_printf(sw,           "%d\r\n", channel);
			zbinbuf_sprintfa(sw->wrbuf, "%d\r\n", channel);
			zselect_set_write(zsel, sw->sock, sw_kst_write_handler, sw);
        }
    }
    if (!regcmpi(last, ">$")){
        if (cfg->kst_name && *cfg->kst_name){
            sw_printf(sw,           "/set name %s\r\n", cfg->kst_name);
			zbinbuf_sprintfa(sw->wrbuf, "/set name %s\r\n", cfg->kst_name);
			zselect_set_write(zsel, sw->sock, sw_kst_write_handler, sw);
        }
        if (ctest != NULL){
            sw_printf(sw,           "/set qra %s\r\n", ctest->pwwlo);
			zbinbuf_sprintfa(sw->wrbuf, "/set qra %s\r\n", ctest->pwwlo);
			zselect_set_write(zsel, sw->sock, sw_kst_write_handler, sw);
		}
        sw->chat = 0;
        if (sw->timer_id > 0) zselect_timer_kill(zsel, sw->timer_id);
        sw->timer_id = zselect_timer_new(zsel, 1 * 1000, kst_timer_function, sw);
    }

}


void sw_kst_disconnect(struct subwin *sw){
    if (sw->sock < 0) return;

    sw_printf(sw, VTEXT(T_DISCONNECTED));
    zselect_set(zsel, sw->sock, NULL, NULL, NULL, NULL); 
    closesocket(sw->sock);
    dbg("sw_kst_disconnect -1\n");
    sw->sock = -1;
    
    kst_clear_users(sw);

    sw_printf(sw, VTEXT(T_ENTER_TO_CONNECT_KST));
}

#define FREE_SW_CX if (c1) {g_free(c1); c1=NULL;} if (c2) {g_free(c2); c2=NULL;}

char *sw_kst_call_under(struct subwin *sw, int x, int y){
    int index;
    int ret, len, i1, i2;
    char *c1 = NULL, *c2 = NULL, *s, call[25];

    //dbg("sw_kst_call_under(sw, %d, %d)\n", x, y);
    if (x >= sw->w){
        int yy, i, ofs;
        //dbg("x>sw->w\n");
        ofs = sw->kst_offset;
        for (yy = 1, i = 0; yy < sw->hh && i < qrv->qrvs->len;)
        {
            struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i++);
            if (aband != NULL && qrv_skip(qi, aband->bi, 0)) continue;
            if (kst_user_skip(sw, qi->call)) continue;
            if (kst_qrb_skip(qi, qi->call)) continue;
			if (kst_wkdwwl_skip(qi)) continue;
			if (ofs) {
                ofs--;
                continue;
            }
            if (y == yy){
                //dbg("found i=%d y=%d call=%s\n", i, y, qi->call);
                return g_strdup(qi->call);
            }
            yy++;
            //sw_kst_draw(qi->call, qi, sw);  //sw->kst_y++ is inside
        }
    }

    if (sw->pattern){
        int yy = sw->h - 1;
        int ofs = sw->offset;
        if (sw->pattern)
            index = sw->lines->len;
        else
            index = sw->lines->len - sw->offset;

        for (index--; 1; index--){
            char *x, *line;
            if (index < 0) return NULL;
            line = (char *)g_ptr_array_index(sw->lines, index);
            x = z_strcasestr(line, sw->pattern);
            if (!x) continue;
            if (yy == y) {
                if (ofs > 0){
                    ofs--;
                    continue;
                }else{
                    break;
                }
            }
            yy--;
            if (!yy) return NULL;
        }
        //x--;
    }else{
        index = sw->lines->len - sw->offset - sw->h + y;
        if (index < 0 || index >= sw->lines->len) return NULL;
    }
    s = (char*)g_ptr_array_index(sw->lines, index);
    if (x < 0 || x >= strlen(s)) return NULL;
    //dbg("line='%s'\n", s);

    s = g_strdup(s);
    z_str_uc(s);

    for (i1 = x; i1 >= 0; i1--){
        if (s[i1] >= 'A' && s[i1] <= 'Z') continue;
        if (s[i1] >= '0' && s[i1] <= '9') continue;
        if (s[i1] == '-') continue;
        if (s[i1] == '/') continue;
        break;
    }
    i1++; // line cannot begin with call

    len = strlen(s);
    for (i2 = x; i2 < len; i2 ++){
        if (s[i2] >= 'A' && s[i2] <= 'Z') continue;
        if (s[i2] >= '0' && s[i2] <= '9') continue;
        if (s[i2] == '-') continue;
        if (s[i2] == '/') continue;
        break;
    }
    if (i2 > i1 && i2 - i1 < 20){
        g_strlcpy(call, s + i1, Z_MIN(i2 - i1 + 1, 20));
        if (z_can_be_call(call, ZCBC_SLASH | ZCBC_MINUS)){
            g_free(s);
            return g_strdup(call);
        }
    }


    /**** ON4KST ****/
    /* 1808Z OK1KRQ/P Club> GE all */
    c1 = NULL;
    c2 = NULL;
    ret = regmatch(s, "^[0-9]{4}Z ([0-9A-Z\\/-]+).*>", &c1, &c2, NULL);
    if (ret == 0){
        safe_strncpy0(call, c2, 19);
        goto doit;
    }
    FREE_SW_CX;
    /**/
doit:;    
   /* dbg("ret=%d  c1='%s'  c2='%s'\n", ret, c1, c2);*/
    FREE_SW_CX;
    g_free(s);
    if (ret) return NULL;


    //dbg("kst_call_under is '%s'\n", call);
    return g_strdup(call);
}

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

    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);
        g_free(value);
        //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){
            char *wwl = NULL;

            if (val == -1){
                qrv_delete(t, aband->bi);
            }
            if (val >= 0){
                wwl = find_wwl_by_call(cw, t);
                if (wwl != NULL) qrv_kst_add(t, wwl, NULL);
            }
        }
        //dbg("call='%s' inserted\n", t);
    }
    
/*    dbg("%d\n", g_hash_table_size(gses->hicalls));*/
    redraw_later();
}


void sw_kst_nothing(void *itdata, void *menudata){
}

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

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

void sw_kst_message(void *itdata, void *menudata){
    struct subwin *sw = (struct subwin*)menudata;
    newkst(sw->il, sw->callunder); // calls z_str_uc(callkst)
    redraw_later();
#ifdef Z_HAVE_SDL
    if (gses && cfg->usetouch){
        if (gses->osk) zosk_free(gses->osk);
        gses->osk = zosk_init(sdl->screen, ZOSK_ENTERCLOSE | ZOSK_SENDENTER, sw->il->cdata);
        zosk_portrait(gses->osk);
    }
#endif
}

// trailing space if aband != NULL
void sw_kst_append_qrg(GString *gs){
    int full_qrg = 0;
    
    if (!aband) return;

    if (aband->skedqrg){
        if (strlen(aband->skedqrg) > 0){
            g_string_append_printf(gs, "%s ", aband->skedqrg);
            if (aband->skedqrg[0] != '.') full_qrg = 1; // defined but no leading dot
        }
    }
    if (!full_qrg){ // not full qrg information
        if (aband->qrg_min < 20000000){
            struct config_band *confb = get_config_band_by_bandchar(aband->bandchar);
            if (confb) g_string_append_printf(gs, "%s ", confb->adifband);
        }else{
            g_string_append_printf(gs, "%s ", aband->pband);
        }
    }
}

void sw_kst_ask_sked(void *itdata, void *menudata){
    time_t now;
    struct tm tm;
    char *name;
    struct subwin *sw = (struct subwin*)menudata;
    GString *gs = g_string_sized_new(256);
	int type = 0;
	if (itdata != NULL) type = GPOINTER_TO_INT(itdata);

    g_string_append_printf(gs, "/cq %s ", sw->callunder);
    
    if (type == 1){
        now = time(&now);
        localtime_r(&now, &tm);
        
        if (tm.tm_hour < 12) g_string_append(gs, "GM ");
        else if (tm.tm_hour < 18) g_string_append(gs, "GA ");
        else g_string_append(gs, "GE ");

        name = find_name_by_call(namedb, sw->callunder);
        if (name) g_string_append_printf(gs, "%s, ", name);

		g_string_append(gs, "PSE sked on ");
	}
	else if (type == 3) {
		g_string_append_printf(gs, "ANT is now in your direction on ");
	}else{ // 2 or default
        g_string_append(gs, "I am calling you on ");
    }

    sw_kst_append_qrg(gs);

	if (type == 1 || type == 2)
        g_string_append_printf(gs, " OK?");
    else
        g_string_append_printf(gs, ". Could you check it?");
    
    askkst(sw->il, gs->str);

    g_string_free(gs, TRUE);
    redraw_later();
#ifdef Z_HAVE_SDL
    if (gses && cfg->usetouch){
        if (gses->osk) zosk_free(gses->osk);
        gses->osk = zosk_init(sdl->screen, ZOSK_ENTERCLOSE | ZOSK_SENDENTER, sw->il->cdata);
        zosk_portrait(gses->osk);
    }
#endif
}

void sw_kst_info(void *itdata, void *menudata){
    char *call;
    struct subwin *sw = (struct subwin*)menudata;
    struct qso *q = g_new0(struct qso, 1);
    call = g_strdup(sw->callunder);
    z_strip_from(call, '-');
    q->callsign = call;
    call_info(q);
    g_free(q);
    g_free(call);
}

#ifdef Z_HAVE_SDL
void sw_kst_ac_info(void *itdata /* NULL or WWL*/, void *menudata){
    struct qrv_item *qi;
    char call[40], *c;
    struct subwin *sw = (struct subwin*)menudata;

    if (!gacs) return;
    if (!sw->callunder) return;

    strncpy(call, sw->callunder, 20);
    c = strchr(call, '-');
    if (c != NULL) *c = '\0';


    g_free(gacs->infolocqso.locator);
    gacs->infolocqso.locator = NULL;

    
    qi = qrv_get(qrv, call);
    if (qi != NULL){
        gacs->infolocqso.locator = g_strdup(qi->wwl);
	}else if (itdata ) {
		gacs->infolocqso.locator = g_strdup((char*)itdata);
	}else{
        char *wwl = find_wwl_by_call(cw, call);
        if (wwl) {
            gacs->infolocqso.locator = g_strdup(wwl);
        }else{
            log_addf(TRANSLATE("Sorry, unknown locator for %s"), call);
            return;
        }
    }

    kst_free_screen(sw);
    sw->l1map = SDL_CreateRGBSurface(SDL_SWSURFACE, sw->w*FONT_W, sw->h*FONT_H, sdl->bpp, 
            sdl->screen->format->Rmask, sdl->screen->format->Gmask, sdl->screen->format->Bmask, 0);
    sw->screen = SDL_CreateRGBSurface(SDL_SWSURFACE, sw->w*FONT_W, sw->h*FONT_H, sdl->bpp, 
            sdl->screen->format->Rmask, sdl->screen->format->Gmask, sdl->screen->format->Bmask, 0);
    MUTEX_INIT(sw->screen);
    sw->gdirty = 1;
    map_update_layout(sw);


    //if (gacs->infolocqso.locator){
    ac_update_infoctp(gacs->infolocqso.locator);
    sw->zoom = 10000;
    map_for_photo(sw, aband, MAP_AC_INFO);
    //}
    
    //redraw_later(); in map_for_photo/zoom
}
#endif

void sw_kst_use(void *itdata, void *menudata){
    char *call;
    struct subwin *sw = (struct subwin*)menudata;
    if (!aband) return;
    call = g_strdup(sw->callunder);
    z_strip_from(call, '-');
    process_input(aband, call, 0);
    il_add_to_history(INPUTLN(aband), call);
    g_free(call);
    sw_unset_focus();
    il_set_focus(INPUTLN(aband));
}

int sw_kst_line_color(struct subwin *sw, char *line){
    char call[20], s[20], *c;
    int color;

    if (strlen(line) < 9) return COL_NORM;
    g_strlcpy(call, line + 6, sizeof(call));
    z_strip_from(call, ' ');
    //z_get_raw_call(raw, call);
    color = sw_kst_color(call);
    if (color != COL_NORM) return color;

    g_strlcpy(call, line, sizeof(call)); // output of sh/us
    z_strip_from(call, ' ');
    //z_get_raw_call(raw, call);
    g_strlcpy(s, call, sizeof(s));
    z_strip_from(s, ')');
    c = s;
    if (*c == '(') c++;
    color = sw_kst_color(c);
    return color;
}

// called with sw == NULL
char *sw_kst_counterp(struct subwin *sw, char *line, int *color){ // thread unsafe!
    static char call[20], raw[20];
    char *c;

    *color = 0;
    if (strlen(line) < 20) return 0;
    c = z_strcasestr(line, "> (");
	if (c) {
		if (strncmp(c, "> (0) (", 7) == 0){ /* "> (0) (CALL) ..." */
			c += 4;  /* "CALL) ..." */
		}
		g_strlcpy(call, c + 3, sizeof(call));
		z_strip_from(call, ')');
		z_get_raw_call(raw, call);

		z_str_uc(raw);
		*color = sw_kst_color(raw);
		return call;
	}
	c = z_strcasestr(line, "> ");
	if (c) {
		g_strlcpy(call, c + 2, sizeof(call));
		z_strip_from(call, ' ');
		z_get_raw_call(raw, call);

		z_str_uc(raw);
		*color = sw_kst_color(raw);
		return call;
	}
	return NULL;
}

int sw_kst_color(char *call){
    int *pval, ret;
    char raw[25];

    if (!call) return COL_NORM;

    pval = (int *)g_hash_table_lookup(gses->hicalls, call);
    if (!pval){
        z_get_raw_call(raw, call);
        pval = (int *)g_hash_table_lookup(gses->hicalls, raw);
        if (!pval) 
        {
            z_strip_from(raw, '-');
            if (worked_on_aband(raw)) return COL_DARKYELLOW;

            return COL_NORM;
        }
    }

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

    if (!aband) return COL_YELLOW;
    z_get_raw_call(raw, call);
    z_strip_from(raw, '-');
    if (worked_on_all_rw(raw)) 
        ret = COL_DARKGREY;
    else if (worked_on_aband(raw))
        ret = COL_DARKYELLOW;
    else
        ret = COL_YELLOW;
    return ret;
}

int kst_read_line(struct subwin *sw, char *line){
    char *call, *mycall;
    int dummy;
	char *c1 = NULL, *c2 = NULL, *c3 = NULL, *c4 = NULL, *c5 = NULL, *wwl, *name, *fullname;
    int ret = 0;
    struct qrv_item *qi;

    call = sw_kst_counterp(NULL, line, &dummy); 
    if (call){
        mycall = ctest ? ctest->pcall : cfg->pcall;
		if (strcasecmp(call, mycall) == 0 || (cfg->kst_user != NULL && strcasecmp(call, cfg->kst_user) == 0)) {
			log_adds(line);
		}
    }

	//if (regmatch(line, "^([A-Z0-9\\(\\)\\/\\ -]{14})\\ ([A-R]{2}[0-9]{2}[A-X]{2})\\ (([^\\ ]{1,}).*)", &c1, &c2, &c3, &c4, &c5, NULL) == 0){
	if (regmatch(line, "^([A-Z0-9\\(\\)\\/\\ -]{14,16})\\ ([A-R]{2}[0-9]{2}[A-X]{2})\\ (([^\\ ]{1,}).*)", &c1, &c2, &c3, &c4, &c5, NULL) == 0){
        time_t now;
        struct tm gmt;
        int stamp;
        double qrb, qtf;

		//if (strstr(line, "9A2SB") != NULL){
		//	int a = 0;
		//}

        call = c2;
        z_str_uc(call);
        z_strip_from(call, ' ');
        z_strip_from(call, ')');
        z_strip_from(call, '-');
		if (call[0] == '(') {
			zg_free0(c1); zg_free0(c2); zg_free0(c3); zg_free0(c4);	zg_free0(c5);
			return 1; // skip
		}

        wwl = c3;
        z_str_uc(wwl);
        
		fullname = c4;
        name = c5;
        name[0] = z_char_uc(name[0]);
        z_strip_from(name, '/');

        //dbg("match call='%s'  wwl='%s'  name='%s'\n", call, wwl, name);
        now = time(NULL);
        gmtime_r(&now, &gmt);
        stamp = (gmt.tm_year + 1900) * 10000 + (gmt.tm_mon + 1) * 100 + gmt.tm_mday;
        add_cw(cw, call, wwl, stamp, NULL);
        add_wc(cw, wwl, call, stamp);
        add_namedb(namedb, call, name);

        qrbqtf(ctest ? ctest->pwwlo : cfg->pwwlo, wwl, &qrb, &qtf, NULL, 0);
        //if (cfg->kst_maxqrb > 0 && qrb >= cfg->kst_minqrb && qrb <= cfg->kst_maxqrb)
        {
            qrv_kst_add(call, wwl, fullname);
        }

        if (sw->shus){
            //z_get_raw_call(raw, call);
            qi = (struct qrv_item *)g_hash_table_lookup(qrv->hash, call/*raw*/);
            //dbg("add %s   qi=%p\n", call, qi);
            // qi can be NULL
            g_hash_table_insert(sw->tmpkstusers, g_strdup(call), qi);
			if (!sw->first_shus && !g_hash_table_lookup_extended(sw->kstusers, call, NULL, NULL)) {
				qi->new_on_kst = 1;
			}
            if (sw->shus == 1) ret = 1; // skip
        }
    }
	zg_free0(c1); zg_free0(c2); zg_free0(c3); zg_free0(c4);	zg_free0(c5);

    if (regmatch(line, "^The\\ ([A-Z0-9]{1,})\\ locator\\ is\\ ([A-R]{2}[0-9]{2}[A-Z]{2})", &c1, &c2, &c3, NULL) == 0){
        time_t now;
        struct tm gmt;
        int stamp;

        call = c2;
        z_str_uc(call);
        z_strip_from(call, '-');
        wwl = c3;
        z_str_uc(wwl);

        //dbg("match call='%s'  wwl='%s'\n", call, wwl);
        now = time(NULL);
        gmtime_r(&now, &gmt);
        stamp = (gmt.tm_year + 1900) * 10000 + (gmt.tm_mon + 1) * 100 + gmt.tm_mday;
        add_cw(cw, call, wwl, stamp, NULL);
        add_wc(cw, wwl, call, stamp);
    }
    //dbg("line='%s'\n", line);
    if (regcmp(line, "^[0-9]{4}Z\\ .*>$") == 0){
        kst_end_shus(sw);
    }
    return ret;
}

void kst_timer_function(void *data){
    struct subwin *sw = (struct subwin *)data;

    //log_addf("-----");
    //log_addf("kst_timer_function enter shus=%d", sw->shus);

    sw->timer_id = zselect_timer_new(zsel, 5 * 60 * 1000, kst_timer_function, sw);
    
    if (sw->sock < 0) {
        //sw_add_block(sw, "*** Skip /sh us, not connected\n");
        return;
    }

    if (sw->chat){
        //sw_add_block(sw, "*** Skip /sh us, chat is active\n");
        return;
    }

    sw_add_block(sw, VTEXT(T_SENDING_SH_US));
	zbinbuf_append(sw->wrbuf, "/sh us\r\n");
	zselect_set_write(zsel, sw->sock, sw_kst_write_handler, sw);

    kst_shus(sw, 1);
    
    //log_addf("kst_timer_function exit shus=%d", sw->shus);
}

void kst_shus(struct subwin *sw, int shus){
    sw->shus = shus;
    //log_addf("kst_shus(%d)", sw->shus);
    //dbg("1 users=%d oldusers=%d\n", g_hash_table_size(sw->kstusers), g_hash_table_size(sw->kstoldusers)); 

    g_hash_table_destroy(sw->tmpkstusers);
	sw->tmpkstusers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); // 2x. really?

	if (qrv != NULL && qrv->qrvs != NULL){
		int i;
		for (i = 0; i < qrv->qrvs->len; i++){
			struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
			qi->new_on_kst = 0;
		}
	}
    //dbg("2 users=%d oldusers=%d\n", g_hash_table_size(sw->kstusers), g_hash_table_size(sw->kstoldusers)); 

    //dbg("/sh us\n");
}

static void kst_copy_users(gpointer key, gpointer value, gpointer user_data){
    struct subwin *sw = (struct subwin*)user_data;
    g_hash_table_insert(sw->kstusers, g_strdup(key), g_strdup(value));
}

void kst_end_shus(struct subwin *sw){
    if (!sw->shus) return;

    //dbg("/sh us end, users=%d oldusers=%d\n", g_hash_table_size(sw->kstusers), g_hash_table_size(sw->kstoldusers)); 
	//log_addf("/sh us end, tmpkstusers=%d -> kstusers=%d", g_hash_table_size(sw->tmpkstusers), g_hash_table_size(sw->kstusers));
    sw->shus = 0;
    if (sw->first_shus) sw->first_shus = 0;

	if (g_hash_table_size(sw->tmpkstusers) > 0){
		g_hash_table_remove_all(sw->kstusers);
		g_hash_table_foreach(sw->tmpkstusers, kst_copy_users, sw);
	}
	//log_addf("/sh us end, kstusers=%d", g_hash_table_size(sw->tmpkstusers));
}

void kst_clear_users(struct subwin *sw){
    g_hash_table_remove_all(sw->kstusers);
	if (sw->tmpkstusers) g_hash_table_remove_all(sw->tmpkstusers);
}

int kst_user_skip(struct subwin *sw, const char *call){
    int *pval;
    if (!call) return 1;

    if (!g_hash_table_lookup_extended(sw->kstusers, call, NULL, NULL)) return 1;


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

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

}

int kst_qrb_skip(struct qrv_item *qi, char *call){
    if (cfg->kst_maxqrb == 0) return 0;
    if (qi->qrb <= cfg->kst_maxqrb) return 0;
    return 1;
}

int kst_wkdwwl_skip(struct qrv_item *qi){
	if (!aband) return 0;
	if (ctest->wwlused == 0) return 0;
	if (ctest->wwlmult == 0 && ctest->wwlbonu == 0) return 0;

	char buf[5];
	get_wwl(buf, qi->wwl);
	if (g_hash_table_lookup(aband->stats->wwls, buf)){
		return 1;
	}
	return 0;
}

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

void kst_open_bh(void *sw){
    kst_open_connection(sw, NULL);
}

void kst_open_connection(void *itdata, void *menudata){
    struct subwin *sw = (struct subwin *)itdata;

    sw_printf(sw, VTEXT(T_RESOLVING), kst_servers[kst_i]);
    zasyncdns_getaddrinfo(sw->adns, zsel, sw_kst_addrinfo, kst_servers[kst_i], AF_INET, sw);
}

void kst_export_text(void *itdata, void *menudata)
{
    struct subwin *sw = (struct subwin *)itdata;
    sw_export_lines(sw, "kst");

}

#ifdef Z_HAVE_SDL    
void kst_free_screen(struct subwin *sw){
    if (sw->l1map) {
        SDL_FreeSurface(sw->l1map);
        sw->l1map = NULL;
    }
    if (sw->screen) {
        SDL_FreeSurface(sw->screen);
        sw->screen = NULL;
    }
    MUTEX_FREE(sw->screen);
}
#endif

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

    fprintf(f, " bi=%c", 'A' + bi);
    fprintf(f, " bands_qrv=");
    for (i = 0; i < 26; i++) if (qi->bands_qrv & (1<<i)) fprintf(f, "%c", 'A' + i);
    fprintf(f, " bands_wkd=");
    for (i = 0; i < 26; i++) if (qi->bands_wkd & (1<<i)) fprintf(f, "%c", 'A' + i);

    if ((qi->bands_wkd & (1<<bi))!=0) {
        ret = 2;
        fprintf(f, "  not wkd");
    }
    if (qrv->showall && !*qrv->search) {
        //qi->ac_drawn = 1;
        fprintf(f, " qrv->showall=%d !*qrv->search", qrv->showall);
        return ret;
    }
    if ((qi->bands_qrv & (1<<bi))==0) {
        ret = 1;
        fprintf(f, "  not qrv");
    }
    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;
        fprintf(f, ret == 0 ? "  found in search" : "  not found in search");
    }
    //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 kst_dump_user_skip(FILE *f, struct subwin *sw, const char *call){
    int *pval;
    if (!call) {
        fprintf(f, " call is NULL");
        return 1;
    }

    if (!g_hash_table_lookup_extended(sw->kstusers, call, NULL, NULL)) {
        fprintf(f, " not in kstusers");
        return 1;
    }


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

    switch (*pval){
        case -2:
        case -1:
            fprintf(f, " *pval=%d", *pval);
            return 1;
        case 0:
            return 0;
        case 1:
            return 0;
        default:
            return 0;
    }

}

void kst_dump_skip(void *itemdata, void *menudata){
    int i;
    char *filename;
    FILE *f;        
    struct subwin *sw = (struct subwin*)itemdata;

    filename = g_strdup_printf("%s/_kstskipdump", ctest->directory);
    z_wokna(filename);
    f = fopen(filename, "wt");
    if (!f) {
        log_addf("Can't open '%s'", filename);
        g_free(filename);
        return;
    }
    if (!ctest) {
        fprintf(f, "No contest opened\n");
        goto x;
    }
    if (!qrv){
        fprintf(f, "No QRV database\n");
        goto x;
    }
    for (i = 0; i < qrv->qrvs->len; i++){
        struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);

        fprintf(f, "\n%-10s", qi->call);

        if (kst_dump_qrv_skip(f, qi, aband->bi, 1)) {
            fprintf(f, "  QRV SKIP");
            continue;
        }
        if (kst_dump_user_skip(f, sw, qi->call)){
            fprintf(f, "  KST SKIP");
            continue;
        }
        
    }
    fprintf(f, "\n");
    
x:;
    fclose(f);
    log_addf("Saved to %s", filename);
    g_free(filename);
}

GPtrArray* sw_kst_calls_under(struct subwin *sw, int n, int y){
	GPtrArray* ret = g_ptr_array_new();
	int index = sw->lines->len - sw->offset - sw->h + y;
	if (index >= sw->lines->len) index = sw->lines->len - 1;

	for (; index >= 0; index--){

		char *b, *e;
		//dbg("index=%d\n", index);
		char *orig = (char *)g_ptr_array_index(sw->lines, index);
		char *line = z_str_uc(g_strconcat(orig, " ", NULL));
		b = line;
		while (*b != '\0'){
			for (; *b != '\0' && !z_can_be_call_char(*b, ZCBC_MINUS); b++) {}
			if (*b != '\0')
			{
				for (e = b; *e != '\0' && z_can_be_call_char(*e, ZCBC_MINUS); e++) {};
				//if (*e == '\0') break; // while
								   
				*e = '\0';
				if (regcmp(b, "^([0-9A-Z]{1,3}\\/)?([0-9][A-Z]|[A-Z]{1,2}[0-9]?)[0-9]{1,4}[A-Z]{1,4}(\\/[A-Z0-9]{1,3})*(-[0-9]{1,2})*$") == 0){
				//if (z_can_be_call(b, ZCBC_MINUS)){

					int eq = zg_ptr_array_find_str(ret, b);
					if (eq == 1){
						g_ptr_array_add(ret, g_strdup(b));
						if (ret->len >= n) {
							index = -1;
							goto x;
						}
						dbg("%s\n", b);
					}
				}
				b = e + 1;
			}
		}
	x:;
		g_free(line);

	}
	return ret;
}

void sw_kst_write_handler(void *arg){
	struct subwin *sw = (struct subwin *)arg;

	int len = sw->wrbuf->len;
	if (len > 1400) len = 1400;
	int ret = send(sw->sock, sw->wrbuf->buf, len, 0);
	if (ret <= 0){
		sw_kst_disconnect(sw);
		return;
	}

	zbinbuf_erase(sw->wrbuf, 0, ret);
	if (sw->wrbuf->len == 0) zselect_set_write(zsel, sw->sock, NULL, sw);
	

}
