#include <menu.h>
#include <menu_private.h>
#include <menu_net.h>
#include <gui.h>
#include <gui/button.h>
#include <gui/container.h>
#include <gui/checkbox.h>
#include <gui/label.h>
#include <gui/line_edit.h>
#include <gui/rounded_rect.h>
#include <net.h>
#include <net/serial.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>

struct join_menu
{
    struct gui_container cnt, bcnt;
    struct menu_net_gui net;
    struct gui_label connecting;
    struct gui_button back, connect;

    enum
    {
        IDLE,
        CONNECT,
        CONNECTING,
        CONNECTED,
        CONNECT_FAILED
    } state;

    struct net_host *host;
    net_peer peer;
};

static void on_connected(const net_peer p, void *const arg)
{
    struct join_menu *const m = arg;

    m->state = CONNECTED;
    m->peer = p;
}

static void on_disconnected(const net_peer p, void *const arg)
{
    struct join_menu *const m = arg;

    m->state = CONNECT_FAILED;
    m->host = NULL;
}

static int on_connect(struct join_menu *const m)
{
    union net_connect c;

    if (menu_net_connect(&m->net, &c))
    {
        fprintf(stderr, "%s: menu_net_connect failed\n", __func__);
        return -1;
    }

    c.common.ev = (const struct net_event)
    {
        .connected = on_connected,
        .disconnected = on_disconnected,
        .arg = m
    };

    if (!(m->host = net_connect(&c)))
    {
        fprintf(stderr, "%s: net_connect failed\n", __func__);
        on_disconnected(NULL, m);
        return -1;
    }

    return 0;
}

static int update_socket(struct join_menu *const m)
{
    if (m->host && net_update(m->host))
        return -1;

    switch (m->state)
    {
        case CONNECT:
            if (on_connect(m))
                m->state = CONNECT_FAILED;
            else
            {
                m->state = CONNECTING;
                m->connecting.text = "Connecting...";
                m->connecting.common.hidden = false;
                break;
            }

            /* Fall through. */
        case CONNECT_FAILED:
            net_close(m->host);
            m->connecting.text = "Failed to connect";
            m->connecting.common.hidden = false;
            m->host = NULL;
            m->state = IDLE;
            break;

        default:
            break;
    }

    return 0;
}

static void hide_elements(struct menu_net_gui *const m)
{
    if (m->domain == NET_DOMAIN_IPV4)
    {
        struct menu_ipv4_gui *const g = &m->gui.ipv4;

        g->announce_cnt.common.hidden = true;
    }
}

static int update(struct menu_common *const c, void *const arg)
{
    struct join_menu *const m = arg;

    m->bcnt.common.y = screen_h - 40;
    menu_net_gui_update(&m->net);
    update_socket(m);
    hide_elements(&m->net);

    if (gui_update(&m->cnt.common, &c->p, &c->cam, &c->in)
        || gui_update(&m->bcnt.common, &c->p, &c->cam, &c->in))
        return -1;

    return 0;
}

static int render(const struct menu_common *const c, void *const arg)
{
    const struct join_menu *const m = arg;

    if (gui_render(&m->cnt.common)
        || gui_render(&m->bcnt.common))
        return -1;

    return 0;
}

static void on_connect_pressed(void *const arg)
{
    struct join_menu *const m = arg;

    if (m->state == IDLE)
        m->state = CONNECT;
}

static void init_footer_gui(struct join_menu *const m, bool *const back)
{
    {
        struct gui_label *const l = &m->connecting;

        gui_label_init(l);
        l->common.hcentered = true;
        l->common.hidden = true;
        gui_add_child(&m->cnt.common, &l->common);
    }

    {
        struct gui_container *const c = &m->bcnt;

        gui_container_init(c);
        c->common.hcentered = true;
        c->mode = GUI_CONTAINER_MODE_H;
    }

    {
        struct gui_button *const b = &m->connect;

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->u.type1.label.text = "Connect";
        b->u.type1.w = 140;
        b->common.vcentered = true;
        b->on_pressed = on_connect_pressed;
        b->arg = m;
        b->common.vcentered = true;
        gui_add_child(&m->bcnt.common, &b->common);
    }

    {
        struct gui_button *const b = &m->back;

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->u.type1.label.text = "Back";
        b->u.type1.w = 100;
        b->common.vcentered = true;
        b->on_pressed = menu_on_pressed;
        b->arg = back;
        b->common.vcentered = true;
        gui_add_child(&m->bcnt.common, &b->common);
    }
}

int menu_join(struct menu_common *const c)
{
    int ret = -1;
    struct join_menu m = {0};
    bool back = false;

    gui_container_init(&m.cnt);
    m.cnt.common.hcentered = true;
    m.cnt.common.vcentered = true;
    m.cnt.mode = GUI_CONTAINER_MODE_V;
    m.cnt.spacing = 2;

    if (menu_net_gui_init(&m.net))
        return -1;

    gui_add_child(&m.cnt.common, &m.net.cnt.common);
    init_footer_gui(&m, &back);

    while (!back && !c->p.common.exit && m.state != CONNECTED)
        if (menu_update(c, update, render, &m))
            goto end;

    if (m.state == CONNECTED && menu_gamecfg(c, m.host, m.peer))
        goto end;

    ret = 0;

end:
    net_close(m.host);
    gui_deinit(&m.net.cnt.common, &c->in);
    return ret;
}
