#include <menu_private.h>
#include <game.h>
#include <gfx.h>
#include <gui.h>
#include <gui/container.h>
#include <gui/checkbox.h>
#include <gui/label.h>
#include <gui/line_edit.h>
#include <gui/rounded_rect.h>
#include <gui/button.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct menu_settings
{
    struct gui_container cnt, bcnt, fs_cnt, res_cnt;
    struct gui_rounded_rect r;
    struct gui_checkbox fs_ch;
    struct gui_label fs, xres, yres, periph, pad, name;
    struct gui_button apply, back, periph_btn, pad_btn;
    struct gui_line_edit name_le, xres_le, yres_le;
    int pad_i;
    size_t n_pads;
    enum peripheral_type type;
};

static bool keyboard_mouse_available(void)
{
    return keyboard_available() && mouse_available();
}

static bool pad_available(void)
{
    return pad_count();
}

static const struct type
{
    const char *str;
    bool (*available)(void);
} types[] =
{
    [PERIPHERAL_TYPE_KEYBOARD_MOUSE] =
    {
        .str = "Keyboard + mouse",
        .available = keyboard_mouse_available
    },

    [PERIPHERAL_TYPE_TOUCH] =
    {
        .str = "Touch",
        .available = keyboard_mouse_available
    },

    [PERIPHERAL_TYPE_PAD] =
    {
        .str = "Gamepad",
        .available = pad_available
    }
};

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

    m->bcnt.common.y = screen_h - 40;
    m->res_cnt.common.hidden = !m->fs_ch.active;

    if (gui_update(&m->r.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 menu_settings *const m = arg;

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

    return 0;
}

static void update_device(struct menu_settings *const m)
{
    const struct type *const t = &types[m->type];

    m->periph_btn.u.type1.label.text = t->str;

    if (m->type == PERIPHERAL_TYPE_PAD)
    {
        m->pad_btn.common.hidden = false;
        m->pad.common.hidden = false;
        m->pad_btn.u.type1.label.text = pad_name(m->pad_i = 0);
    }
    else
    {
        m->pad_btn.common.hidden = true;
        m->pad.common.hidden = true;
    }
}

static void on_input_pressed(void *const arg)
{
    struct menu_settings *const m = arg;
    enum peripheral_type i = m->type;

    do
    {
        if (++i >= sizeof types / sizeof *types)
            i = 0;

        const struct type *const t = &types[i];

        if (t->available())
        {
            m->type = i;
            update_device(m);
            break;
        }

    } while (i != m->type);
}

static void init_settings_gui(struct menu_settings *const m,
    char *const name, const size_t name_len)
{
    {
        struct gui_label *const l = &m->name;

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

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

        gui_line_edit_init(le, name, name_len);
        le->common.hcentered = true;
        le->w = 140;
        gui_add_child(&m->cnt.common, &le->common);
    }

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

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

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

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->common.hcentered = true;
        b->u.type1.w = 200;
        b->on_pressed = on_input_pressed;
        b->arg = m;
        gui_add_child(&m->cnt.common, &b->common);
    }

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

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

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

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->common.hcentered = true;
        b->u.type1.w = 200;
        gui_add_child(&m->cnt.common, &b->common);
    }

    update_device(m);
}

static void init_display_gui(struct menu_settings *const m,
    char *const xres, const size_t xres_len, char *const yres,
    const size_t yres_len)
{
    enum {WIDTH = 60};

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

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

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

        gui_label_init(l);
        l->font = FONT;
        l->text = "x:";
        l->common.vcentered = true;
        gui_add_child(&m->res_cnt.common, &l->common);
    }

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

        gui_line_edit_init(le, xres, xres_len);
        le->common.vcentered = true;
        le->w = WIDTH;
        le->filters = GUI_LINE_EDIT_FILTER_NUM;
        gui_add_child(&m->res_cnt.common, &le->common);
    }

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

        gui_label_init(l);
        l->font = FONT;
        l->text = "y:";
        l->common.vcentered = true;
        gui_add_child(&m->res_cnt.common, &l->common);
    }

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

        gui_line_edit_init(le, yres, yres_len);
        le->common.vcentered = true;
        le->w = WIDTH;
        le->filters = GUI_LINE_EDIT_FILTER_NUM;
        gui_add_child(&m->res_cnt.common, &le->common);
    }
}

static void init_fullscreen_gui(struct menu_settings *const m)
{
    {
        struct gui_container *const c = &m->fs_cnt;

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

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

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

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

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

static void init_footer_gui(struct menu_settings *const m, bool *const back,
    bool *const apply)
{
    {
        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->apply;

        gui_button_init(b, GUI_BUTTON_TYPE_1);
        b->u.type1.label.text = "Apply";
        b->common.vcentered = true;
        b->u.type1.w = 140;
        b->on_pressed = menu_on_pressed;
        b->arg = apply;
        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->common.vcentered = true;
        b->u.type1.w = 100;
        b->arg = back;
        b->on_pressed = menu_on_pressed;
        gui_add_child(&m->bcnt.common, &b->common);
    }
}

static int apply_settings(struct menu_common *const c,
    struct menu_settings *const m,
    struct settings *const s)
{
    if (gfx_fullscreen_available()
        && (s->fullscreen = m->fs_ch.active))
    {
        const unsigned long w = strtoul(m->xres_le.text, NULL, 10),
            h = strtoul(m->yres_le.text, NULL, 10);

        if (!w)
        {
            fprintf(stderr, "%s: unexpected X resolution %lu\n",
                __func__, w);
            return -1;
        }
        else if (!h)
        {
            fprintf(stderr, "%s: unexpected Y resolution %lu\n",
                __func__, h);
            return -1;
        }

        s->screen_w = w;
        s->screen_h = h;
    }

    s->periph_type = m->type;
    s->pad_i = m->pad_i;
    strcpy(s->name, m->name_le.text);
    return settings_apply(s, &(const struct settings_rt){.p = &c->p});
}

int menu_settings(struct menu_common *const c)
{
    int ret = -1;
    struct menu_settings m = {0};

    {
        struct gui_rounded_rect *const r = &m.r;

        gui_rounded_rect_init(r);
        r->adjust = true;
        r->common.hcentered = true;
        r->common.vcentered = true;
    }

    {
        struct gui_container *const c = &m.cnt;

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

    struct settings *const s = &c->s;
    char name[sizeof s->name], xres[sizeof "65535"], yres[sizeof xres];
    bool back = false;

    strcpy(name, s->name);
    m.type = c->p.common.type;
    init_settings_gui(&m, name, sizeof name);

    if (gfx_fullscreen_available())
    {
        init_fullscreen_gui(&m);
        snprintf(xres, sizeof xres, "%hd", s->screen_w);
        snprintf(yres, sizeof yres, "%hd", s->screen_h);
        init_display_gui(&m, xres, sizeof xres, yres, sizeof yres);
    }

    bool apply = false;

    init_footer_gui(&m, &back, &apply);

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

    if (apply && apply_settings(c, &m, s))
        goto end;

    ret = 0;

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