#include <menu.h>
#include <menu_private.h>
#include <game.h>
#include <gui.h>
#include <gui/button.h>
#include <gui/checkbox.h>
#include <gui/container.h>
#include <gui/label.h>
#include <gui/line_edit.h>
#include <gui/rounded_rect.h>
#include <net.h>
#include <packet.h>
#include <util.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

enum
{
    CHAT_ENTRIES = 6,
    TOTAL_MESSAGE_LEN = GAME_PLAYER_NAME_LEN
        + sizeof ": " - 1
        + PACKET_MAX_MESSAGE_LEN
};

struct gamecfg_menu
{
    char chat_in[PACKET_MAX_MESSAGE_LEN],
        chat[CHAT_ENTRIES][TOTAL_MESSAGE_LEN];
    char chat_label[sizeof "Chat (255)"];
    uint8_t chat_i, unread_messages;
    struct gui_container bcnt;
    struct gui_button start, back;
    struct net_host *host;
    struct packet_ctx ctx;
    bool update_page;

    enum
    {
        PAGE_1,
        PAGE_2,

        N_PAGES
    } page_i;

    struct player_info
    {
        struct player_info_net
        {
            net_peer peer;
            struct packet_input in;
        } *net;

        char name[GAME_PLAYER_NAME_LEN];

        enum
        {
            OPEN,
            CLOSED,
            CONNECTING,
            NOT_READY,
            READY
        } state;
    } pl[GAME_MAX_PLAYERS];

    union
    {
        struct gui_container root;

        struct gamecfg_page1
        {
            struct gui_container root, cnt, ready_cnt;
            struct gui_rounded_rect r;
            struct gui_label ready;
            struct gui_checkbox ready_ch;
            struct gui_button chat_page;

            struct player_info_gui
            {
                struct gui_container cnt;
                struct gui_label name, state_label;
            } pl[GAME_MAX_PLAYERS];
        } page1;

        struct gamecfg_page2
        {
            struct gui_container root, chat_cnt, bcnt;
            struct gui_rounded_rect r;
            struct gui_line_edit chat_le;
            struct gui_button send, player_page;
            struct gui_label entries[CHAT_ENTRIES];
        } page2;
    } u;
};

static void update_players(struct gamecfg_menu *const m)
{
    if (m->page_i != PAGE_1)
        return;

    for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
    {
        struct player_info *const pi = &m->pl[i];
        struct player_info_gui *const g = &m->u.page1.pl[i];

        static const char *const states[] =
        {
            [OPEN] = "Open",
            [CLOSED] = "Closed",
            [NOT_READY] = "Not ready",
            [CONNECTING] = "Connecting",
            [READY] = "Ready"
        };

        g->state_label.text = states[pi->state];
    }
}

static void on_next_page_pressed(void *const arg)
{
    struct gamecfg_menu *const m = arg;

    m->update_page = true;
}

static int send_ready(const net_peer p, const struct packet_ctx *const c,
    const struct player_info *const pi)
{
    const struct packet_ready r =
    {
        .common =
        {
            .type = PACKET_TYPE_READY
        },

        .ready = pi->state == READY
    };

    return packet_send(c, p, (const union packet *)&r);
}

static void on_ready_pressed(const bool active, void *const arg)
{
    struct gamecfg_menu *const m = arg;
    struct player_info *const self = m->pl;

    self->state = active ? READY : NOT_READY;

    for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
    {
        struct player_info_net *const net = m->pl[i].net;

        if (net && net->peer)
            send_ready(net->peer, &m->ctx, self);
    }
}

static void init_page1(struct gamecfg_menu *const m)
{
    struct gamecfg_page1 *const p = &m->u.page1;

    {
        struct gui_rounded_rect *const r = &p->r;

        gui_rounded_rect_init(r);
        r->adjust = true;
        r->common.hcentered = true;
        gui_add_child(&p->root.common, &r->common);
    }

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

        gui_container_init(c);
        c->mode = GUI_CONTAINER_MODE_V;
        c->spacing = 4;
        gui_add_child(&p->r.common, &c->common);
    }

    for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
    {
        struct player_info *const pi = &m->pl[i];
        struct player_info_gui *const g = &p->pl[i];

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

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

        {
            struct gui_label *const l = &g->name;

            gui_label_init(l);
            l->text = pi->name;
            l->common.vcentered = true;
            gui_add_child(&g->cnt.common, &l->common);
        }

        {
            struct gui_label *const l = &g->state_label;

            gui_label_init(l);
            l->common.vcentered = true;
            gui_add_child(&g->cnt.common, &l->common);
        }
    }

    {
        struct gui_container *const c = &p->ready_cnt;

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

    {
        struct gui_checkbox *const c = &p->ready_ch;

        gui_checkbox_init(c);
        c->common.vcentered = true;
        c->on_pressed = on_ready_pressed;
        c->arg = m;
        gui_add_child(&p->ready_cnt.common, &c->common);
    }

    {
        struct gui_label *const l = &p->ready;

        gui_label_init(l);
        l->font = FONT;
        l->text = "Ready";
        l->common.vcentered = true;
        gui_add_child(&p->ready_cnt.common, &l->common);
    }

    {
        struct gui_button *const b = &p->chat_page;

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->u.type1.w = 140;
        b->common.hcentered = true;
        b->on_pressed = on_next_page_pressed;
        b->arg = m;
        b->u.type1.label.text = "Chat";
        gui_add_child(&m->u.root.common, &b->common);
    }
}

static int broadcast(struct gamecfg_menu *const m, const union packet *const p)
{
    for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
    {
        struct player_info_net *const net = m->pl[i].net;

        if (net)
        {
            const net_peer peer = net->peer;

            if (peer && packet_send(&m->ctx, peer, p))
            {
                fprintf(stderr, "%s: net_write failed\n", __func__);
                return -1;
            }
        }
    }

    return 0;
}

static void append_msg(struct gamecfg_menu *const m,
    const char *const player, const char *const msg)
{
    static const size_t sz = sizeof m->chat / sizeof *m->chat;
    char *dst;

    if (m->chat_i < sz)
        dst = m->chat[m->chat_i++];
    else
    {
        for (size_t i = 0; i < sz - 1; i++)
            strcpy(m->chat[i], m->chat[i + 1]);

        dst = m->chat[sz - 1];
        *dst = '\0';
    }

    strcat(dst, player);
    strcat(dst, ": ");
    strcat(dst, msg);
}

static void on_send_pressed(void *const arg)
{
    struct gamecfg_menu *const m = arg;
    struct gui_line_edit *const le = &m->u.page2.chat_le;
    char *const src = le->text;

    if (!*src)
        return;

    const struct packet_chat c =
    {
        .common =
        {
            .type = PACKET_TYPE_CHAT
        },

        .msg = src
    };

    if (broadcast(m, (const union packet *)&c))
        return;

    append_msg(m, m->pl->name, src);
    *src = '\0';
    le->i = 0;
}

static void init_page2(struct gamecfg_menu *const m)
{
    struct gamecfg_page2 *const p = &m->u.page2;

    {
        struct gui_rounded_rect *const r = &p->r;

        gui_rounded_rect_init(r);
        r->adjust = true;
        r->common.hcentered = true;
        gui_add_child(&m->u.root.common, &r->common);
    }

    {
        struct gui_container *const c = &p->chat_cnt;

        gui_container_init(c);
        c->mode = GUI_CONTAINER_MODE_V;
        c->spacing = 2;
        gui_add_child(&p->r.common, &c->common);
    }

    for (size_t i = 0; i < sizeof p->entries / sizeof *p->entries; i++)
    {
        struct gui_label *const l = &p->entries[i];

        gui_label_init(l);
        l->text = m->chat[i];
        l->font = FONT;
        gui_add_child(&p->chat_cnt.common, &l->common);
    }

    {
        struct gui_line_edit *const le = &p->chat_le;

        gui_line_edit_init(le, m->chat_in, sizeof m->chat_in);
        le->common.hcentered = true;
        le->w = 240;
        gui_add_child(&m->u.root.common, &le->common);
    }

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

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

    {
        struct gui_button *const b = &p->send;

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

    {
        struct gui_button *const b = &p->player_page;

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->common.vcentered = true;
        b->u.type1.w = 150;
        b->u.type1.label.text = "Player info";
        b->on_pressed = on_next_page_pressed;
        b->arg = m;
        gui_add_child(&p->bcnt.common, &b->common);
    }

    m->unread_messages = 0;
}

static void init_page_gui(struct gamecfg_menu *const m)
{
    {
        struct gui_container *const c = &m->u.root;

        gui_container_init(c);
        c->common.hcentered = true;
        c->common.vcentered = true;
        c->mode = GUI_CONTAINER_MODE_V;
        c->spacing = 4;
    }

    static void (*const init[])(struct gamecfg_menu *) =
    {
        [PAGE_1] = init_page1,
        [PAGE_2] = init_page2
    };

    init[m->page_i](m);
}

static void update_send(struct menu_common *const c,
    struct gamecfg_menu *const m)
{
    if (m->page_i != PAGE_2)
        return;

    switch (c->p.common.type)
    {
        case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
            /* Fall through. */
        case PERIPHERAL_TYPE_TOUCH:
        {
            struct keyboard *const k = &c->p.kbm.keyboard;

            if (m->u.page2.chat_le.focus
                && keyboard_justreleased(k,
                    &KEYBOARD_COMBO(KEYBOARD_KEY_RETURN)))
                on_send_pressed(m);
        }
            break;

        case PERIPHERAL_TYPE_PAD:
            break;
    }
}

static void update_page(struct gamecfg_menu *const m)
{
    if (m->update_page)
    {
        if (++m->page_i >= N_PAGES)
            m->page_i = 0;

        init_page_gui(m);
        m->update_page = false;
    }
}

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

    m->bcnt.common.y = screen_h - 40;

    if (m->host)
        net_update(m->host);

    update_players(m);
    update_send(c, m);
    update_page(m);

    if (gui_update(&m->u.root.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 gamecfg_menu *const m = arg;

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

    return 0;
}

static int add_peer(struct gamecfg_menu *const m, const net_peer p)
{
    bool found = false;

    for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
    {
        struct player_info *const pi = &m->pl[i];
        struct player_info_net *const net = pi->net;

        if (net && !net->peer)
        {
            net->peer = p;
            pi->state = CONNECTING;
            strcpy(pi->name, "<new player>");
            found = true;
            break;
        }
    }

    return !found;
}

static int send_name(const net_peer p, const struct packet_ctx *const c,
    const struct player_info *const pi)
{
    const struct packet_name n =
    {
        .common =
        {
            .type = PACKET_TYPE_NAME
        },

        .name = pi->name
    };

    return packet_send(c, p, (const union packet *)&n);
}

static int broadcast_player_info(const net_peer p,
    const struct gamecfg_menu *const m)
{
    for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
    {
        const struct player_info *const pi = &m->pl[i];

        if (*pi->name)
        {
            const struct packet_ctx *const c = &m->ctx;

            if (send_name(p, c, pi) || send_ready(p, c, pi))
                return -1;
        }
    }

    return 0;
}

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

    if (net_role(m->host) == NET_ROLE_SERVER)
        broadcast_player_info(p, m);

    add_peer(m, p);
}

static void on_disconnected(const net_peer p, void *const arg)
{
}

static struct player_info *get_pl_from_peer(struct gamecfg_menu *const m,
    const net_peer peer)
{
    for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
    {
        struct player_info *const pi = &m->pl[i];

        if (pi->net && pi->net->peer == peer)
            return pi;
    }

    return NULL;
}

static void on_received(const net_peer p, const void *const buf,
    const size_t n, void *const arg)
{
    struct gamecfg_menu *const m = arg;
    struct player_info *const pi = get_pl_from_peer(m, p);

    if (pi && pi->net && packet_feed(&m->ctx, p, &pi->net->in, buf, n))
        fprintf(stderr, "%s: packet_feed failed\n", __func__);
}

static void init_footer_gui(struct gamecfg_menu *const m,
    bool *const start, bool *const back)
{
    {
        struct gui_container *const c = &m->bcnt;

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

    if (net_role(m->host) == NET_ROLE_SERVER)
    {
        struct gui_button *const b = &m->start;

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->u.type1.label.text = "Start";
        b->u.type1.w = 140;
        b->common.vcentered = true;
        b->on_pressed = menu_on_pressed;
        b->arg = start;
        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);
    }
}

static void init_own(const struct menu_common *const c,
    struct player_info *const pi)
{
    strcpy(pi->name, c->s.name);
    pi->state = NOT_READY;
}

static void on_packet_received(const net_peer peer,
    const union packet *const pkt, void *const arg)
{
    struct gamecfg_menu *const m = arg;

    switch (pkt->common.type)
    {
        case PACKET_TYPE_CHAT:
        {
            struct player_info *const pi = get_pl_from_peer(m, peer);

            if (pi)
            {
                append_msg(m, pi->name, pkt->chat.msg);

                if (m->page_i == PAGE_1)
                {
                    if (m->unread_messages < CHAT_ENTRIES)
                        m->unread_messages++;

                    snprintf(m->chat_label, sizeof m->chat_label,
                        "Chat (%" PRIu8 ")", m->unread_messages);
                    m->u.page1.chat_page.u.type1.label.text = m->chat_label;
                }
            }
        }
            break;

        case PACKET_TYPE_NAME:
        {
            struct player_info *const pi = get_pl_from_peer(m, peer);

            if (pi)
                strcpy(pi->name, pkt->name.name);
        }
            break;

        case PACKET_TYPE_READY:
        {
            struct player_info *const pi = get_pl_from_peer(m, peer);

            if (pi)
                pi->state = pkt->ready.ready ? READY : NOT_READY;
        }
            break;

        default:
            break;
    }
}

int menu_gamecfg(struct menu_common *const c, struct net_host *const h,
    const net_peer p)
{
    struct gamecfg_menu m =
    {
        .host = h,
        .ctx =
        {
            .arg = &m,
            .cb = on_packet_received,
            .host = h
        }
    };

    const struct net_event ev =
    {
        .connected = on_connected,
        .disconnected = on_disconnected,
        .received = on_received,
        .arg = &m
    };

    struct player_info_net n[sizeof m.pl / sizeof *m.pl - 1] = {0};
    bool start = false, back = false;

    for (size_t i = 0; i < sizeof n / sizeof *n; i++)
        m.pl[i + 1].net = &n[i];

    if (net_set_event(m.host, &ev))
        return -1;

    init_own(c, m.pl);
    init_page_gui(&m);
    init_footer_gui(&m, &start, &back);

    if (p)
    {
        const struct packet_ctx *const c = &m.ctx;

        if (add_peer(&m, p)
            || send_name(p, c, m.pl)
            || send_ready(p, c, m.pl))
            return -1;
    }

    while (!back && !c->p.common.exit && !start)
        if (menu_update(c, update, render, &m))
            return -1;

    if (start)
    {
        const struct game_cfg cfg =
        {
            .p = &c->p,
            .map = "city1.txt"
        };

        return game(&cfg);
    }

    return 0;
}
