#include <gui.h>
#include <camera.h>
#include <input.h>
#include <peripheral.h>

static void get_centered(const struct gui_common *const g,
    short *const x, short *const y)
{
    if (!g->hidden && g->cb && g->cb->get_dim)
    {
        short w, h, pw = 0, ph = 0;
        const struct gui_common *p = g->parent;

        if (p)
        {
            if (!p->hidden && p->cb && p->cb->get_dim)
                p->cb->get_dim(p, &pw, &ph);
        }
        else
        {
            pw = screen_w;
            ph = screen_h;
        }

        g->cb->get_dim(g, &w, &h);

        if (g->hcentered)
            *x += (pw - w) / 2;

        if (g->vcentered)
            *y += (ph - h) / 2;
    }
}

void gui_coords(const struct gui_common *const g, short *const x,
    short *const y)
{
    if (g->hidden)
        return;

    *x = g->x + g->xoff;
    *y = g->y + g->yoff;

    const struct gui_common *p = g->parent;

    if (p)
    {
        short px = p->x + p->xoff, py = p->y + p->yoff;

        gui_coords(p, &px, &py);
        *x += px;
        *y += py;
    }

    if (g->hcentered || g->vcentered)
        get_centered(g, x, y);
}


static bool check_collision(const struct gui_common *const g,
    const union peripheral *const p,
    const struct camera *cam, const short w, const short h)
{
    short x, y;

    gui_coords(g, &x, &y);

    const struct util_rect d =
    {
        .x = x,
        .y = y,
        .w = w,
        .h = h
    };

    return cursor_collision(cam, &d);
}

bool gui_pressed(const struct gui_common *const g,
    const struct input *const in,
    const union peripheral *const p,
    const struct camera *const cam, const short w, const short h)
{
    if (g->hidden)
        return false;

    bool check = false;

    switch (p->common.type)
    {
        case PERIPHERAL_TYPE_PAD:
            check = input_pad_justpressed(in, &p->pad.pad, PAD_KEY_A);
            break;

        case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
            /* Fall through. */
        case PERIPHERAL_TYPE_TOUCH:
            check = input_mouse_justreleased(in, &p->kbm.mouse,
                MOUSE_BUTTON_LEFT);
            break;
    }

    if (check)
        return check_collision(g, p, cam, w, h);

    return false;
}

bool gui_released(const struct gui_common *const g,
    const union peripheral *const p,
    const struct camera *const cam, const short w, const short h)
{
    if (g->hidden)
        return false;

    bool check = false;

    switch (p->common.type)
    {
        case PERIPHERAL_TYPE_PAD:
            check = pad_justpressed(&p->pad.pad, PAD_KEY_A);
            break;

        case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
            /* Fall through. */
        case PERIPHERAL_TYPE_TOUCH:
            check = mouse_justreleased(&p->kbm.mouse,
                MOUSE_BUTTON_LEFT);
            break;
    }

    if (check)
        return !check_collision(g, p, cam, w, h);

    return false;
}

void gui_add_sibling(struct gui_common *const g,
    struct gui_common *const s)
{
    for (struct gui_common *c = g; c; c = c->sibling)
        if (!c->sibling)
        {
            c->sibling = s;
            break;
        }
}

void gui_add_child(struct gui_common *const p,
    struct gui_common *const c)
{
    if (p->child)
        gui_add_sibling(p->child, c);
    else
        p->child = c;

    c->parent = p;

    if (p->cb && p->cb->add_child)
        p->cb->add_child(p, c);
}

int gui_update(struct gui_common *const g, const union peripheral *const p,
    const struct camera *const c, struct input *const in)
{
    if (!g->hidden
        && ((g->child && gui_update(g->child, p, c, in))
            || (g->cb && g->cb->update && g->cb->update(g, p, c, in))))
        return -1;

    for (struct gui_common *s = g->sibling; s; s = s->sibling)
        if (!s->hidden && ((s->child && gui_update(s->child, p, c, in))
            || (s->cb && s->cb->update && s->cb->update(s, p, c, in))))
            return -1;

    return 0;
}

int gui_render(const struct gui_common *const g)
{
    if (!g->hidden && ((g->cb && g->cb->render && g->cb->render(g))
        || (g->child && gui_render(g->child))))
        return -1;

    for (struct gui_common *s = g->sibling; s; s = s->sibling)
        if (!s->hidden && ((s->cb && s->cb->render && s->cb->render(s))
            || (s->child && gui_render(s->child))))
            return -1;

    return 0;
}

void gui_deinit(struct gui_common *const g, struct input *const in)
{
    if (g->cb && g->cb->deinit)
        g->cb->deinit(g, in);

    if (g->child)
        gui_deinit(g->child, in);

    for (struct gui_common *s = g->sibling; s; s = s->sibling)
    {
        if (s->cb && s->cb->deinit)
            s->cb->deinit(s, in);

        if (s->child)
            gui_deinit(s->child, in);
    }
}
