#include <input.h>
#include <keyboard.h>
#include <mouse.h>
#include <pad.h>
#include <peripheral.h>
#include <ctype.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>

static void send_key(struct input *const in, const enum keyboard_key key)
{
    if (key != KEYBOARD_KEY_NONE)
    {
        switch (key)
        {
            case KEYBOARD_KEY_BACKSPACE:
                in->erase(in->user);
                break;

            case KEYBOARD_KEY_ESC:
                *in = (const struct input){0};
                break;

            default:
                break;
        }
    }
}

static void send_input(struct input *const in,
    const struct keyboard_input *const ki)
{
    {
        const struct keyboard_combo *const c = &ki->combo;

        for (size_t i = 0; i < sizeof c->keys / sizeof *c->keys; i++)
            send_key(in, c->keys[i]);
    }

    {
        const struct keyboard_chars *const c = &ki->chars;

        for (size_t i = 0; i < sizeof c->c / sizeof *c->c; i++)
        {
            const char ch = c->c[i];

            if (ch)
                in->cb(ch, in->user);
        }
    }
}

static void update_keyboard(struct input *const in,
    const struct keyboard *const k)
{
    struct keyboard_input ki;

    if (keyboard_any_justpressed(k, &ki))
        send_input(in, &ki);
    else if (keyboard_any_pressed(k, &ki))
    {
        if (memcmp(&ki, &in->prev, sizeof ki))
        {
            in->repeat = false;
            in->t = 0;
        }
        else
        {
            enum {LONG_INTERVAL = 25, SHORT_INTERVAL = 2};

            if (!in->repeat)
            {
                if (++in->t >= LONG_INTERVAL)
                    in->repeat = true;
            }
            else if (++in->t >= SHORT_INTERVAL)
            {
                send_input(in, &ki);
                in->t = 0;
            }
        }

        in->prev = ki;
    }
    else
    {
        in->repeat = false;
        in->t = 0;
    }
}

void input_update(struct input *const in, const union peripheral *const p)
{
    switch (p->common.type)
    {
        case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
            if (in->cb && in->erase)
                update_keyboard(in, &p->kbm.keyboard);

            break;

        case PERIPHERAL_TYPE_TOUCH:
            /* Fall through. */
        case PERIPHERAL_TYPE_PAD:
            break;
    }
}

int input_render(const struct input *const in, const union peripheral *const p)
{
    switch (p->common.type)
    {
        case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
            break;

        case PERIPHERAL_TYPE_TOUCH:
            /* Fall through. */
        case PERIPHERAL_TYPE_PAD:
            break;
    }

    return 0;
}

bool input_keyboard_justpressed(const struct input *const in,
    const struct keyboard *const k,
    const struct keyboard_combo *const c)
{
    return in->cb ? false : keyboard_justpressed(k, c);
}

bool input_keyboard_pressed(const struct input *const in,
    const struct keyboard *const k,
    const struct keyboard_combo *const c)
{
    return in->cb ? false : keyboard_pressed(k, c);
}

bool input_keyboard_justreleased(const struct input *const in,
    const struct keyboard *const k,
    const struct keyboard_combo *const c)
{
    return in->cb ? false : keyboard_justreleased(k, c);
}

bool input_pad_pressed(const struct input *const in,
    const struct pad *const p,
    const enum pad_key k)
{
    return in->cb ? false : pad_pressed(p, k);
}

bool input_pad_justpressed(const struct input *const in,
    const struct pad *const p,
    const enum pad_key k)
{
    return in->cb ? false : pad_justpressed(p, k);
}

bool input_pad_released(const struct input *const in,
    const struct pad *const p,
    const enum pad_key k)
{
    return in->cb ? false : pad_released(p, k);
}

bool input_mouse_pressed(const struct input *const in,
    const struct mouse *const m,
    const enum mouse_button b)
{
    return in->cb ? false : mouse_pressed(m, b);
}

bool input_mouse_justpressed(const struct input *const in,
    const struct mouse *const m,
    const enum mouse_button b)
{
    return in->cb ? false : mouse_justpressed(m, b);
}

bool input_mouse_justreleased(const struct input *const in,
    const struct mouse *const m,
    const enum mouse_button b)
{
    return in->cb ? false : mouse_justreleased(m, b);
}
