/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2022  Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/


#include "header.h"

#include "bfu.h"
#include "cwdb.h"
#include "fifo.h"
#include "main.h"
#include "masterdb.h"
#include "qsodb.h"
#include "tsdl.h"
#include "session.h"
#include "stats.h"
#include "subwin.h"

struct cw *cw;
static gchar *cw_next;
static gchar *wc_next;

struct cw *init_cw(void){
    struct cw *cw;
    time_t now;
    struct tm tm;

    progress(VTEXT(T_INIT_CWDB));       
    cw = g_new0(struct cw, 1);
    
    cw->cw = z_hash_table_new(g_str_hash, g_str_equal);
    cw->wc = g_hash_table_new(g_str_hash, g_str_equal);
    
    cw_next = NULL;
    wc_next = NULL;
    cw->latest = 0;
    
    
    time(&now);
    now -= 740 * 86400L; /* 740 days = two years + something */
    gmtime_r(&now, &tm);
    cw->minstamp = (tm.tm_year+1900)*10000 + (tm.tm_mon+1)*100 + tm.tm_mday;

    return cw;
}

static gboolean free_cw_item(gpointer key, gpointer value, gpointer user_data){
    struct cw_item *cwi;
    
    g_free(key);
    cwi = (struct cw_item *)value;
    if (cwi->wwl0) g_free(cwi->wwl0);
    if (cwi->wwl1) g_free(cwi->wwl1);
    g_free(value);
    return TRUE;    
}

static gboolean free_wc_item(gpointer key, gpointer value, gpointer user_data){
    struct wc_item *wci;
    
    g_free(key);
    wci = (struct wc_item *)value;
    if (wci->call0) g_free(wci->call0);
    if (wci->call1) g_free(wci->call1);
    g_free(value);
    return TRUE;
        
}

void free_cw(struct cw *cw){
    progress(VTEXT(T_TERM_CWDB));       

    g_hash_table_foreach_remove(cw->wc, free_wc_item, NULL);
    g_hash_table_destroy(cw->wc);
    
    z_hash_table_foreach_remove(cw->cw, free_cw_item, NULL);
    z_hash_table_destroy(cw->cw);
    g_free(cw);
}

gint get_cw_size(struct cw *cw){
    return z_hash_table_size(cw->cw);
}

gint get_wc_size(struct cw *cw){
    return g_hash_table_size(cw->wc);
}

#define CW_DELIM " \t\r\n"

void load_one_cw(struct cw *cw, gchar *s){
    gchar *call, *wwl, *stamp_str, *qrv_str;
    char *token_ptr;
    
    z_str_uc(s);
    call = strtok_r(s, CW_DELIM, &token_ptr);
    if (!call) return;
    z_strip_from(call, '-');  // before 3.15 in db can be calls with SSID from KST

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

    stamp_str = strtok_r(NULL, CW_DELIM, &token_ptr);
    if (!stamp_str) return;
    
    qrv_str = strtok_r(NULL, CW_DELIM, &token_ptr);
    /*dbg("qrv_str(%s)=%s\n", call, qrv_str);*/

    add_cw(cw, call, wwl, atoi(stamp_str), qrv_str); /* qrv can be null */
    add_wc(cw, wwl, call, atoi(stamp_str));
}

int load_cw_from_file(struct cw *cw, gchar *filename){
    FILE *f;
    char s[102];

    f = fopen(filename, "rt");
    if (!f){
        /*dbg("Can't open '%s'\n", filename);*/
        return -1;
    }

    while((fgets(s, 100, f))!=NULL){
        load_one_cw(cw,s);
    }

    fclose(f);
    return 0;
}

int load_cw_from_mem(struct cw *cw, const char *file, size_t len){
    GString *gs; 
    long int pos;
    
    gs = g_string_sized_new(100);
    
    pos=0;
    while(1){
        if (!zfile_mgets(gs, file, &pos, len, 1)) break;
        load_one_cw(cw, gs->str);
    }
    g_string_free(gs, 1);
    return 0;

}

void read_cw_files(struct cw *cw){
    gchar *s;
    int ret = 0;
    
    progress(VTEXT(T_LOADING_CWDB));        

/*#ifdef UNIX
    ret |= load_cw_from_file(cw, "/etc/tucnakcw");
#endif*/
    s = g_strconcat(tucnak_dir, "/tucnakcw", NULL); 
    z_wokna(s);
    ret |= load_cw_from_file(cw, s);
    if (ret) load_cw_from_mem(cw, txt_tucnakcw, sizeof(txt_tucnakcw));
    cw->dirty = 0;
    g_free(s);
}

static void save_one_cw(gpointer key, gpointer value, gpointer user_data){
    GString *gs;
    gchar *call;
    struct cw_item *cwi;
    gchar qrv_str[33], *c;
    int i;
    char s[4096];

    gs   = (GString *) user_data;
    call = (gchar *) key;
    cwi  = (struct cw_item *) value;
    
    for (i=0, c=qrv_str;i<32;i++){
        if (!(cwi->qrv & (1<<i))) continue;
        *c='A'+i;
        c++;
    }
    *c='\0';

    if (cwi->wwl0){ 
        sprintf(s, "%-14s %-6s %08d %s\n", call, cwi->wwl0, cwi->stamp0, qrv_str);
        g_string_append(gs, s);
        // g_string_append_printf is incredibely slow under msvc
        //g_string_append_printf(gs, "%-14s %-6s %08d %s\n", call, cwi->wwl0, cwi->stamp0, qrv_str);
    }
    
    if (cwi->wwl1){
        sprintf(s, "%-14s %-6s %08d %s\n", call, cwi->wwl1, cwi->stamp1, qrv_str);
        g_string_append(gs, s);
        //g_string_append_printf(gs, "%-14s %-6s %08d %s\n", call, cwi->wwl1, cwi->stamp1, qrv_str);
    }
    
}

int save_cw_string(struct cw *cw, GString *gs){
    z_hash_table_foreach(cw->cw, save_one_cw, (gpointer) gs);
    return 0;
}

int save_cw_into_file(struct cw *cw, gchar *filename){
    FILE *f;
    GString *gs;
    int ret;

    f = fopen(filename, "wt"); /* FIXME swp first */
    if (!f) {
        return errno;
    }
    
    gs=g_string_sized_new(100000);
    save_cw_string(cw,gs);
    ret = fprintf(f,"%s",gs->str) != gs->len ? errno: 0;
    fclose(f);
    g_string_free(gs,TRUE);
    return ret;
}

int save_cw(struct cw *cw, int verbose){
    char *s;
    int ret;

    if (!cw->dirty) return 0;
    s = g_strconcat(tucnak_dir, "/tucnakcw", NULL);
    z_wokna(s);
    ret=save_cw_into_file(cw, s);        
    if (ret){
        errbox(VTEXT(T_CANT_WRITE), ret);
    }else{
        if (verbose) log_addf(VTEXT(T_SAVED_S), s);
        cw->dirty = 0;
    }
    g_free(s);
    return ret;
}


static void format_one_cw(gpointer key, gpointer value, gpointer user_data){
    GString *gs;
    gchar *call;
    struct cw_item *cwi;
    gchar qrv_str[33], *c;
    int i;
    char s[4096];

    gs   = (GString *) user_data;
    call = (gchar *) key;
    cwi  = (struct cw_item *) value;
    
    for (i=0, c=qrv_str;i<32;i++){
        if (!(cwi->qrv & (1<<i))) continue;
        *c='A'+i;
        c++;
    }
    *c='\0';

    if (cwi->wwl0){ 
        sprintf(s, "%s %s %d %s;", call, cwi->wwl0, cwi->stamp0, qrv_str);
        g_string_append(gs, s);
        // g_string_append_printf is incredibely slow under msvc
        //g_string_append_printf(gs, "%-14s %-6s %08d %s\n", call, cwi->wwl0, cwi->stamp0, qrv_str);
    }
    
    if (cwi->wwl1){
        sprintf(s, "%4s %s %d %s;", call, cwi->wwl1, cwi->stamp1, qrv_str);
        g_string_append(gs, s);
        //g_string_append_printf(gs, "%-14s %-6s %08d %s\n", call, cwi->wwl1, cwi->stamp1, qrv_str);
    }
    
}

int format_cw_string(struct cw *cw, GString *gs){
    z_hash_table_foreach(cw->cw, format_one_cw, (gpointer) gs);
    return 0;
}

void add_cw(struct cw *cw, gchar *call, gchar *wwl, gint stamp, gchar *qrv_str){
    struct cw_item *cwi;
    char *c;
    
    if (stamp<0) return; 
    if (stamp>cw->latest) cw->latest=stamp;
    
    cwi = (struct cw_item *)z_hash_table_lookup(cw->cw, call);
    if (!cwi){
        cwi = g_new0(struct cw_item, 1);
        z_hash_table_insert(cw->cw, g_strdup(call), cwi);
    }
    
    if (qrv_str){ /* can be NULL*/
        for (c=qrv_str;*c!='\0';c++){
            *c=z_char_uc(*c);
            cwi->qrv|=1<<(*c-'A');
        }
    }
    
    if (stamp < cwi->stamp1) return;
                
    if (stamp < cwi->stamp0){
        if (strcasecmp(cwi->wwl0, wwl)==0) return;

        if (cwi->wwl1) g_free(cwi->wwl1);
        cwi->wwl1   = g_strdup(wwl);
        cwi->stamp1 = stamp;
        cw->dirty = 1;
        return;
    }

    if (cwi->wwl0 && strcasecmp(cwi->wwl0, wwl)==0) {
        cwi->stamp0 = stamp;
        cw->dirty = 1;
        return;
    }

    if (cwi->wwl1) g_free(cwi->wwl1);
    cwi->wwl1   = cwi->wwl0;
    cwi->stamp1 = cwi->stamp0;
    
    cwi->wwl0   = g_strdup(wwl);
    cwi->stamp0 = stamp;
    cw->dirty = 1;
}


void add_wc(struct cw *cw, gchar *wwl, gchar *call, gint stamp){
    struct wc_item *wci;
    
    if (stamp<0) return; 
    if (stamp>cw->latest) cw->latest=stamp;
    
    wci = (struct wc_item *)g_hash_table_lookup(cw->wc, wwl);
    if (!wci){
        wci = g_new0(struct wc_item, 1);
        g_hash_table_insert(cw->wc, g_strdup(wwl), wci);
    }

    if (stamp < wci->stamp1) return;

    if (stamp < wci->stamp0){
        if (strcasecmp(wci->call0, call)==0) return;

        if (wci->call1) g_free(wci->call1);
        wci->call1  = g_strdup(call);
        wci->stamp1 = stamp;
        return;
    }

    if (wci->call0 && strcasecmp(wci->call0, call)==0) {
        wci->stamp0 = stamp;
        return;
    }
    
    
    if (wci->call1) g_free(wci->call1);
    wci->call1  = wci->call0;
    wci->stamp1 = wci->stamp0;
    
    wci->call0  = g_strdup(call);
    wci->stamp0 = stamp;
}


gchar *find_wwl_by_call(struct cw *cw, gchar *call){
    struct cw_item *cwi;

    if (!call) return cw_next;
    cw_next = NULL;
        
    cwi = (struct cw_item *)z_hash_table_lookup(cw->cw, call);
    if (!cwi) return NULL;
    
    cw_next = cwi->wwl1;
    return cwi->wwl0;
}

gchar *find_wwl_by_call_newer(struct cw *cw, gchar *call, int maxstamp){
    struct cw_item *cwi;

    cwi = (struct cw_item *)z_hash_table_lookup(cw->cw, call);
    if (!cwi) return NULL;
    if (cwi->stamp0 > maxstamp) return cwi->wwl0;
    if (cwi->stamp1 > maxstamp) return cwi->wwl1;
    return NULL;
}

gchar *find_call_by_wwl(struct cw *cw, gchar *wwl){
    struct wc_item *wci;

    if (!wwl) return wc_next;
    wc_next = NULL;
        
    wci = g_hash_table_lookup(cw->wc, wwl);
    if (!wci) return NULL;
    
    wc_next = wci->call1;
    return wci->call0;
}


gchar *find_qrv_str_by_call(struct cw *cw, gchar *call){
    int i,j;
    gchar *c;
    struct cw_item *cwi;
    gint qrv;
    static gchar qrv_str[33];
    gchar *ret;
    char raw[25], raw2[25];
    struct qso *q;
    struct band *band;
    
    if (!aband) return NULL;

    qrv=0;
    
    cwi = (struct cw_item *)z_hash_table_lookup(cw->cw, call);
    if (cwi) qrv|=cwi->qrv;

    cwi = (struct cw_item *)z_hash_table_lookup(cw->cw, z_get_raw_call(raw, call));
    if (cwi) qrv|=cwi->qrv;

    c=g_strdup_printf("%s/P", raw);
    cwi = (struct cw_item *)z_hash_table_lookup(cw->cw, c);
    if (cwi) qrv|=cwi->qrv;
    g_free(c);

    if (!qrv) return NULL;
    
    for (i='A',c=qrv_str; i<='Z'; i++){
        
        if (i==aband->bandchar) continue;          /* same band */
        if (!(qrv & ctest->qrv & (1<<(i-'A')))) continue;   /* we are not qrv on band */

        band = find_band_by_bandchar(i);
        if (band){
            for (j=0; j<band->qsos->len; j++){
                q = (struct qso *)g_ptr_array_index(band->qsos, j);
                if (q->error || q->dupe) continue;
                z_get_raw_call(raw2, q->callsign);
                /*if (!cfg->daqrv){
                    if (strcmp(raw2, raw)==0) goto next;
                }else{ */
                    if (strcmp(raw2, raw)==0) {
                        *c = z_char_lc(i);
                        c++;
                        goto next;
                    }
                //}
            }
        }
        
        *c=i;
        c++;
next:;        
    }
    *c='\0';
    ret=strlen(qrv_str)?qrv_str:NULL;
    /*log_addf("find_qrv_str_by_call(%s)='%s'", call, ret);*/
    return ret;
}

/*static int is_string_in_array(GPtrArray *arr, gchar *str){
    int i;
    gchar *c;

    for (i=0; i<arr->len; i++){
        c = (gchar *)g_ptr_array_index(arr, i);
        if (!c) continue;

        if (strcmp(c, str)==0) return 1;
        
    }
    return 0;
} */


int cmp_cw_qs(gchar *call, struct cw_item *cwi, GPtrArray *result){
    gchar *c;
    int ret=0, i;
    int qrb_int, qtf_int;
    char qrv_str[35];
    int qrvfilter;
    char degree[2];

#if defined(Z_HAVE_SDL) && !defined(Z_ANDROID)     
    if (sdl) 
        strcpy(degree, "\xb0");
    else
#endif
        strcpy(degree, "");

    qrvfilter = ctest ? ctest->qrv : -1; 
    
    c=qrv_str;
    for (i=0;i<32;i++){
        if ((cwi->qrv & qrvfilter & (1<<i)) == 0) continue;
        *c='A'+i;
        c++;
    }
    *c='\0';

    
    if (z_strstr(call, gses->qs_str)){
        if (cwi->wwl0) {
            qrb_qtf_int(ctest ? ctest->pwwlo : cfg->pwwlo,  cwi->wwl0, &qrb_int, &qtf_int);
            c=g_strdup_printf("%-10s %-6s%5d/%3d%s %s", call, cwi->wwl0, qrb_int, qtf_int, degree, qrv_str);
            g_ptr_array_add(result, c);
            if (cwi->wwl1){ /* wwl1 is only together with wwl0 */
                qrb_qtf_int(ctest ? ctest->pwwlo : cfg->pwwlo,  cwi->wwl1, &qrb_int, &qtf_int);
                c=g_strdup_printf("%-10s %-6s%5d/%3d%s %s", call, cwi->wwl1, qrb_int, qtf_int, degree, qrv_str);
                g_ptr_array_add(result, c);
            }
        }else{
            c=g_strdup(call);
            g_ptr_array_add(result, c);
        }
        goto x;
    }
    
    if (cwi->wwl0 && z_strstr(cwi->wwl0, gses->qs_str)){
        qrb_qtf_int(ctest ? ctest->pwwlo : cfg->pwwlo,  cwi->wwl0, &qrb_int, &qtf_int);
        c=g_strdup_printf("%-10s %-6s%5d/%3d%s %s", call, cwi->wwl0, qrb_int, qtf_int, degree, qrv_str);
     /*   dbg(",%s", c);*/
        g_ptr_array_add(result, c);
        goto x;
    }
    
    if (cwi->wwl1 && z_strstr(cwi->wwl1, gses->qs_str)){
        qrb_qtf_int(ctest ? ctest->pwwlo : cfg->pwwlo,  cwi->wwl1, &qrb_int, &qtf_int);
        c=g_strdup_printf("%-10s %-6s%5d/%3d%s %s", call, cwi->wwl1, qrb_int, qtf_int, degree, qrv_str);
    /*    dbg(",%s", c);  */
        g_ptr_array_add(result, c);
        goto x;
    }
x:;    
    return ret;
}

void get_cw_qs(gchar *str){
    
    qs_thread_kill();
    
    
    if (strlen(str)<2) {
//        zg_free0(gses->qs_master);
        zg_ptr_array_free_items(gses->qs);
        return;
    }

    if (gses && gses->ontop && gses->ontop->type == SWT_HF){
        zg_free0(gses->qs_master);
       // ST_START;
        gses->qs_master = masterdb_search(masterdb, str, gses->ontop->w);
       // ST_STOP;
        //log_adds(gses->qs_master);
    }else{
        zg_free0(gses->qs_str);
        gses->qs_str=g_strdup(str);
        gses->qs_max_matches = term->y - QSONR_HEIGHT - 4 - cfg->loglines - 1;
        if (ctest && !ctest->oldcontest) gses->qs_max_matches -= ctest->spypeers->len;
        if (ctest && ctest->bands->len==1) gses->qs_max_matches*=2;
        if (!ctest) gses->qs_max_matches*=3;
        qs_thread_create();
    }
}

static gpointer qs_thread_func(gpointer data){
    GPtrArray *result;
    ZHashNode *node;
    gint i;

    zg_thread_set_name("Tucnak quicksearch");

    result=g_ptr_array_new();
    
    for (i = 0; i < cw->cw->size; i++){
        for (node = cw->cw->nodes[i]; node; node = node->next){
            cmp_cw_qs((char *)node->key, (struct cw_item*)node->value, result);
            
            if (result->len >= gses->qs_max_matches) goto x;
   
            if (gses->qs_thread_break){
                zg_ptr_array_free_all(result);
                return NULL;
            }

        }
    }
x:; 
    z_ptr_array_qsort((ZPtrArray *)result, z_compare_string); // FIXME GPtr vs. ZPtr ?
    
    MUTEX_LOCK(gses->qs);
    zg_ptr_array_free_all(gses->qs);
    gses->qs=result;
    MUTEX_UNLOCK(gses->qs);
    
    //sprintf(s, "CWQS\n");
    //ret = write(tpipe->threadpipe_write, s, strlen(s));
    zselect_msg_send(zsel, "CWQS");

    return NULL;
}

void qs_thread_create(void){
    if (gses->qs_thread) zinternal("qs_thread_create gses->qs_thread");
    gses->qs_thread_break=0;
    gses->qs_thread=g_thread_try_new("quicksearch", qs_thread_func, (gpointer)NULL, NULL);
    if (!gses->qs_thread) zinternal("qs_thread_create !gses->qs_thread");
}


void qs_thread_join(void){
    if (!gses->qs_thread) return;
    //dbg("join qs...\n");
    g_thread_join(gses->qs_thread);
    //dbg("done\n");
    gses->qs_thread=NULL;
}

void qs_thread_kill(){
    if (!gses->qs_thread) return;
    gses->qs_thread_break=1;
//    dbg("join qs...\n");
    g_thread_join(gses->qs_thread);
 //   dbg("done\n");
  /*  dbg("done\n");*/
    gses->qs_thread=NULL;
}

void update_cw_from_band(struct cw *cw, struct band *band){
    int i;
    struct qso *q;
    gchar qrv_str[2];

    stats_thread_join(band);
    qrv_str[0]=band->bandchar;
    qrv_str[1]='\0';

    for (i=0; i<band->qsos->len; i++){
        q = (struct qso *)g_ptr_array_index(band->qsos, i);

        if (q->error) continue;
        
        if (!q->callsign || !q->locator || !q->date_str) continue;
        if (strlen(q->locator)!=6) continue;

        add_cw(cw, q->callsign, q->locator, atoi(q->date_str), qrv_str);
        add_wc(cw, q->locator, q->callsign, atoi(q->date_str));
        
    }
    
}


void update_cw_from_ctest(struct cw *cw, struct contest *ctest){
    int i;
    struct band *band;

    if (!ctest) return;

    for (i=0; i<ctest->bands->len; i++){
        band = (struct band *) g_ptr_array_index(ctest->bands, i);

        update_cw_from_band(cw, band);
    }
    
}



int similar_calls(const char *call1, const char *call2, int factor, int thr, int p){
    factor = z_levenshtein(call1, call2);
    p=1;
    if (p) {
//        dbg("similar %s %s = %d", call1, call2, factor);
//        printf("  leven=%d", levenshtein_distance(call1, call2));
//        dbg("\n");
    }
    /*else printf("          %s %s = %d\n", call1, call2, factor);*/
    return factor;
}
 



int get_susp_ambiguous_call(struct cw *cw, struct band *band, char *call, char *wwl, GString *gs, int thr){
    int i;
    ZHashNode *node;
    struct cw_item *cwi;

    for (i = 0; i < cw->cw->size; i++){
        for (node = cw->cw->nodes[i]; node; node = node->next){
            int factor;
            cwi  = (struct cw_item *) node->value;
            
            if ((cwi->wwl0 && strcmp(cwi->wwl0, wwl)==0 && cwi->stamp0 > cw->minstamp) ||
                (cwi->wwl1 && strcmp(cwi->wwl1, wwl)==0 && cwi->stamp1 > cw->minstamp)){
                            
                /* dbg("hit %s %s\n", node->key, wwl);*/
                if (strcmp((gchar*)node->key, call)==0) continue;
                if (band && get_qso_by_callsign(band, call)) continue;  // call is also worked, no warning

                factor = similar_calls((gchar*)node->key, call, 0, thr, 0);
                if (factor<=thr){
                    if (!gs) return 1;

                    g_string_append(gs, (char *)node->key);
//                    g_string_append_printf(gs, "(%d<=%d)", factor, thr);
                    g_string_append_c(gs, ' ');
                }
            }
        }
    }
    return 0;
}

int cwdb_call_info(GString *gs, gchar *call, gchar *stroke, GHashTable *wwls){
    struct cw_item *cwi;
    char c[40], *myqth;
    int i;
    double qrb, qtf;
    char degree[2];
 
    strcpy(c, call);
    strcat(c, stroke);
    cwi = (struct cw_item *)z_hash_table_lookup(cw->cw, c);
    if (!cwi) return 0;

#if defined(Z_HAVE_SDL) && !defined(Z_ANDROID)     
    if (sdl) 
        strcpy(degree, "\xb0");
    else
#endif
        strcpy(degree, "");

    myqth = ctest ? ctest->pwwlo : cfg->pwwlo;
     /* wwl0 should be always defined */
    if (cwi->wwl0) {
        qrbqtf(myqth, cwi->wwl0, &qrb, &qtf, NULL, 0);
        g_string_append_printf(gs, "C_W: %-10s %s %6dkm %3d%s  %d\n", c, cwi->wwl0, (int)qrb, (int)qtf, degree, cwi->stamp0);
        if (wwls && g_hash_table_lookup(wwls, cwi->wwl0) == NULL) g_hash_table_insert(wwls, cwi->wwl0, NULL);
    }
    if (cwi->wwl1) {
        qrbqtf(myqth, cwi->wwl1, &qrb, &qtf, NULL, 0);
        g_string_append_printf(gs, "C_W: %-10s %s %6dkm %3d%s  %d\n", c, cwi->wwl1, (int)qrb, (int)qtf, degree, cwi->stamp1);
        if (wwls && g_hash_table_lookup(wwls, cwi->wwl1) == NULL) g_hash_table_insert(wwls, cwi->wwl1, NULL);
    }
    if (cwi->qrv>0){
        g_string_append(gs, VTEXT(T_QRV_ON));
        for (i=0;i<26;i++){
            if ((1<<i)&cwi->qrv) g_string_append_c(gs, i+'A');
        }
        g_string_append_c(gs, '\n');
    }
    return 1;
}
