/* termtcp_gui.cpp — FLTK GUI for TermTCP
 * BPQTermTCP protocol client for BPQ32 packet radio nodes.
 * Compile: see Makefile target 'gui'
 */

#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Text_Display.H>
#include <FL/Fl_Text_Buffer.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Tile.H>
#include <FL/Fl_Hold_Browser.H>
#include <FL/Fl_Window.H>
#include <FL/fl_ask.H>

extern "C" {
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
}

#define GUI_VERSION "0.0.60"
#define MAX_HOSTS   10
#define RECV_SZ     4096
#define MON_FBUF_SZ 4096

/* ── Config ──────────────────────────────────────────────── */

struct HostEntry {
    char host[100];
    char port[10];
    char username[80];
    char password[80];
    char name[50];
};

static HostEntry hosts[MAX_HOSTS];
static int       num_hosts  = 0;
static char      cfg_path[512];

static int  mon_visible = 1;
static int  mon_tx      = 1;
static int  mon_com     = 1;
static int  mon_nodes   = 0;
static int  mon_colour  = 1;
static int  mon_ui_only = 1;
static int  port_mask   = 0xFFFF;
static char port_names[32][32];

/* ── Session state ───────────────────────────────────────── */

static int    g_sock      = -1;
static int    g_connected = 0;
static int    g_host_idx  = -1;
static time_t g_conn_time = 0;
static int    g_in_mon    = 0;
static char   g_mon_fbuf[MON_FBUF_SZ];
static int    g_mon_flen  = 0;

/* ── GUI widgets ─────────────────────────────────────────── */

static Fl_Double_Window *main_win     = nullptr;
static Fl_Text_Display  *out_disp     = nullptr;
static Fl_Text_Display  *mon_disp     = nullptr;
static Fl_Text_Buffer   *out_buf      = nullptr;
static Fl_Text_Buffer   *out_styl     = nullptr;
static Fl_Text_Buffer   *mon_buf      = nullptr;
static Fl_Text_Buffer   *mon_styl     = nullptr;
static Fl_Input         *inp          = nullptr;
static Fl_Box           *status_lbl   = nullptr;
static Fl_Menu_Bar      *menu_bar     = nullptr;

/* Style table — index from 'A'.
 * A=default  B=green(monitor)  C=red(error)
 * D=yellow(status)  E=cyan(info)  F=magenta  */
static const Fl_Text_Display::Style_Table_Entry STYLES[] = {
    { FL_FOREGROUND_COLOR,       FL_COURIER, 13 }, /* A */
    { fl_rgb_color(0,180,0),     FL_COURIER, 13 }, /* B */
    { FL_RED,                    FL_COURIER, 13 }, /* C */
    { fl_rgb_color(180,140,0),   FL_COURIER, 13 }, /* D */
    { fl_rgb_color(0,190,210),   FL_COURIER, 13 }, /* E */
    { FL_MAGENTA,                FL_COURIER, 13 }, /* F */
};
#define N_STYLES 6

/* ── Forward declarations ────────────────────────────────── */

static void tcp_disconnect(void);
static void update_status(void);
static void cb_timer(void *);

/* ── Config I/O ──────────────────────────────────────────── */

static void load_config(void)
{
    const char *home = getenv("HOME");
    if (!home) home = "/tmp";
    snprintf(cfg_path, sizeof(cfg_path), "%s/.BPQTermTCP.ini", home);

    FILE *fp = fopen(cfg_path, "r");
    if (!fp) return;

    char line[256];
    int  hi = -1;
    while (fgets(line, sizeof(line), fp)) {
        /* strip trailing newline */
        int l = (int)strlen(line);
        while (l > 0 && (line[l-1]=='\n'||line[l-1]=='\r'||line[l-1]==' '))
            line[--l] = '\0';

        if (line[0] == '[') {
            /* section header */
            if (strncmp(line, "[Host", 5) == 0 && num_hosts < MAX_HOSTS) {
                hi = num_hosts++;
                memset(&hosts[hi], 0, sizeof(HostEntry));
            } else {
                hi = -1;
            }
            continue;
        }

        char *eq = strchr(line, '=');
        if (!eq) continue;
        *eq = '\0';
        const char *key = line, *val = eq + 1;

        if (hi >= 0) {
            if      (!strcmp(key,"Host"))     { strncpy(hosts[hi].host,     val, 99);  }
            else if (!strcmp(key,"Port"))     { strncpy(hosts[hi].port,     val,  9);  }
            else if (!strcmp(key,"Username")) { strncpy(hosts[hi].username, val, 79);  }
            else if (!strcmp(key,"Password")) { strncpy(hosts[hi].password, val, 79);  }
            else if (!strcmp(key,"Name"))     { strncpy(hosts[hi].name,     val, 49);  }
        } else {
            if      (!strcmp(key,"MonTX"))      mon_tx      = atoi(val);
            else if (!strcmp(key,"MonCom"))     mon_com     = atoi(val);
            else if (!strcmp(key,"MonNodes"))   mon_nodes   = atoi(val);
            else if (!strcmp(key,"MonUIOnly"))  mon_ui_only = atoi(val);
            else if (!strcmp(key,"MonVisible")) mon_visible = atoi(val);
            else if (!strcmp(key,"PortMask"))   port_mask   = atoi(val);
            else if (strncmp(key,"PortName",8)==0) {
                int pn = atoi(key+8);
                if (pn >= 1 && pn <= 32)
                    snprintf(port_names[pn-1], 32, "%s", val);
            }
        }
    }
    fclose(fp);
}

/* ── Text display helpers ────────────────────────────────── */

static void append_styled(Fl_Text_Buffer *tbuf, Fl_Text_Buffer *sbuf,
                           const char *text, int len, char style)
{
    if (len <= 0) return;

    /* Build a style string the same length as text (minus any stripped
     * BPQ 0x1B colour-escape pairs which we skip). */
    char *tmp = (char *)malloc(len + 1);
    char *sty = (char *)malloc(len + 1);
    if (!tmp || !sty) { free(tmp); free(sty); return; }

    int ti = 0;
    char cur = style;
    for (int i = 0; i < len; ) {
        unsigned char c = (unsigned char)text[i];
        if (c == 0x1B && i + 1 < len) {
            /* BPQ colour byte — map to style char */
            unsigned char cb = (unsigned char)text[i+1];
            int idx = (int)cb - 10;
            if      (idx <  0)  cur = 'A';
            else if (idx <  4)  cur = 'E';   /* dark blue → cyan */
            else if (idx < 20)  cur = 'B';   /* green */
            else if (idx < 60)  cur = 'C';   /* reds */
            else                cur = 'D';   /* bright → yellow */
            i += 2;
        } else if (c == '\r') {
            i++;   /* skip bare CR */
        } else {
            tmp[ti] = (char)c;
            sty[ti] = cur;
            ti++;
            i++;
        }
    }
    tmp[ti] = '\0';
    sty[ti] = '\0';

    tbuf->append(tmp);
    sbuf->append(sty);

    free(tmp);
    free(sty);
}

static void out_append(const char *text, int len, char style = 'A')
{
    append_styled(out_buf, out_styl, text, len, style);
    /* scroll to bottom */
    out_disp->scroll(out_buf->count_lines(0, out_buf->length()), 0);
}

static void mon_append(const char *text, int len, char style = 'B')
{
    if (!mon_visible) return;
    append_styled(mon_buf, mon_styl, text, len, style);
    mon_disp->scroll(mon_buf->count_lines(0, mon_buf->length()), 0);
}

/* ── BPQ protocol ────────────────────────────────────────── */

static void send_trace_options(bool with_ports)
{
    if (g_sock < 0) return;
    char buf[80];
    int  len;
    if (with_ports)
        len = snprintf(buf, sizeof(buf),
                       "\\\\\\\\%x %x %x %x %x %x 0 1\r",
                       port_mask, mon_tx, mon_com, mon_nodes, mon_colour, mon_ui_only);
    else
        len = snprintf(buf, sizeof(buf),
                       "\\\\\\\\%x %x %x %x %x %x\r",
                       port_mask, mon_tx, mon_com, mon_nodes, mon_colour, mon_ui_only);
    send(g_sock, buf, len, 0);
}

static void parse_portinfo(const char *data, int len)
{
    const char *p = data, *e = data + len;
    int nports = 0;
    while (p < e && *p >= '0' && *p <= '9') nports = nports*10 + (*p++ - '0');
    if (p >= e || *p != '|') return;
    p++;
    for (int i = 0; i < nports && p < e; i++) {
        int n = 0;
        while (p < e && *p >= '0' && *p <= '9') n = n*10 + (*p++ - '0');
        if (p >= e || *p != ' ') break;
        p++;
        const char *desc = p;
        while (p < e && *p != '|') p++;
        int dlen = (int)(p - desc);
        if (n >= 1 && n <= 32 && dlen > 0) {
            int copy = dlen < 31 ? dlen : 31;
            memcpy(port_names[n-1], desc, copy);
            port_names[n-1][copy] = '\0';
        }
        if (p < e && *p == '|') p++;
    }
}

static void bpq_process(const char *buf, int len)
{
    const char *p   = buf;
    const char *end = buf + len;

    while (p < end) {
        if (!g_in_mon) {
            const char *ff = (const char *)memchr(p, 0xFF, end - p);
            if (!ff) {
                out_append(p, (int)(end - p));
                break;
            }
            if (ff > p)
                out_append(p, (int)(ff - p));
            p = ff + 1;
            if (p < end && (unsigned char)*p == 0xFF) {
                parse_portinfo(p + 1, (int)(end - p - 1));
                p = end;
            } else {
                g_in_mon  = 1;
                g_mon_flen = 0;
            }
        } else {
            const char *fe      = (const char *)memchr(p, 0xFE, end - p);
            int         seg_len = fe ? (int)(fe - p) : (int)(end - p);
            if (seg_len > 0) {
                int room = MON_FBUF_SZ - 1 - g_mon_flen;
                int add  = seg_len < room ? seg_len : room;
                memcpy(g_mon_fbuf + g_mon_flen, p, add);
                g_mon_flen += add;
            }
            if (fe) {
                if (g_mon_flen > 0) {
                    /* NODES filter */
                    int is_nodes = 0;
                    for (int i = 0; i <= g_mon_flen - 6 && !is_nodes; i++)
                        if (memcmp(g_mon_fbuf + i, ">NODES", 6) == 0) is_nodes = 1;
                    if (mon_nodes || !is_nodes) {
                        g_mon_fbuf[g_mon_flen] = '\n';
                        mon_append(g_mon_fbuf, g_mon_flen + 1);
                    }
                }
                g_mon_flen = 0;
                g_in_mon   = 0;
                p = fe + 1;
            } else {
                break;
            }
        }
    }
}

/* ── Socket handling ─────────────────────────────────────── */

static void on_socket_ready(int /*fd*/, void * /*data*/)
{
    char buf[RECV_SZ];
    int  n = recv(g_sock, buf, sizeof(buf) - 1, 0);
    if (n > 0) {
        bpq_process(buf, n);
    } else if (n == 0 || (n < 0 && errno != EAGAIN)) {
        out_append("\n*** Connection closed by server ***\n", 37, 'C');
        tcp_disconnect();
    }
}

static int tcp_connect_to(int idx)
{
    if (idx < 0 || idx >= num_hosts) return -1;

    struct addrinfo hints, *res = nullptr;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if (getaddrinfo(hosts[idx].host, hosts[idx].port, &hints, &res) != 0 || !res)
        return -1;

    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (fd < 0) { freeaddrinfo(res); return -1; }

    if (connect(fd, res->ai_addr, res->ai_addrlen) != 0) {
        close(fd); freeaddrinfo(res); return -1;
    }
    freeaddrinfo(res);
    return fd;
}

static void tcp_disconnect(void)
{
    if (g_sock >= 0) {
        Fl::remove_fd(g_sock);
        close(g_sock);
        g_sock = -1;
    }
    g_connected = 0;
    g_host_idx  = -1;
    g_conn_time = 0;
    g_in_mon    = 0;
    g_mon_flen  = 0;
    update_status();
}

static void tcp_send(const char *text, int len)
{
    if (g_sock < 0 || !g_connected) return;
    send(g_sock, text, len, 0);
}

/* ── Status bar ──────────────────────────────────────────── */

static void update_status(void)
{
    static char buf[160];
    if (!g_connected || g_host_idx < 0) {
        snprintf(buf, sizeof(buf), "  Not connected");
    } else {
        time_t elapsed = time(nullptr) - g_conn_time;
        int    h = (int)(elapsed / 3600);
        int    m = (int)((elapsed % 3600) / 60);
        int    s = (int)(elapsed % 60);
        time_t now = time(nullptr);
        struct tm *utc = gmtime(&now);
        snprintf(buf, sizeof(buf),
                 "  %s (%s:%s)   %02d:%02d:%02d   %02d:%02d UTC",
                 hosts[g_host_idx].name,
                 hosts[g_host_idx].host, hosts[g_host_idx].port,
                 h, m, s,
                 utc->tm_hour, utc->tm_min);
    }
    status_lbl->copy_label(buf);
    status_lbl->redraw();
}

static void cb_timer(void *)
{
    update_status();
    Fl::repeat_timeout(1.0, cb_timer, nullptr);
}

/* ── Connect dialog ──────────────────────────────────────── */

static int g_dlg_result = -1;

static void cb_dlg_connect(Fl_Widget *w, void *browser_ptr)
{
    Fl_Hold_Browser *br = (Fl_Hold_Browser *)browser_ptr;
    int sel = br->value();
    g_dlg_result = (sel > 0) ? sel - 1 : -1;
    w->window()->hide();
}

static void cb_dlg_cancel(Fl_Widget *w, void *)
{
    g_dlg_result = -1;
    w->window()->hide();
}

static void cb_dlg_dblclick(Fl_Widget *w, void *)
{
    Fl_Hold_Browser *br = (Fl_Hold_Browser *)w;
    if (br->value() > 0) {
        g_dlg_result = br->value() - 1;
        br->window()->hide();
    }
}

static int show_connect_dialog(void)
{
    if (num_hosts == 0) {
        fl_message("No hosts configured.\nEdit %s to add BPQ nodes.", cfg_path);
        return -1;
    }

    Fl_Window *dlg = new Fl_Window(320, 240, "Connect to Host");
    dlg->begin();

    Fl_Hold_Browser *br = new Fl_Hold_Browser(10, 10, 300, 170);
    for (int i = 0; i < num_hosts; i++) {
        char lbl[140];
        snprintf(lbl, sizeof(lbl), "%s  (%s:%s)",
                 hosts[i].name[0] ? hosts[i].name : hosts[i].host,
                 hosts[i].host, hosts[i].port);
        br->add(lbl);
    }
    br->select(1);
    br->callback(cb_dlg_dblclick);
    br->when(FL_WHEN_CHANGED);  /* double-click triggers when() */

    Fl_Return_Button *btn_ok  = new Fl_Return_Button(130, 195, 90, 30, "Connect");
    btn_ok->callback(cb_dlg_connect, br);

    Fl_Button *btn_cancel = new Fl_Button(230, 195, 80, 30, "Cancel");
    btn_cancel->callback(cb_dlg_cancel);

    dlg->end();
    dlg->set_modal();
    g_dlg_result = -1;
    dlg->show();
    while (dlg->shown()) Fl::wait();
    int result = g_dlg_result;
    delete dlg;
    return result;
}

/* ── Menu callbacks ──────────────────────────────────────── */

static void do_connect(int idx)
{
    if (g_connected) tcp_disconnect();

    char msg[160];
    snprintf(msg, sizeof(msg), "Connecting to %s (%s:%s)...\n",
             hosts[idx].name[0] ? hosts[idx].name : hosts[idx].host,
             hosts[idx].host, hosts[idx].port);
    out_append(msg, (int)strlen(msg), 'E');

    int fd = tcp_connect_to(idx);
    if (fd < 0) {
        snprintf(msg, sizeof(msg), "Connection failed: %s\n", strerror(errno));
        out_append(msg, (int)strlen(msg), 'C');
        return;
    }

    g_sock      = fd;
    g_connected = 1;
    g_host_idx  = idx;
    g_conn_time = time(nullptr);

    Fl::add_fd(g_sock, FL_READ, on_socket_ready, nullptr);
    send_trace_options(true);
    update_status();

    snprintf(msg, sizeof(msg), "Connected.\n");
    out_append(msg, (int)strlen(msg), 'E');
}

static void cb_connect(Fl_Widget *, void *)
{
    int idx = show_connect_dialog();
    if (idx >= 0) do_connect(idx);
}

static void cb_disconnect(Fl_Widget *, void *)
{
    if (!g_connected) { fl_message("Not connected."); return; }
    out_append("*** Disconnected\n", 17, 'C');
    tcp_disconnect();
}

static void cb_quit(Fl_Widget *, void *)
{
    if (g_connected) tcp_disconnect();
    exit(0);
}

static void cb_clear_out(Fl_Widget *, void *)
{
    out_buf->remove(0, out_buf->length());
    out_styl->remove(0, out_styl->length());
}

static void cb_clear_mon(Fl_Widget *, void *)
{
    mon_buf->remove(0, mon_buf->length());
    mon_styl->remove(0, mon_styl->length());
}

static void cb_toggle_mon(Fl_Widget *, void *)
{
    mon_visible = !mon_visible;
    if (mon_visible)
        out_append("[Monitor ON]\n", 13, 'D');
    else
        out_append("[Monitor OFF]\n", 14, 'D');
}

/* ── Input send ──────────────────────────────────────────── */

static void cb_send(Fl_Widget *, void *)
{
    const char *text = inp->value();
    if (!text || text[0] == '\0') return;
    if (!g_connected) {
        out_append("Not connected.\n", 15, 'C');
        inp->value("");
        return;
    }

    /* Echo locally */
    char echo[514];
    int elen = snprintf(echo, sizeof(echo), "> %s\n", text);
    out_append(echo, elen, 'D');

    /* Send to server with CR */
    int tlen = (int)strlen(text);
    char *tbuf = (char *)malloc(tlen + 2);
    if (tbuf) {
        memcpy(tbuf, text, tlen);
        tbuf[tlen]   = '\r';
        tbuf[tlen+1] = '\0';
        tcp_send(tbuf, tlen + 1);
        free(tbuf);
    }
    inp->value("");
}

/* ── Main window ─────────────────────────────────────────── */

static Fl_Menu_Item menu_items[] = {
    { "&File", 0, nullptr, nullptr, FL_SUBMENU },
        { "&Connect...",    FL_CTRL+'k', cb_connect    },
        { "&Disconnect",    FL_CTRL+'d', cb_disconnect },
        { "Clear output",   0,           cb_clear_out  },
        { "Clear monitor",  0,           cb_clear_mon  },
        { "E&xit",          FL_CTRL+'q', cb_quit, nullptr, FL_MENU_DIVIDER },
        { nullptr },
    { "&Monitor", 0, nullptr, nullptr, FL_SUBMENU },
        { "Toggle monitor pane", FL_CTRL+'m', cb_toggle_mon },
        { nullptr },
    { "&Help", 0, nullptr, nullptr, FL_SUBMENU },
        { "About", 0, [](Fl_Widget*, void*){
            fl_message("TermTCP GUI v" GUI_VERSION "\n"
                       "BPQTermTCP client for BPQ32 packet radio nodes.\n"
                       "Config: ~/.BPQTermTCP.ini");
        }},
        { nullptr },
    { nullptr }
};

static void build_window(void)
{
    const int W = 900, H = 650;
    const int MENU_H   = 25;
    const int STATUS_H = 22;
    const int INPUT_H  = 32;
    const int SEND_W   = 70;
    const int MON_FRAC = 3;  /* monitor gets 1/3 of inner height */

    main_win = new Fl_Double_Window(W, H, "TermTCP " GUI_VERSION);
    main_win->begin();
    main_win->color(FL_WHITE);

    /* Menu bar */
    menu_bar = new Fl_Menu_Bar(0, 0, W, MENU_H);
    menu_bar->menu(menu_items);
    menu_bar->color(fl_rgb_color(230,230,230));

    /* Resizable split area */
    int tile_y = MENU_H;
    int tile_h = H - MENU_H - INPUT_H - STATUS_H;
    int out_h  = tile_h - tile_h / MON_FRAC;
    int mon_h  = tile_h / MON_FRAC;

    Fl_Tile *tile = new Fl_Tile(0, tile_y, W, tile_h);

    /* Output display */
    out_disp = new Fl_Text_Display(0, tile_y, W, out_h);
    out_buf  = new Fl_Text_Buffer();
    out_styl = new Fl_Text_Buffer();
    out_disp->buffer(out_buf);
    out_disp->highlight_data(out_styl, STYLES, N_STYLES, 'A', nullptr, 0);
    out_disp->textfont(FL_COURIER);
    out_disp->textsize(13);
    out_disp->color(fl_rgb_color(20, 20, 20));
    out_disp->textcolor(FL_WHITE);
    out_disp->cursor_color(FL_WHITE);
    out_disp->scrollbar_width(14);
    out_disp->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0);

    /* Monitor display */
    mon_disp = new Fl_Text_Display(0, tile_y + out_h, W, mon_h);
    mon_buf  = new Fl_Text_Buffer();
    mon_styl = new Fl_Text_Buffer();
    mon_disp->buffer(mon_buf);
    mon_disp->highlight_data(mon_styl, STYLES, N_STYLES, 'B', nullptr, 0);
    mon_disp->textfont(FL_COURIER);
    mon_disp->textsize(12);
    mon_disp->color(fl_rgb_color(10, 30, 10));
    mon_disp->textcolor(fl_rgb_color(0, 220, 0));
    mon_disp->cursor_color(fl_rgb_color(0,220,0));
    mon_disp->scrollbar_width(14);

    tile->end();

    /* Input row */
    int inp_y = tile_y + tile_h;
    inp = new Fl_Input(0, inp_y, W - SEND_W, INPUT_H);
    inp->textfont(FL_COURIER);
    inp->textsize(13);
    inp->color(fl_rgb_color(30,30,30));
    inp->textcolor(FL_WHITE);
    inp->cursor_color(FL_WHITE);
    inp->when(FL_WHEN_ENTER_KEY_ALWAYS);
    inp->callback(cb_send);

    Fl_Return_Button *send_btn = new Fl_Return_Button(W - SEND_W, inp_y, SEND_W, INPUT_H, "Send");
    send_btn->callback(cb_send);

    /* Status bar */
    int st_y = inp_y + INPUT_H;
    status_lbl = new Fl_Box(0, st_y, W, STATUS_H, "  Not connected");
    status_lbl->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
    status_lbl->box(FL_FLAT_BOX);
    status_lbl->color(fl_rgb_color(0, 0, 140));
    status_lbl->labelcolor(FL_WHITE);
    status_lbl->labelfont(FL_HELVETICA);
    status_lbl->labelsize(12);

    main_win->resizable(tile);
    main_win->end();
    main_win->callback([](Fl_Widget *, void *) {
        if (g_connected) tcp_disconnect();
        exit(0);
    });
}

/* ── main ────────────────────────────────────────────────── */

int main(int argc, char **argv)
{
    /* Init port names */
    for (int i = 0; i < 32; i++)
        snprintf(port_names[i], 32, "Port %d", i+1);

    load_config();
    Fl::scheme("gtk+");

    build_window();
    main_win->show(argc, argv);

    /* Status timer */
    Fl::add_timeout(1.0, cb_timer, nullptr);

    /* Welcome banner */
    const char *banner = "TermTCP GUI v" GUI_VERSION
                         " — File > Connect to get started\n";
    out_append(banner, (int)strlen(banner), 'E');

    /* Auto-connect if exactly one host and -c flag or single host */
    int auto_idx = -1;
    for (int i = 1; i < argc; i++) {
        if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--connect") == 0)
            && i+1 < argc)
            auto_idx = atoi(argv[++i]);
    }
    if (auto_idx < 0 && num_hosts == 1) auto_idx = 0;

    if (auto_idx >= 0 && auto_idx < num_hosts)
        do_connect(auto_idx);

    return Fl::run();
}
