#include <menu.h>
#include <menu_net.h>
#include <game.h>
#include <gui.h>
#include <gui/container.h>
#include <gui/label.h>
#include <gui/button.h>
#include <gui/line_edit.h>
#include <gui/checkbox.h>
#include <net.h>
#include <net/serial.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static const enum net_domain domains[] =
{
    NET_DOMAIN_IPV4,
    NET_DOMAIN_SERIAL
};

static void on_relay_pressed(const bool active, void *const arg)
{
    struct menu_ipv4_gui *const m = arg;

    m->next_page_btn.common.hidden = !active;
}

static void init_ipv4_gui(struct menu_ipv4_gui *const m,
    char *const addr, const size_t addrlen,
    char *const port, const size_t portlen)
{
    {
        struct gui_container *const c = &m->cnt;

        gui_container_init(c);
        c->mode = GUI_CONTAINER_MODE_V;
        c->spacing = 2;
    }

    {
        struct gui_label *const l = &m->address;

        gui_label_init(l);
        l->common.hcentered = true;
        l->text = "Address:";
        gui_add_child(&m->cnt.common, &l->common);
    }

    {
        struct gui_line_edit *const le = &m->address_le;

        gui_line_edit_init(le, addr, addrlen);
        le->w = 280;
        le->common.hcentered = true;
        gui_add_child(&m->cnt.common, &le->common);
    }

    {
        struct gui_label *const l = &m->port;

        gui_label_init(l);
        l->common.hcentered = true;
        l->text = "Port:";
        gui_add_child(&m->cnt.common, &l->common);
    }

    {
        struct gui_line_edit *const le = &m->port_le;

        gui_line_edit_init(le, port, portlen);
        le->w = 80;
        le->common.hcentered = true;
        le->filters = GUI_LINE_EDIT_FILTER_NUM;
        gui_add_child(&m->cnt.common, &le->common);
    }

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

        gui_container_init(c);
        c->mode = GUI_CONTAINER_MODE_H;
        c->spacing = 4;
        c->common.hcentered = true;
        gui_add_child(&m->cnt.common, &c->common);
    }

    {
        struct gui_checkbox *const c = &m->relay_ch;

        gui_checkbox_init(c);
        c->common.vcentered = true;
        c->on_pressed = on_relay_pressed;
        c->arg = m;
        gui_add_child(&m->relay_cnt.common, &c->common);
    }

    {
        struct gui_label *const l = &m->relay;

        gui_label_init(l);
        l->common.vcentered = true;
        l->font = FONT;
        l->text = "Relay to serial";
        gui_add_child(&m->relay_cnt.common, &l->common);
    }

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

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->common.vcentered = true;
        b->u.type1.label.text = "Next page";
        b->u.type1.w = 140;
        b->common.hidden = true;
        gui_add_child(&m->relay_cnt.common, &b->common);
    }

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

        gui_container_init(c);
        c->mode = GUI_CONTAINER_MODE_H;
        c->spacing = 4;
        c->common.hcentered = true;
        gui_add_child(&m->cnt.common, &c->common);
    }

    {
        struct gui_checkbox *const c = &m->announce_ch;

        gui_checkbox_init(c);
        c->common.vcentered = true;
        c->arg = m;
        gui_add_child(&m->announce_cnt.common, &c->common);
    }

    {
        struct gui_label *const l = &m->announce;

        gui_label_init(l);
        l->common.vcentered = true;
        l->font = FONT;
        l->text = "Announce server";
        gui_add_child(&m->announce_cnt.common, &l->common);
    }
}

static const char *const speeds[] =
{
    "19200", "38400", "57600", "115200"
};

static void on_speed_pressed(void *const arg)
{
    struct menu_serial_gui *const m = arg;

    if (++m->speed_i >= sizeof speeds / sizeof *speeds)
        m->speed_i = 0;

    m->speed_btn.u.type1.label.text = speeds[m->speed_i];
}

static void init_serial_gui(struct menu_serial_gui *const m,
    char *const buf, const size_t n)
{
    m->speed_i = sizeof speeds / sizeof *speeds - 1;

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

        gui_container_init(c);
        c->mode = GUI_CONTAINER_MODE_V;
        c->spacing = 2;
    }

    {
        struct gui_label *const l = &m->device;

        gui_label_init(l);
        l->common.hcentered = true;
        l->text = "Device:";
        gui_add_child(&m->cnt.common, &l->common);
    }

    {
        struct gui_line_edit *const le = &m->device_le;

        gui_line_edit_init(le, buf, n);
        le->w = 280;
        le->common.hcentered = true;
        gui_add_child(&m->cnt.common, &le->common);
    }

    {
        struct gui_label *const l = &m->speed;

        gui_label_init(l);
        l->font = FONT;
        l->text = "Baud rate:";
        l->common.hcentered = true;
        gui_add_child(&m->cnt.common, &l->common);
    }

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

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->arg = m;
        b->on_pressed = on_speed_pressed;
        b->u.type1.w = 140;
        b->u.type1.label.text = speeds[m->speed_i];
        b->common.hcentered = true;
        gui_add_child(&m->cnt.common, &b->common);
    }

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

        gui_container_init(c);
        c->spacing = 8;
        c->mode = GUI_CONTAINER_MODE_H;
        c->common.hcentered = true;
        gui_add_child(&m->cnt.common, &c->common);
    }

    {
        struct gui_label *const l = &m->hw_ctrl;

        gui_label_init(l);
        l->text = "HW control";
        l->font = FONT;
        l->common.vcentered = true;
        gui_add_child(&m->hw_ctrl_cnt.common, &l->common);
    }

    {
        struct gui_checkbox *const c = &m->hw_ctrl_ch;

        gui_checkbox_init(c);
        c->common.vcentered = true;
        gui_add_child(&m->hw_ctrl_cnt.common, &c->common);
    }
}

static void on_type_pressed(void *const arg)
{
    struct menu_net_gui *const m = arg;

    do
        if (++m->domain >= sizeof domains / sizeof *domains)
            m->domain = 0;
    while (!net_available(domains[m->domain]));

    m->update_domain = true;
}

static void update_domain(struct menu_net_gui *const m)
{
    const enum net_domain d = domains[m->domain];

    switch (d)
    {
        case NET_DOMAIN_IPV4:
            init_ipv4_gui(&m->gui.ipv4,
                m->address, sizeof m->address,
                m->port, sizeof m->port);
            break;

        case NET_DOMAIN_SERIAL:
        {
            struct menu_serial_gui *const s = &m->gui.serial;

            init_serial_gui(s, m->device, sizeof m->device);

            if (net_serial_unique())
            {
                s->device.common.hidden = true;
                s->device_le.common.hidden = true;
            }
            else
                s->device.common.hidden = false;

            break;
        }
    }

    m->type_btn.u.type1.label.text = net_domain_str(d);
    m->cnt.common.hcentered = true;
    /* Restore parent removed by init function. */
    m->gui.cnt.common.parent = &m->cnt.common;
}

static int set_common(const struct menu_net_gui *const m,
    const char **const addr, short *const port,
    const char **const dev, unsigned long *const baud)
{
    switch (m->domain)
    {
        case NET_DOMAIN_IPV4:
        {
            const struct menu_ipv4_gui *const g = &m->gui.ipv4;

            if (addr)
                *addr = g->address_le.text;

            if (port)
            {
                const char *const t = g->port_le.text;

                if (!*t)
                    return -1;

                errno = 0;

                const unsigned long port_ul = strtoul(t, NULL, 10);

                if (errno)
                {
                    fprintf(stderr, "%s: strtoul(3) failed: %s\n",
                        __func__, strerror(errno));
                    return -1;
                }

                *port = port_ul;
            }
        }
            break;

        case NET_DOMAIN_SERIAL:
        {
            const struct menu_serial_gui *const g = &m->gui.serial;

            if (dev)
                *dev = g->device_le.label.text;

            if (baud)
            {
                const char *const t = g->speed_btn.u.type1.label.text;

                if (!*t)
                    return -1;

                errno = 0;
                *baud = strtoul(t, NULL, 10);

                if (errno)
                {
                    fprintf(stderr, "%s: strtoul(3) failed: %s\n",
                        __func__, strerror(errno));
                    return -1;
                }
            }
        }
            break;
    }

    return 0;
}

int menu_net_server(const struct menu_net_gui *const m,
    union net_server *const s)
{
    *s = (const union net_server)
    {
        .common =
        {
            .domain = m->domain,
            .max_players = GAME_MAX_PLAYERS - 1
        }
    };

    struct net_serial_cfg *const scfg = &s->serial.cfg;

    return set_common(m, NULL, &s->ipv4.port, &scfg->dev, &scfg->baud);
}

int menu_net_connect(const struct menu_net_gui *const m,
    union net_connect *const c)
{
    *c = (const union net_connect)
    {
        .common =
        {
            .domain = m->domain
        }
    };

    struct net_serial_cfg *const scfg = &c->serial.cfg;

    return set_common(m, &c->ipv4.addr, &c->ipv4.port, &scfg->dev, &scfg->baud);
}

void menu_net_gui_update(struct menu_net_gui *const m)
{
    if (m->update_domain)
    {
        m->update_domain = false;
        update_domain(m);
    }
}

int menu_net_gui_init(struct menu_net_gui *const m)
{
    {
        struct gui_container *const c = &m->cnt;

        gui_container_init(c);
        c->mode = GUI_CONTAINER_MODE_V;
        c->spacing = 1;
    }

    {
        struct gui_label *const l = &m->type;

        gui_label_init(l);
        l->common.hcentered = true;
        l->text = "Type:";
        gui_add_child(&m->cnt.common, &l->common);
    }

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

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->common.hcentered = true;
        b->u.type1.w = 140;
        b->u.type1.label.text = net_domain_str(domains[m->domain]);
        b->on_pressed = on_type_pressed;
        b->arg = m;
        gui_add_child(&m->cnt.common, &b->common);
    }

    /* Set default port. */
    strcpy(m->port, "7822");

    while (!net_available(domains[m->domain]))
        if (++m->domain >= sizeof domains / sizeof *domains)
            return -1;

    update_domain(m);
    gui_add_child(&m->cnt.common, &m->gui.cnt.common);
    return 0;
}
