#include "gl-app.h"

static void draw_string_part(float x, float y, const char *s, const char *e)
{
    int c;
    ui_begin_text(ctx);
    while (s < e)
    {
        s += fz_chartorune(&c, s);
        x += ui_draw_character(ctx, c, x, y + ui.baseline);
    }
    ui_end_text(ctx);
}

static float measure_string_part(const char *s, const char *e)
{
    int c;
    float w = 0;
    while (s < e)
    {
        s += fz_chartorune(&c, s);
        w += ui_measure_character(ctx, c);
    }
    return w;
}

static char *find_string_location(char *s, char *e, float w, float x)
{
    int c;
    while (s < e)
    {
        int n = fz_chartorune(&c, s);
        float cw = ui_measure_character(ctx, c);
        if (w + (cw / 2) >= x)
            return s;
        w += cw;
        s += n;
    }
    return e;
}

static inline int myisalnum(char *s)
{
    int cat, c;
    fz_chartorune(&c, s);
    cat = ucdn_get_general_category(c);
    if (cat >= UCDN_GENERAL_CATEGORY_LL && cat <= UCDN_GENERAL_CATEGORY_LU)
        return 1;
    if (cat >= UCDN_GENERAL_CATEGORY_ND && cat <= UCDN_GENERAL_CATEGORY_NO)
        return 1;
    return 0;
}

static char *prev_char(char *p, char *start)
{
    --p;
    while ((*p & 0xC0) == 0x80 && p > start) /* skip middle and final multibytes */
        --p;
    return p;
}

static char *next_char(char *p)
{
    ++p;
    while ((*p & 0xC0) == 0x80) /* skip middle and final multibytes */
        ++p;
    return p;
}

static char *prev_word(char *p, char *start)
{
    while (p > start && !myisalnum(prev_char(p, start))) p = prev_char(p, start);
    while (p > start && myisalnum(prev_char(p, start))) p = prev_char(p, start);
    return p;
}

static char *next_word(char *p, char *end)
{
    while (p < end && !myisalnum(p)) p = next_char(p);
    while (p < end && myisalnum(p)) p = next_char(p);
    return p;
}

static void ui_input_delete_selection(struct input *input)
{
    char *p = input->p < input->q ? input->p : input->q;
    char *q = input->p > input->q ? input->p : input->q;
    memmove(p, q, input->end - q);
    input->end -= q - p;
    input->p = input->q = p;
}

static void ui_input_paste(struct input *input, const char *buf, int n)
{
    if (input->p != input->q)
        ui_input_delete_selection(input);
    if (input->end + n + 1 < input->text + sizeof(input->text))
    {
        memmove(input->p + n, input->p, input->end - input->p);
        memmove(input->p, buf, n);
        input->p += n;
        input->end += n;
        *input->end = 0;
    }
    input->q = input->p;
}

static int ui_input_key(struct input *input)
{
    switch (ui.key)
    {
    case KEY_LEFT:
        if (ui.mod == GLFW_MOD_CONTROL + GLFW_MOD_SHIFT)
        {
            input->q = prev_word(input->q, input->text);
        }
        else if (ui.mod == GLFW_MOD_CONTROL)
        {
            if (input->p != input->q)
                input->p = input->q = input->p < input->q ? input->p : input->q;
            else
                input->p = input->q = prev_word(input->q, input->text);
        }
        else if (ui.mod == GLFW_MOD_SHIFT)
        {
            if (input->q > input->text)
                input->q = prev_char(input->q, input->text);
        }
        else if (ui.mod == 0)
        {
            if (input->p != input->q)
                input->p = input->q = input->p < input->q ? input->p : input->q;
            else if (input->q > input->text)
                input->p = input->q = prev_char(input->q, input->text);
        }
        break;
    case KEY_RIGHT:
        if (ui.mod == GLFW_MOD_CONTROL + GLFW_MOD_SHIFT)
        {
            input->q = next_word(input->q, input->end);
        }
        else if (ui.mod == GLFW_MOD_CONTROL)
        {
            if (input->p != input->q)
                input->p = input->q = input->p > input->q ? input->p : input->q;
            else
                input->p = input->q = next_word(input->q, input->end);
        }
        else if (ui.mod == GLFW_MOD_SHIFT)
        {
            if (input->q < input->end)
                input->q = next_char(input->q);
        }
        else if (ui.mod == 0)
        {
            if (input->p != input->q)
                input->p = input->q = input->p > input->q ? input->p : input->q;
            else if (input->q < input->end)
                input->p = input->q = next_char(input->q);
        }
        break;
    case KEY_UP:
    case KEY_HOME:
        if (ui.mod == GLFW_MOD_CONTROL + GLFW_MOD_SHIFT)
        {
            input->q = input->text;
        }
        else if (ui.mod == GLFW_MOD_CONTROL)
        {
            input->p = input->q = input->text;
        }
        else if (ui.mod == GLFW_MOD_SHIFT)
        {
            input->q = input->text;
        }
        else if (ui.mod == 0)
        {
            input->p = input->q = input->text;
        }
        break;
    case KEY_DOWN:
    case KEY_END:
        if (ui.mod == GLFW_MOD_CONTROL + GLFW_MOD_SHIFT)
        {
            input->q = input->end;
        }
        else if (ui.mod == GLFW_MOD_CONTROL)
        {
            input->p = input->q = input->end;
        }
        else if (ui.mod == GLFW_MOD_SHIFT)
        {
            input->q = input->end;
        }
        else if (ui.mod == 0)
        {
            input->p = input->q = input->end;
        }
        break;
    case KEY_DELETE:
        if (input->p != input->q)
            ui_input_delete_selection(input);
        else if (input->p < input->end)
        {
            char *np = next_char(input->p);
            memmove(input->p, np, input->end - np);
            input->end -= np - input->p;
            *input->end = 0;
            input->q = input->p;
        }
        break;
    case KEY_ESCAPE:
        return -1;
    case KEY_ENTER:
        return 1;
    case KEY_BACKSPACE:
        if (input->p != input->q)
            ui_input_delete_selection(input);
        else if (input->p > input->text)
        {
            char *pp = prev_char(input->p, input->text);
            memmove(pp, input->p, input->end - input->p);
            input->end -= input->p - pp;
            *input->end = 0;
            input->q = input->p = pp;
        }
        break;
    case KEY_CTL_A:
        input->p = input->q = input->text;
        break;
    case KEY_CTL_E:
        input->p = input->q = input->end;
        break;
    case KEY_CTL_W:
        if (input->p != input->q)
            ui_input_delete_selection(input);
        else
        {
            input->p = prev_word(input->p, input->text);
            ui_input_delete_selection(input);
        }
        break;
    case KEY_CTL_U:
        input->p = input->q = input->end = input->text;
        break;
    case KEY_CTL_C:
    case KEY_CTL_X:
        if (input->p != input->q)
        {
            char buf[sizeof input->text];
            char *p = input->p < input->q ? input->p : input->q;
            char *q = input->p > input->q ? input->p : input->q;
            memmove(buf, p, q - p);
            buf[q-p] = 0;
            glfwSetClipboardString(window, buf);
            if (ui.key == KEY_CTL_X)
                ui_input_delete_selection(input);
        }
        break;
    case KEY_CTL_V:
        {
            const char *buf = glfwGetClipboardString(window);
            if (buf)
                ui_input_paste(input, buf, (int)strlen(buf));
        }
        break;
    default:
        if (ui.key >= 32)
        {
            int cat = ucdn_get_general_category(ui.key);
            if (ui.key == ' ' || (cat >= UCDN_GENERAL_CATEGORY_LL && cat < UCDN_GENERAL_CATEGORY_ZL))
            {
                char buf[8];
                int n = fz_runetochar(buf, ui.key);
                ui_input_paste(input, buf, n);
            }
        }
        break;
    }
    return 0;
}

int ui_input(int x0, int y0, int x1, int y1, struct input *input)
{
    float px, qx, ex;
    char *p, *q;
    int state;

    if (ui.x >= x0 && ui.x < x1 && ui.y >= y0 && ui.y < y1)
    {
        ui.hot = input;
        if (!ui.active && ui.down)
        {
            input->p = find_string_location(input->text, input->end, x0 + 2, ui.x);
            ui.active = input;
        }
    }

    if (ui.active == input)
    {
        input->q = find_string_location(input->text, input->end, x0 + 2, ui.x);
        ui.focus = input;
    }

    if (!ui.focus)
        ui.focus = input;

    if (ui.focus == input)
        state = ui_input_key(input);
    else
        state = 0;

    glColor4f(0, 0, 0, 1);
    glRectf(x0, y0, x1, y1);

    glColor4f(1, 1, 1, 1);
    glRectf(x0+1, y0+1, x1-1, y1-1);

    p = input->p < input->q ? input->p : input->q;
    q = input->p > input->q ? input->p : input->q;

    px = x0 + 2 + measure_string_part(input->text, p);
    qx = px + measure_string_part(p, q);
    ex = qx + measure_string_part(q, input->end);

    if (ui.focus)
    {
        glColor4f(0.6f, 0.6f, 1.0f, 1.0f);
        glRectf(px, y0 + 2, qx+1, y1 - 2);
    }

    glColor4f(0, 0, 0, 1);
    draw_string_part(x0 + 2, y0 + 2, input->text, input->end);

    return state;
}