#include <gui/rounded_rect.h>
#include <gui.h>
#include <gui_private.h>
#include <gfx.h>

struct sprite gui_rounded_rect_sprites[MAX_GUI_ROUNDED_RECT_SPRITES];

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

static int render_top(const struct gui_rounded_rect *const r,
    const short x, const short y)
{
    {
        sprite_get_or_ret(left, -1);

        if (sprite_clone(&refs[GUI_ROUNDED_RECT_UP_LEFT], left))
            return -1;

        left->x = x;
        left->y = y;

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

    {
        sprite_get_or_ret(right, -1);

        const struct sprite *const ref =
            &refs[GUI_ROUNDED_RECT_UP_RIGHT];

        if (sprite_clone(ref, right))
            return -1;

        right->x = x + r->w - ref->w;
        right->y = y;

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

    return 0;
}

static int render_vert(const struct gui_rounded_rect *const r,
    const short x, const short y)
{
    const struct sprite *const vert = &refs[GUI_ROUNDED_RECT_MID_VERT];
    const short top_h = refs[GUI_ROUNDED_RECT_UP_LEFT].h,
        h = r->h - (top_h * 2),
        rem_vert = h % vert->h,
        whole_vert = h / vert->h,
        n_vert = rem_vert ? whole_vert + 1 : whole_vert;

    for (struct
        {
            size_t i;
            short y;
        } a = {.y = y + top_h}; a.i < n_vert; a.i++, a.y += vert->h)
    {
        sprite_get_or_ret(v, -1);

        if (sprite_clone(vert, v))
            return -1;

        v->y = a.y;
        v->x = x;

        if (rem_vert && a.i + 1 == n_vert)
            v->h = rem_vert;
        else
            v->h = vert->h;

        sprite_sort(v);
    }

    return 0;
}

static int render_vertleft(const struct gui_rounded_rect *const r,
    const short x, const short y)
{
    return render_vert(r, x, y);
}

static int render_vertright(const struct gui_rounded_rect *const r,
    const short x, const short y)
{
    const struct sprite *const ref = &refs[GUI_ROUNDED_RECT_MID_VERT];

    return render_vert(r, x + r->w - ref->w, y);
}

static int render_down(const struct gui_rounded_rect *const r,
    const short x, const short y)
{
    {
        const struct sprite *const ref = &refs[GUI_ROUNDED_RECT_DOWN_LEFT];
        sprite_get_or_ret(left, -1);

        if (sprite_clone(ref, left))
            return -1;

        left->x = x;
        left->y = y + r->h - ref->h;
        sprite_sort(left);
    }

    {
        const struct sprite *const ref = &refs[GUI_ROUNDED_RECT_DOWN_RIGHT];
        sprite_get_or_ret(right, -1);

        if (sprite_clone(ref, right))
            return -1;

        right->x = x + r->w - ref->w;
        right->y = y + r->h - ref->h;
        sprite_sort(right);
    }

    return 0;
}

static int render_rect(const struct gui_rounded_rect *const r,
    const short x, const short y)
{
    rect_get_or_ret(sel, -1);
    semitrans_rect_init(sel);

    const struct sprite *const mid = &refs[GUI_ROUNDED_RECT_MID_TOP],
        *const vert = &refs[GUI_ROUNDED_RECT_MID_VERT];

    sel->x = x + vert->w;
    sel->y = y + mid->h;
    sel->w = r->w - (vert->w * 2);
    sel->h = r->h - (mid->h * 2);
    sel->r = r->r;
    sel->g = r->g;
    sel->b = r->b;
    rect_sort(sel);
    return 0;
}

static int render_mid(const struct gui_rounded_rect *const r,
    const short x, const short y, const struct sprite *const s)
{
    const short mid_w = s->w,
        top_w = refs[GUI_ROUNDED_RECT_UP_LEFT].w,
        w = r->w - (top_w * 2),
        rem_mid = w % mid_w,
        whole_mid = w / mid_w,
        n_mid = rem_mid ? whole_mid + 1 : whole_mid;

    if (n_mid < 0)
    {
        fprintf(stderr, "%s: invalid dimensions\n", __func__);
        return -1;
    }

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

        if (sprite_clone(s, m))
            return -1;

        m->x = a.x;
        m->y = y;

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

        sprite_sort(m);
    }

    return 0;
}

static int render_midtop(const struct gui_rounded_rect *const r,
    const short x, const short y)
{
    return render_mid(r, x, y, &refs[GUI_ROUNDED_RECT_MID_TOP]);
}

static int render_middown(const struct gui_rounded_rect *const r,
    const short x, const short y)
{
    const struct sprite *const s = &refs[GUI_ROUNDED_RECT_MID_DOWN];

    return render_mid(r, x, y + r->h - s->h, s);
}

static int render(const struct gui_common *const g)
{
    const struct gui_rounded_rect *const r = (const struct gui_rounded_rect *)g;

    short x, y;

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

    static int (*const steps[])(const struct gui_rounded_rect *const r,
        const short x, const short y) =
    {
        render_top,
        render_rect,
        render_midtop,
        render_middown,
        render_vertleft,
        render_vertright,
        render_down
    };

    for (size_t i = 0; i < sizeof steps / sizeof *steps; i++)
        if (steps[i](r, x, y))
            return -1;

    return 0;
}

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

    *w = r->w;
    *h = r->h;
}

static void add_child(struct gui_common *const parent,
    struct gui_common *const child)
{
    struct gui_rounded_rect *const r = (struct gui_rounded_rect *)parent;

    if (!r->adjust || child->hidden || !child->cb || !child->cb->get_dim)
        return;

    short w, h;
    const short ref_w = refs[GUI_ROUNDED_RECT_MID_VERT].w,
        ref_h = refs[GUI_ROUNDED_RECT_MID_TOP].h,
        min_w = ref_w * 2 + refs[GUI_ROUNDED_RECT_MID_TOP].w,
        min_h = ref_h * 2 + refs[GUI_ROUNDED_RECT_MID_VERT].h;

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

    if (w > r->w - (ref_w * 2))
        r->w = w + (ref_w * 2);

    if (h > r->h - (ref_h * 2))
        r->h = h + (ref_h * 2);

    if (r->w < min_w)
        r->w = min_w;

    if (r->h < min_h)
        r->h = min_h;

    if (!child->hcentered)
        child->xoff = ref_w;

    if (!child->vcentered)
        child->yoff = ref_h;
}

static int update(struct gui_common *const g,
    const union peripheral *const p, const struct camera *const cam,
    struct input *const in)
{
    struct gui_rounded_rect *const r = (struct gui_rounded_rect *)g;

    if (r->adjust)
    {
        r->w = r->h = 0;

        struct gui_common *const child = r->common.child;

        if (child && child->cb && child->cb->get_dim)
            add_child(&r->common, child);

        for (struct gui_common *s = child->sibling; s; s = s->sibling)
            add_child(&r->common, s);
    }

    return 0;
}

void gui_rounded_rect_init(struct gui_rounded_rect *const r)
{
    static const struct gui_common_cb cb =
    {
        .get_dim = get_dim,
        .update = update,
        .render = render
    };

    *r = (const struct gui_rounded_rect)
    {
        .common =
        {
            .cb = &cb
        }
    };
}
