#include <keyboard.h>
#include <keyboard/key.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>

static bool chars_justpressed(const struct keyboard *const k,
    struct keyboard_chars *const c)
{
    bool ret = false;
    const struct keyboard_chars *const ref = &k->input.chars;

    for (size_t i = 0; i < sizeof c->c / sizeof *c->c; i++)
        c->c[i] = '\0';

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

        if (ch && k->oldinput.chars.c[i] != ch)
        {
            c->c[j++] = ch;
            ret = true;
        }
    }

    return ret;
}

static bool keys_justpressed(const struct keyboard *const k,
    struct keyboard_combo *const c)
{
    bool ret = false;

    for (size_t i = 0; i < sizeof c->keys / sizeof *c->keys; i++)
        c->keys[i] = KEYBOARD_KEY_NONE;

    const struct keyboard_combo *const ref = &k->input.combo;

    for (size_t i = 0, j = 0;
        i < sizeof ref->keys / sizeof *ref->keys; i++)
    {
        const enum keyboard_key key = ref->keys[i];

        if (key != KEYBOARD_KEY_NONE
            && k->oldinput.combo.keys[i] != key)
        {
            c->keys[j++] = key;
            ret = true;
        }
    }

    return ret;
}

bool keyboard_any_justpressed(const struct keyboard *const k,
    struct keyboard_input *const in)
{
    /* Use bitwise OR since both functions must be always executed. */
    return keys_justpressed(k, &in->combo)
        | chars_justpressed(k, &in->chars);
}

static bool keys_pressed(const struct keyboard *const k,
    struct keyboard_combo *const c)
{
    const struct keyboard_combo *const ref = &k->input.combo;

    for (size_t i = 0; i < sizeof ref->keys / sizeof *ref->keys; i++)
    {
        if (ref->keys[i] != KEYBOARD_KEY_NONE)
        {
            *c = *ref;
            return true;
        }
    }

    return false;
}

static bool chars_pressed(const struct keyboard *const k,
    struct keyboard_chars *const ch)
{
    const struct keyboard_chars *const ref = &k->input.chars;

    for (size_t i = 0; i < sizeof ref->c / sizeof *ref->c; i++)
    {
        if (ref->c[i])
        {
            *ch = *ref;
            return true;
        }
    }

    return false;
}

bool keyboard_any_pressed(const struct keyboard *const k,
    struct keyboard_input *const in)
{
    /* Use bitwise OR since both functions must be always executed. */
    return keys_pressed(k, &in->combo) | chars_pressed(k, &in->chars);
}

static bool combo_pressed(const struct keyboard_combo *const ref,
        const struct keyboard_combo *const c)
{
    bool ret = false;

    for (size_t i = 0; i < sizeof c->keys / sizeof *c->keys; i++)
    {
        const enum keyboard_key key = c->keys[i];

        if (key != KEYBOARD_KEY_NONE)
        {
            bool success = false;

            for (size_t i = 0; i < sizeof ref->keys / sizeof *ref->keys; i++)
            {
                if (key == ref->keys[i])
                {
                    success = true;
                    break;
                }
            }

            if (!(ret = success))
                break;
        }
    }

    return ret;
}

bool keyboard_pressed(const struct keyboard *const k,
    const struct keyboard_combo *const c)
{
    return combo_pressed(&k->input.combo, c);
}

bool keyboard_justreleased(const struct keyboard *const k,
    const struct keyboard_combo *const c)
{
    return !combo_pressed(&k->input.combo, c)
        && combo_pressed(&k->oldinput.combo, c);
}

bool keyboard_justpressed(const struct keyboard *const k,
    const struct keyboard_combo *const c)
{
    return combo_pressed(&k->input.combo, c)
        && !combo_pressed(&k->oldinput.combo, c);
}

void keyboard_init(struct keyboard *const k)
{
    *k = (const struct keyboard){0};
}

const char *keyboard_key_str(const enum keyboard_key k)
{
    static const char *const s[] =
    {
#define X(x) [x] = #x,
        KEYBOARD_KEYS
#undef X
    };

    return s[k];
}
