#include <stdio.h>
#include <ctype.h>
#include <string.h>

#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT

#include "nuklear.h"
#include "input.h"

#define INITIAL_KEY_DOWN 500
#define KEY_NAV_REPEAT 100
#define KEY_VAL_REPEAT 50

unsigned int pc_get_ticks (void);

struct key_status key_down = {0};
struct key_status key_up = {0};
struct key_status key_page_down = {0};
struct key_status key_page_up = {0};
struct key_status key_left = {0};
struct key_status key_right = {0};

int input_check_for_key_trigger(struct nk_context *ctx, struct key_status *status, enum nk_keys key, unsigned int init_key_down, unsigned int key_repeat)
{
    int fast = ctx->input.keyboard.keys[NK_KEY_SHIFT].down;

    if (ctx->input.keyboard.keys[key].down) {
        if (status->key_down) {
            if (status->repeat_mode) {
                if ((pc_get_ticks() - status->last_trig_time) > key_repeat) {
                    status->last_trig_time = pc_get_ticks();
                    return fast ? 2 : 1;
                }
                return 0;
            }
            else {
                if ((pc_get_ticks() - status->last_trig_time) > init_key_down) {
                    status->repeat_mode = 1;
                    status->last_trig_time = pc_get_ticks();
                    return fast ? 2 : 1;
                }
                return 0;
            }
        }
        else {
            status->key_down = 1;
            status->last_trig_time = pc_get_ticks();
            status->repeat_mode = 0;
            return fast ? 2 : 1;
        }
    }
    else {
        status->key_down = 0;
        status->repeat_mode = 0;
    }

    return 0;
}

int input_check_select_item (struct nk_context *ctx)
{
    static int enter_down = 0;

    if (ctx->input.keyboard.keys[NK_KEY_ENTER].down && !enter_down) {
        enter_down = 1;

        return 1;
    }
    if (!ctx->input.keyboard.keys[NK_KEY_ENTER].down) {
        enter_down = 0;
    }

    return 0;
}

int input_check_toggle_item (struct nk_context *ctx)
{
    static int space_down = 0;

    if (!space_down) {
        for (int i = 0; i < ctx->input.keyboard.text_len; i++) {
            if (ctx->input.keyboard.text[i] == ' ') {
                space_down = 1;
                return 1;
            }
        }
    }
    else {
        space_down = 0;
        for (int i = 0; i < ctx->input.keyboard.text_len; i++) {
            if (ctx->input.keyboard.text[i] == ' ') {
                space_down = 1;
            }
        }
    }

    return 0;
}

int input_check_vertical_navigation_ex(struct nk_context *ctx, int *selected,
                                       int max_items, int page_size,
                                       int *keyboard_updated)
{
    int trigger = 0;
    int key_trigger = 0;
    int changed = 0;

    if (keyboard_updated)
        *keyboard_updated = 0;

    if (!selected || !max_items)
        return 0;

    if (page_size < 1) page_size = 1;

    /* Move down (mouse scroll or arrow key) */
    if (ctx->input.mouse.scroll_delta.y < -0.1)
        trigger = 1;
    key_trigger = input_check_for_key_trigger(ctx, &key_down, NK_KEY_DOWN,
                                              INITIAL_KEY_DOWN, KEY_NAV_REPEAT);
    trigger += key_trigger;
    if (trigger) {
        *selected = (*selected + 1) % max_items;
        changed = 1;
        if (keyboard_updated && key_trigger)
            *keyboard_updated = 1;
    }

    /* Page down */
    key_trigger = input_check_for_key_trigger(ctx, &key_page_down,
                                              NK_KEY_SCROLL_DOWN,
                                              INITIAL_KEY_DOWN, KEY_NAV_REPEAT);
    if (key_trigger) {
        *selected += page_size;
        if (*selected >= max_items) *selected = max_items - 1;
        changed = 1;
        if (keyboard_updated)
            *keyboard_updated = 1;
        /* Consume the key so Nuklear's internal scrollbar doesn't also scroll the view. */
        ctx->input.keyboard.keys[NK_KEY_SCROLL_DOWN].down = 0;
    }

    /* Move up (mouse scroll or arrow key) */
    trigger = 0;
    if (ctx->input.mouse.scroll_delta.y > 0.1)
        trigger = 1;
    key_trigger = input_check_for_key_trigger(ctx, &key_up, NK_KEY_UP,
                                              INITIAL_KEY_DOWN, KEY_NAV_REPEAT);
    trigger += key_trigger;
    if (trigger) {
        *selected = (*selected - 1 + max_items) % max_items;
        changed = 1;
        if (keyboard_updated && key_trigger)
            *keyboard_updated = 1;
    }

    /* Page up */
    key_trigger = input_check_for_key_trigger(ctx, &key_page_up,
                                              NK_KEY_SCROLL_UP,
                                              INITIAL_KEY_DOWN, KEY_NAV_REPEAT);
    if (key_trigger) {
        *selected -= page_size;
        if (*selected < 0) *selected = 0;
        changed = 1;
        if (keyboard_updated)
            *keyboard_updated = 1;
        /* Consume the key so Nuklear's internal scrollbar doesn't also scroll the view. */
        ctx->input.keyboard.keys[NK_KEY_SCROLL_UP].down = 0;
    }

    return changed;
}

int input_check_vertical_navigation(struct nk_context *ctx, int *selected, int max_items, int page_size)
{
    return input_check_vertical_navigation_ex(ctx, selected, max_items, page_size, NULL);
}

int input_check_typeahead(struct nk_context *ctx, char *buf, int max_len, unsigned int timeout_ms)
{
    static char ta_buf[64];
    static int ta_len = 0;
    static unsigned int ta_last_time = 0;
    static int backspace_down = 0;
    int changed = 0;

    /* Timeout: clear buffer after inactivity */
    if (ta_len > 0 && (pc_get_ticks() - ta_last_time) > timeout_ms) {
        ta_len = 0;
        ta_buf[0] = '\0';
        changed = 1;
    }

    /* Accumulate typed alphanumeric characters */
    for (int i = 0; i < ctx->input.keyboard.text_len; i++) {
        char c = ctx->input.keyboard.text[i];
        if ((isalnum((unsigned char)c) || c == '.') && ta_len < max_len - 1) {
            ta_buf[ta_len++] = c;
            ta_buf[ta_len] = '\0';
            ta_last_time = pc_get_ticks();
            changed = 1;
        }
    }

    /* Backspace: remove last char (with debounce) */
    if (ctx->input.keyboard.keys[NK_KEY_BACKSPACE].down) {
        if (!backspace_down && ta_len > 0) {
            ta_buf[--ta_len] = '\0';
            ta_last_time = pc_get_ticks();
            changed = 1;
        }
        backspace_down = 1;
    }
    else {
        backspace_down = 0;
    }

    if (buf && max_len > 0) {
        strncpy(buf, ta_buf, max_len - 1);
        buf[max_len - 1] = '\0';
    }

    return changed;
}

int input_check_horizontal_navigation(struct nk_context *ctx, int *selected, int max_items)
{
    static int tab_down = 0;
    int changed = 0;

    if (!selected || !max_items)
        return 0;

    // Handle Tab navigation
    if (ctx->input.keyboard.keys[NK_KEY_TAB].down && !tab_down) {
        tab_down = 1;
        if (ctx->input.keyboard.keys[NK_KEY_SHIFT].down) {
            // Shift+Tab: Move focus to the left
            *selected = (*selected - 1 + max_items) % max_items;
            changed = 1;
        } else {
            // Tab: Move focus to the right
            *selected = (*selected + 1) % max_items;
            changed = 1;
        }
    }

    if (!ctx->input.keyboard.keys[NK_KEY_TAB].down)
        tab_down = 0;

    return changed;
}

int input_check_left_action(struct nk_context *ctx, struct key_status *status)
{
    if (!status)
        return 0;

    return input_check_for_key_trigger(ctx, status, NK_KEY_LEFT,
                                      INITIAL_KEY_DOWN, KEY_NAV_REPEAT);
}

int input_check_right_action(struct nk_context *ctx, struct key_status *status)
{
    if (!status)
        return 0;

    return input_check_for_key_trigger(ctx, status, NK_KEY_RIGHT,
                                      INITIAL_KEY_DOWN, KEY_NAV_REPEAT);
}

int input_check_value_navigation(struct nk_context *ctx, int *value, int min_value, int max_value)
{
    int changed = 0;
    int trigger;

    trigger = input_check_for_key_trigger(ctx, &key_right, NK_KEY_RIGHT, INITIAL_KEY_DOWN, KEY_NAV_REPEAT);
    if (trigger) {
        if (*value < max_value) {
            (*value)++;
        }
        else {
            *value = min_value;
        }
        changed = 1;
    }

    trigger = input_check_for_key_trigger(ctx, &key_left, NK_KEY_LEFT, INITIAL_KEY_DOWN, KEY_NAV_REPEAT);
    if (trigger) {
        if (*value > min_value) {
            (*value)--;
        }
        else {
            *value = max_value;
        }
        changed = 1;
    }

    return changed;
}
