#include <gui.h>
#include <gui_private.h>
#include <gui/label.h>
#include <gui/line_edit.h>
#include <camera.h>
#include <gfx.h>
#include <input.h>
#include <mouse.h>
#include <pad.h>
#include <peripheral.h>
#include <ctype.h>
#include <string.h>

struct sprite gui_line_edit_sprites[MAX_GUI_LINE_EDIT_SPRITES];

/* Alias for readability. */
static const struct sprite *const refs = gui_line_edit_sprites;

static int render_left(const struct gui_line_edit *const l,
    short *const x, const short y)
{
    sprite_get_or_ret(s, -1);

    if (sprite_clone(&refs[GUI_LINE_EDIT_LEFT], s))
        return -1;

    s->x = *x;
    s->y = y;

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

    *x = s->x + s->w;
    return 0;
}

static int render_mid(const struct gui_line_edit *const l,
    short *const x, const short y)
{
    const short mid_w = refs[GUI_LINE_EDIT_MID].w,
        lw = refs[GUI_LINE_EDIT_LEFT].w,
        rw = refs[GUI_LINE_EDIT_RIGHT].w,
        w = l->w - lw - rw;

    if (w > 0)
    {
        const short rem_mid = w > 0 ? w % mid_w : 0,
            whole_mid = w / mid_w,
            n_mid = rem_mid ? whole_mid + 1 : whole_mid;

        for (struct
            {
                size_t i;
                short x;
            } a = {.x = lw};
            a.i < n_mid;
            a.i++, a.x += mid_w)
        {
            sprite_get_or_ret(m, -1);

            if (sprite_clone(&refs[GUI_LINE_EDIT_MID], m))
                return -1;

            m->x = *x;
            m->y = y;

            if (rem_mid && a.i + 1 == n_mid)
                m->w = rem_mid;
            else
                m->w = mid_w;

            if (sprite_sort(m))
            {
                fprintf(stderr, "%s: sprite_sort failed\n", __func__);
                return -1;
            }

            *x += m->w;
        }
    }
    else
        return -1;

    return 0;
}

static int render_right(const short x, const short y)
{
    sprite_get_or_ret(s, -1);

    if (sprite_clone(&refs[GUI_LINE_EDIT_RIGHT], s))
        return -1;

    s->x = x;
    s->y = y;

    const int ret = sprite_sort(s);

    if (ret)
        fprintf(stderr, "%s: sprite_sort failed\n", __func__);

    return ret;
}

static int render(const struct gui_common *const g)
{
    const struct gui_line_edit *const l = (const struct gui_line_edit *)g;
    short x, y;

    gui_coords(&l->common, &x, &y);

    if (render_left(l, &x, y)
        || render_mid(l, &x, y)
        || render_right(x, y))
        return -1;

    return 0;
}

static void get_dim(const struct gui_common *const g,
    short *const w, short *const h)
{
    struct gui_line_edit *const l = (struct gui_line_edit *)g;

    *w = l->w;
    *h = refs->h;
}

static void on_char(const char ch, void *const user)
{
    struct gui_line_edit *const l = user;
    bool allowed = false;

    if (l->filters)
    {
        if (((l->filters & GUI_LINE_EDIT_FILTER_ALPHA)
                && isalpha((unsigned char)ch))
            || ((l->filters & GUI_LINE_EDIT_FILTER_NUM)
            && isdigit((unsigned char)ch)))
            allowed = true;
    }
    else
        allowed = true;

    if (allowed && l->i + 1 < l->sz)
    {
        l->text[l->i++] = ch;
        l->text[l->i] = '\0';
    }
}

static void on_erase(void *const user)
{
    struct gui_line_edit *const l = user;

    if (l->i)
        l->text[--l->i] = '\0';
}

static int update(struct gui_common *const g,
    const union peripheral *const p, const struct camera *const c,
    struct input *const in)
{
    struct gui_line_edit *const l = (struct gui_line_edit *)g;

    if (gui_pressed(&l->common, in, p, c, l->w, refs->h))
    {
        l->focus = true;

        *in = (const struct input)
        {
            .cb = on_char,
            .erase = on_erase,
            .user = l
        };
    }
    else if (l->focus && gui_released(&l->common, p, c, l->w, refs->h))
    {
        l->focus = false;

        if (in->user == l)
            *in = (const struct input){0};
    }

    return 0;
}

static void deinit(struct gui_common *const g, struct input *const in)
{
    struct gui_line_edit *const l = (struct gui_line_edit *)g;

    if (l->focus)
        *in = (const struct input){0};
}

void gui_line_edit_init(struct gui_line_edit *const l, char *const buf,
    const size_t sz)
{
    static const struct gui_common_cb cb =
    {
        .get_dim = get_dim,
        .update = update,
        .render = render,
        .deinit = deinit
    };

    *l = (const struct gui_line_edit)
    {
        .common =
        {
            .cb = &cb
        },

        .text = buf,
        .sz = sz,
        .i = strlen(buf)
    };

    gui_label_init(&l->label);
    l->label.common.hcentered = true;
    l->label.common.vcentered = true;
    l->label.text = l->text;
    gui_add_child(&l->common, &l->label.common);
}
