/*
    httpd.c - Contest progress displayed via http
    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 "header.h"

#include "chart.h"
#include "fifo.h"
#include "html.h"
#include "httpd.h"
#include "icons.h"
#include "language2.h"
#include "main.h"
#include "map.h"
#include "net.h"
#include "rc.h"
#include "qsodb.h"
#include "stats.h"
#include "subwin.h"
#include "tsdl.h"



struct httpd *httpd;

struct httpd *init_httpd(void){
    struct httpd *httpd;
    struct sockaddr_in sin;
    char errbuf[100];

    if (!cfg->httpd_enable) return NULL;

    progress(VTEXT(T_INIT_HTTPD));      

    httpd = g_new0(struct httpd, 1);
    httpd->port = cfg->httpd_port; 
    httpd->refresh = cfg->httpd_refresh; 

    httpd->sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (httpd->sock < 0){
        error("Can't create httpd socket");
        goto x;
    } 

    if (z_sock_reuse(httpd->sock, 1)){
        trace(cfg->trace_sock, "Can't set SO_REUSEADDR\n");
        goto x;
    }
    
    if (z_sock_nonblock(httpd->sock, 1)){
        trace(cfg->trace_sock, "Can't set O_NONBLOCK\n");
        goto x;
    }
        
    memset(&sin, 0, sizeof(struct sockaddr_in));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(httpd->port);
    sin.sin_addr.s_addr = INADDR_ANY;
    if (bind(httpd->sock, (struct sockaddr *)&sin, sizeof(sin))) {
        int err = z_sock_errno;
        trace(cfg->trace_sock, "Can't bind port %d, %s\n", httpd->port, z_sock_strerror());
        goto x;
    }
    
    if (listen(httpd->sock, 10)){
        trace(cfg->trace_sock, "Can't listen on socket %d, tcp port %d \n", httpd->sock, httpd->port);
        goto x;
    }
    
    zselect_set(zsel, httpd->sock, httpd_accept_handler, NULL, NULL, NULL);
    httpd->conns = g_ptr_array_new();
    dbg("httpd active\n");

    return httpd;
x:;
    if (httpd->sock >= 0) closesocket(httpd->sock);
    httpd->sock = -1;
    g_free(httpd);
    return NULL;
}

void free_httpd(struct httpd *httpd){
    if (!httpd) return;

    progress(VTEXT(T_FREE_HTTPD));      

    if (httpd->sock >= 0) {
        zselect_set(zsel, httpd->sock, NULL, NULL, NULL, NULL);
        closesocket(httpd->sock);
    }

    g_ptr_array_free(httpd->conns, TRUE);
    g_free(httpd);
}

void free_httpconn(struct httpconn *conn){
    if (conn->sock >= 0) {
        zselect_set(zsel, conn->sock, NULL, NULL, NULL, NULL);
        closesocket(conn->sock);
    }
    g_string_free(conn->request, TRUE);
    g_string_free(conn->response, TRUE);
    if (conn->response_zbb) zbinbuf_free(conn->response_zbb);
    g_free(conn);
}

void httpd_accept_handler(void *arg){
    int sock;
    socklen_t socklen;
    
    struct httpconn *conn;
    
    conn = g_new0(struct httpconn, 1);

    socklen = sizeof(conn->peer);
    sock = accept(httpd->sock, (struct sockaddr *)&conn->peer, &socklen);
    if (!socklen || sock < 0) {
        g_free(conn);
        return;
    }

    trace(cfg->trace_sock, "Accepted socket %d %s:%d\n", sock, inet_ntoa(conn->peer.sin_addr), ntohs(conn->peer.sin_port));

    conn->sock = sock;
    conn->request = g_string_sized_new(500);
    conn->response = g_string_sized_new(500);
    
    zselect_set(zsel, conn->sock, httpd_read_handler, NULL, NULL, conn);
    g_ptr_array_add(httpd->conns, conn);
}

void httpd_read_handler(void *arg){
    struct httpconn *conn;
    char s[1030], *c, lf;
    int ret;

    conn = (struct httpconn*)arg;
    ret = recv(conn->sock, s, 1024, 0);
    //err=z_sock_errno;
    if (ret <= 0){
        g_ptr_array_remove(httpd->conns, conn);
        free_httpconn(conn);
        return;
    }
    s[ret] = '\0';
    g_string_append(conn->request, s);

    lf = 0;
    for (c = conn->request->str; *c != '\0'; c++){
        if (*c == '\r') continue;
        if (*c != '\n') {
            lf = 0;
            continue;
        }
        lf++;
        if (lf < 2) continue;
        break;
    }
    if (lf < 2) return;
    
    g_strlcpy(s, conn->request->str, sizeof(s));
    c = strchr(s, '\r');
    if (c) *c = '\0';
    log_addf("HTTP %s: %s", inet_ntoa(conn->peer.sin_addr), s);

    if (strncasecmp(conn->request->str, "GET ", 4) == 0){
        httpd_get(conn);
    }else{
        httpd_header(conn, 400, NULL);
        g_string_printf(conn->response, "<html><body>Bad request</body></html>");
    }
/*    for (i = 0; i < 1000; i++){
        g_string_append_c(conn->response, '0' + (i / 100));
    }*/
    zselect_set(zsel, conn->sock, NULL, httpd_write_handler, NULL, arg);
}

void httpd_write_handler(void *arg){
    struct httpconn *conn;
    int ret, len;

    conn = (struct httpconn*)arg;

    if (conn->response->len > 0){
        len = conn->response->len;
        if (len > 1400) len = 1400;
        ret = send(conn->sock, conn->response->str, len, 0);
        //err = z_sock_errno;
        if (ret <= 0){
            g_ptr_array_remove(httpd->conns, conn);
            free_httpconn(conn);
            return;
        }

        g_string_erase(conn->response, 0, ret);
        if (conn->response->len <= 0 && !conn->response_zbb){
            g_ptr_array_remove(httpd->conns, conn);
            free_httpconn(conn);
            return;
        }
    }
    else
    {
        if (conn->response_zbb){
            len = conn->response_zbb->len - conn->response_i;
            if (len > 1400) len = 1400;
            ret = send(conn->sock, conn->response_zbb->buf + conn->response_i, len, 0);
            if (ret <= 0){
                g_ptr_array_remove(httpd->conns, conn);
                free_httpconn(conn);
                return;
            }
            conn->response_i += ret;
            if (conn->response_i >= conn->response_zbb->len){
                g_ptr_array_remove(httpd->conns, conn);
                free_httpconn(conn);
                return;
            }
        }
    }
}

void httpd_header(struct httpconn *conn, int status, char *contenttype){
    char *ss;
    if (!contenttype) contenttype = "text/html; charset=iso-8859-2";
    switch (status){
        case 200: ss = "OK"; break;
        case 405: ss = "Not Found"; break;
        case 500: ss = "Internal server error"; break;
        default: ss = "Unknown status"; break;
    }

    g_string_printf(conn->response, "HTTP/1.1 %d %s\r\n", status, ss);
    g_string_append_printf(conn->response, "Server: Tucnak-%s %s\r\n", Z_PLATFORM, PACKAGE_VERSION);
    g_string_append_printf(conn->response, "Connection: close\r\n");
    g_string_append_printf(conn->response, "Content-Type: %s\r\n", contenttype);
    g_string_append_printf(conn->response, "\r\n");

}

static void httpd_get_style_css(struct httpconn *conn){
    g_string_append(conn->response, 
"body {\n"
"    font-size: 12pt;\n"
/*"    font-family: Tahoma, Helvetica, Arial, sans-serif}\n"*/
"    font-family: Courier New, Courier-new, sans-serif}\n"

"a {\n"
"    font-weight: bold}\n"

"h1, h2, h3, h4 {\n"
"    font-family: Tahoma, Helvetica, Arial, sans-serif;\n"
"    color: #f8f8f8};\n"

"h2 {\n"
"    margin-top: 20;\n"
"    margin-bottom: 5;\n"
"    margin-left:10 }\n"
/*
input {                                                                        
    padding: 2px;
    margin: 2px;
    font-weight: normal; 
    font-size: 12px; 
    color: #000000; 
    font-family: sans-serif}                                                                               

li {
    margin-bottom: 20px
}

p.stress{
    font-style: italic}
      */
"table.qsos {\n"
"    border: black 2px solid;\n"
"    padding: 5px;\n"
"    background: #606060}\n"

"th {\n"
"    padding-left: 5px;\n"
"    padding-right: 5px}\n"

"td {\n"
"    padding-left: 5px;\n"
"    padding-right: 5px}\n"

"#calltest {\n"
"    font-size: 200%}\n"

"#q {\n"
"   text-transform: uppercase;}\n"

);

}

static void httpd_get_mem(struct httpconn *conn, const unsigned char *mem, int len){
    conn->response_zbb = zbinbuf_init();
    zbinbuf_append_bin(conn->response_zbb, (void*)mem, len);
    conn->response_i = 0;
}


static void httpd_get_index(struct httpconn *conn){
    GString *gs2, *title;
    int i;
    GString *gs = conn->response;

    httpd_header(conn, 200, NULL);

    if (!ctest){
        html_header(conn->response, "No contest", HTML_ICON, httpd->refresh, NULL);
        g_string_append_printf(conn->response, "<p>No contest opened</p>\n");
        html_footer(conn->response, 0);
        return;
    }
    
    gs2 = g_string_sized_new(1024);
    title = g_string_sized_new(200);
    
    zg_string_eprintfa("h", title, "%S - %s", ctest->pcall, ctest->tname);
    html_header(gs, title->str, HTML_ICON, httpd->refresh, NULL);

    g_string_append_printf(gs, "<a href=\"complete\">Complete report</a><br>\n");
    g_string_append_printf(gs, "<a href=\"search?q=\">QSO search</a><br>\n");
    for (i = 0; i < ctest->bands->len; i++){
        struct band *b;
        b = (struct band*)g_ptr_array_index(ctest->bands, i);

        zg_string_eprintfa("h", gs, "<a name=\"%c\"></a><h2>Band %s</h2>\n", b->bandchar, b->bandname);
        g_string_append_printf(gs, "<p>QSOs: %d<br>\n", b->stats->nqsos);
        g_string_append_printf(gs, "Points: %d<br>\n", b->stats->ntotal);
        g_string_append_printf(gs, "WWLs: %d<br>\n", g_hash_table_size(b->stats->wwls));
        g_string_append_printf(gs, "DXCCs: %d<br>\n", g_hash_table_size(b->stats->dxcs));
        if (b->stats->nqsos){
            g_string_append_printf(gs, "AVG: %5.2f pts/qso<br>\n", (double)b->stats->ntotal/(double)b->stats->nqsos);
        }
        if (b->stats->odxcall){
            zg_string_eprintfa("h", gs, "ODX: %S %S %d km op %S<br>\n", b->stats->odxcall, b->stats->odxwwl, b->stats->odxqrb_int, b->stats->odxoperator);
        }
        g_string_append_printf(gs, "<a href=\"stats/%c\">Statistics</a>\n", b->bandchar);
        g_string_append_printf(gs, "<a href=\"qsos/%c\">QSOs</a><br>\n", b->bandchar);
        g_string_append_printf(gs, "</p>\n");


    }

    html_footer(conn->response, 0);
    g_string_free(gs2, TRUE);
    g_string_free(title, TRUE);
}

void httpd_get_stats(struct httpconn *conn, const char *bandchar_str){
    int i;
    struct band *b;

    httpd_header(conn, 200, NULL);
    if (!ctest){
        html_header(conn->response, VTEXT(T_NO_CTEST), HTML_ICON, httpd->refresh, NULL); 
        g_string_append(conn->response, VTEXT(T_NO_CONTEST_OPENED));
        html_footer(conn->response, 0);
        return;
    }
    b = find_band_by_bandchar(bandchar_str[0]);
    if (!b){
        html_header(conn->response, VTEXT(T_BAND_NOT_FOUND), HTML_ICON, httpd->refresh, NULL); 
        g_string_append_printf(conn->response, VTEXT(T_BAND_NOT_FOUND), bandchar_str[0]);
        html_footer(conn->response, 0);
        return;
    }

    html_header(conn->response, VTEXT(T_STATS), HTML_ICON, httpd->refresh, NULL); 
    g_string_append_printf(conn->response, "<pre>");
    for (i = 0; i < b->statsfifo1->items->len ; i++){
        char *s = (char*)g_ptr_array_index(b->statsfifo1->items, i);
        g_string_append_printf(conn->response, "%s\n", s);
    }
    g_string_append_printf(conn->response, "</pre>");
    html_footer(conn->response, 0);

}

void httpd_get_qsos(struct httpconn *conn, const char *bandchar_str){
    struct band *b;
    struct config_band *confb;
    char bandchar;
    struct subwin *map, *chart;
    
    httpd_header(conn, 200, NULL);
    if (!ctest){
        html_header(conn->response, VTEXT(T_NO_CTEST), HTML_ICON, httpd->refresh, NULL); 
        g_string_append(conn->response, VTEXT(T_NO_CONTEST_OPENED));
        html_footer(conn->response, 0);
        return;
    }
    bandchar = z_char_uc(bandchar_str[0]);
    b = find_band_by_bandchar(bandchar);
    confb = get_config_band_by_bandchar(bandchar);
    if (!b || !confb){
        html_header(conn->response, VTEXT(T_BAND_NOT_FOUND), HTML_ICON, httpd->refresh, NULL); 
        g_string_append_printf(conn->response, VTEXT(T_BAND_C_NOT_FOUND), bandchar_str[0]);
        html_footer(conn->response, 0);
        return;
    }

    map = sw_raise_or_new(SWT_MAP);
    chart = sw_raise_or_new(SWT_CHART);

    if (map || chart){
        html_header(conn->response, "QSOs", HTML_ICON, httpd->refresh, NULL); 
        html_band_header(conn->response, b, confb, HTML_ICON | HTML_IMG_ROOT, map, chart);
        html_qsos(conn->response, b, confb);
        html_band_footer(conn->response);
        html_footer(conn->response, 0);
    }
}

void httpd_get_complete(struct httpconn *conn){
    httpd_header(conn, 200, NULL);
    if (!ctest){
        html_header(conn->response, VTEXT(T_NO_CTEST), HTML_ICON, httpd->refresh, NULL); 
        g_string_append(conn->response, VTEXT(T_NO_CONTEST_OPENED));
        html_footer(conn->response, 0);
        return;
    }
    html_complete(conn->response, HTML_IMG_ROOT);
}

#ifdef Z_HAVE_SDL
void httpd_get_map(struct httpconn *conn, char bandchar){
    struct band *band;
    struct subwin *map;
    int ret, oldaa;


    band = find_band_by_bandchar(bandchar);
    if (!band){
        httpd_header(conn, 404, NULL);
        return;
    }
    map = sw_raise_or_new(SWT_MAP);
    if (!map || !map->screen) {
        httpd_header(conn, 404, NULL);
        return;
    }

    oldaa = zsdl->antialiasing;
    if (zsdl->antialiasing_supported) zsdl->antialiasing = 1;

    map->gdirty = 1;
    map_for_photo(map, band, 0);
    map_recalc_gst(map, band);
    sw_map_redraw(map, band, HTML_FOR_PHOTO);

    conn->response_zbb = zbinbuf_init();
    ret = zpng_save(map->screen, NULL, conn->response_zbb);
    if (ret){
        zbinbuf_free(conn->response_zbb);
        conn->response_zbb = NULL;
        httpd_header(conn, 500, NULL);
        zsdl->antialiasing = oldaa;
        return;
    }
    httpd_header(conn, 200, "image/png");

    zsdl->antialiasing = oldaa;
    map_recalc_gst(map, aband);
}

void httpd_get_chart(struct httpconn *conn, char bandchar){
    struct band *band;
    struct subwin *chart;
    int ret;

    band = find_band_by_bandchar(bandchar);
    if (!band){
        httpd_header(conn, 404, NULL);
        return;
    }
    chart = sw_raise_or_new(SWT_CHART);
    if (!chart || !chart->screen) {
        httpd_header(conn, 404, NULL);
        return;
    }

    chart->gdirty = 1;
    sw_chart_recalc_extremes(chart, band);
    sw_chart_redraw(chart, band, HTML_FOR_PHOTO);

    conn->response_zbb = zbinbuf_init();
    ret = zpng_save(chart->screen, NULL, conn->response_zbb);
    if (ret){
        zbinbuf_free(conn->response_zbb);
        conn->response_zbb = NULL;
        httpd_header(conn, 500, NULL);
        return;
    }
    httpd_header(conn, 200, "image/png");
    sw_chart_recalc_extremes(chart, aband);
}

#endif

void httpd_search_form(struct httpconn *conn){
    int i, nqsos = 0;
    time_t now;
    struct tm *tm;
    GString *gs = conn->response;

    zg_string_eprintfa("h", gs,  
"<div id=\"calltest\">%s - %s</div>\n"
"<form action=\"search\" method=\"get\">"
"<input type=\"text\" name=\"q\" id=\"q\" size=\"40\">&nbsp; "
"<input type=\"submit\" value=\"Search\" class=\"button\">"
"</form>\n", 
    ctest->pcall, ctest->tname);

    
    for (i = 0; i < ctest->bands->len; i++){
        struct band *b = (struct band *)g_ptr_array_index(ctest->bands, i);
        nqsos += b->stats->nqsos;
    }
    now = time(NULL);
    tm = gmtime(&now);

    zg_string_eprintfa("h", gs,  
"<div class=\"info\">%d %s | "
"%d.%d.%d | %02d:%02d UTC</div> "
"<br><br><br>",
    nqsos, 
    VTEXT(T_QSOS_IN_DATABASE),
    tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min);
}

/*void httpd_search(struct httpconn *conn){
    GString *gs = conn->response;

    httpd_header(conn, 200, NULL);

    if (!ctest){
        html_header(conn->response, "No contest", HTML_ICON | HTML_CENTER, NULL); 
        g_string_append_printf(conn->response, "<p>No contest opened</p>\n");
        g_string_append(gs, "<br><br><br>\n");
        html_footer(conn->response, HTML_CENTER);
        return;
    }



    html_header(gs, "Tucnak online log", HTML_ICON | HTML_CENTER, "onLoad=\"document.getElementById('q').focus();\"");
    httpd_search_form(conn);
    g_string_append(gs, "<br><br><br>\n");
    html_footer(conn->response, HTML_CENTER);
} */

void httpd_search(struct httpconn *conn, char *call){
    int i, j, found = 0;
    char *c;
    GString *gs = conn->response;
    GString *call2 = g_string_new("");

    for (c = call; *c != '\0'; c++){
        if (*c != '%') {
            g_string_append_c(call2, *c);
            continue;
        }
        if (strncmp(c, "%2F", 3) == 0){
            g_string_append(call2, "/");
            c += 2;
            continue;
        }
        // skip other %..
    }

    httpd_header(conn, 200, NULL);

    if (!ctest){
        html_header(conn->response, VTEXT(T_NO_CTEST), HTML_ICON|HTML_CENTER, httpd->refresh, NULL);
        g_string_append(conn->response, VTEXT(T_NO_CONTEST_OPENED));
        g_string_append(gs, "<br><br><br>\n");
        html_footer(conn->response, HTML_CENTER);
        return;
    }
    html_header(gs, VTEXT(T_TUCNAK_ONLINE_LOG), HTML_ICON | HTML_CENTER, httpd->refresh, "onLoad=\"document.getElementById('q').focus();\""); 
    httpd_search_form(conn);

    /*zg_string_eprintfa("h", gs,  
"<div id=\"calltest\">%s - %s</div>\n",
    ctest->pcall, ctest->tname);*/

    if (strlen(call)){
        g_string_append_printf(gs, 
            "<table class=\"qsos\">"
            "<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>", VTEXT(T_BAND2), VTEXT(T_DATE2), VTEXT(T_UTC), VTEXT(T_CALL), VTEXT(T_INFO));

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

            for (j = 0; j < b->qsos->len; j++){
                struct qso *q = (struct qso *)g_ptr_array_index(b->qsos, j);
                if (strcasecmp(call2->str, q->callsign) != 0) continue;

                zg_string_eprintfa("h", gs, "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>",
                    b->pband, q->date_str, q->time_str, q->callsign);
                if (q->error) g_string_append(gs, "ERROR ");
                if (q->dupe) g_string_append(gs, "DUPE ");
                g_string_append(gs, "</td></tr>\n");
                found++;
            }
        }

        g_string_append(gs, "</table>");
        if (!found) g_string_append_printf(gs, "<p><b>%s</b></p>\n", VTEXT(T_CALL_NOT_FOUND));
    }
    g_string_append(gs, "<br><br><br>\n");
    html_footer(conn->response, HTML_CENTER);
    g_string_free(call2, TRUE);
}

void httpd_get(struct httpconn *conn){
    char *c, *page, *tmp, *page0, *page1/*, *page2*/;
    
    tmp = NULL;
    page = conn->request->str + strlen("GET") + 1;
    while (*page == ' ') page++;
    c = strchr(page, ' ');
    if (c != NULL) *c = '\0';

    if (strcmp(page, "/") == 0){
        if (cfg->httpd_show_priv){
            httpd_get_index(conn);
        }else{
            httpd_search(conn, "");
        }
        return;
    }

    page0 = strtok_r(page, "/", &tmp);
    page1 = strtok_r(NULL, "/", &tmp);
    //page2 = strtok_r(NULL, "/", &tmp);
    if (cfg->httpd_show_priv){
        // private web - all informations
        if (strcmp(page0, "style.css") == 0){
            httpd_get_style_css(conn);
        }else if (strcmp(page0, "tucnak64.png") == 0){
            httpd_get_mem(conn, icon_tucnak64, sizeof(icon_tucnak64));
        }else if (strcmp(page0, "stats") == 0){
            httpd_get_stats(conn, page1);
        }else if (strcmp(page0, "qsos") == 0){
            httpd_get_qsos(conn, page1);
        }else if (strcmp(page0, "complete") == 0){
            httpd_get_complete(conn);
        }else if (strncmp(page0, "map", 3) == 0){
#ifdef Z_HAVE_SDL
            httpd_get_map(conn, page0[3]);
#else
            httpd_header(conn, 404, NULL);
#endif
        }else if (strncmp(page0, "chart", 5) == 0){
#ifdef Z_HAVE_SDL
            httpd_get_chart(conn, page0[5]);
#else
            httpd_header(conn, 404, NULL);
#endif
        }else if (strncmp(page, "/search?q=", 10) == 0){
            httpd_search(conn, page + 10);
        }else{
            httpd_get_index(conn);
        }
    }else{
        // public web - only insensitive informations
        if (strcmp(page0, "style.css") == 0){
            httpd_get_style_css(conn);
        }else if (strcmp(page0, "tucnak64.png") == 0){
            httpd_get_mem(conn, icon_tucnak64, sizeof(icon_tucnak64));
        }else if (strncmp(page, "/search?q=", 10) == 0){
            httpd_search(conn, page + 10);
        }else{
            httpd_search(conn, "");
        }
    }
}

