#include "Jtk.h"

#ifdef JTK_X

#include <GL/glx.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include "Jtk_internal.h"
#include <unordered_map>

/** Do not wait longer than 100ms */
#define MAX_WAIT_TIME 100000u

static std::unordered_map<unsigned int, size_t> g_repeating_keys;
static std::unordered_map<size_t, unsigned int> g_key_repeat_timers;

#if 0
static void StopKeyRepeat(unsigned int x_keycode)
{
    auto it = g_repeating_keys.find(x_keycode);
    if (it != g_repeating_keys.end())
    {
        size_t timer_id = it->second;
        Jtk_RemoveTimer(timer_id);
        g_repeating_keys.erase(x_keycode);
        g_key_repeat_timers.erase(timer_id);
    }
}
#endif

#if 0
static unsigned int GetXState()
{
    Window win;
    int i;
    unsigned int state;
    XQueryPointer(g_display, RootWindow(g_display, DefaultScreen(g_display)), &win, &win, &i, &i, &i, &i, &state);
    return state;
}
#endif

static uint32_t XStateToJtkKeyMods(unsigned int x_state)
{
    uint32_t mods = 0u;
    /* OR in the modifier states */
    if (x_state & ShiftMask)
    {
        mods |= JES_KEY_MODS_SHIFT;
    }
    if (x_state & LockMask)
    {
        mods |= JES_KEY_MODS_LOCK;
    }
    if (x_state & ControlMask)
    {
        mods |= JES_KEY_MODS_CTRL;
    }
    return mods;
}

static uint32_t XKeyToJtkKey(unsigned int x_keycode, unsigned int x_state)
{
    XKeyEvent x_key_event;
    x_key_event.type = KeyPress;
    x_key_event.display = g_display;
    /* Turn off the ControlMask bit for looking up keys. We'll handle control
     * keys ourselves. */
    x_key_event.state = x_state & ~ControlMask;
    x_key_event.keycode = x_keycode;
    char buffer;
    KeySym keysym;
    uint32_t key = JES_KEY_UNKNOWN;

    if (XLookupString(&x_key_event, &buffer, 1, &keysym, nullptr) > 0)
    {
        if (buffer == '\r')
        {
            key = '\n';
        }
        else
        {
            key = buffer;
        }
    }
    else
    {
        switch (keysym)
        {
        case XK_F1:             key = JES_KEY_F1;        break;
        case XK_F2:             key = JES_KEY_F2;        break;
        case XK_F3:             key = JES_KEY_F3;        break;
        case XK_F4:             key = JES_KEY_F4;        break;
        case XK_F5:             key = JES_KEY_F5;        break;
        case XK_F6:             key = JES_KEY_F6;        break;
        case XK_F7:             key = JES_KEY_F7;        break;
        case XK_F8:             key = JES_KEY_F8;        break;
        case XK_F9:             key = JES_KEY_F9;        break;
        case XK_F10:            key = JES_KEY_F10;       break;
        case XK_F11:            key = JES_KEY_F11;       break;
        case XK_F12:            key = JES_KEY_F12;       break;
        case XK_F13:            key = JES_KEY_F13;       break;
        case XK_F14:            key = JES_KEY_F14;       break;
        case XK_F15:            key = JES_KEY_F15;       break;
        case XK_F16:            key = JES_KEY_F16;       break;
        case XK_F17:            key = JES_KEY_F17;       break;
        case XK_F18:            key = JES_KEY_F18;       break;
        case XK_F19:            key = JES_KEY_F19;       break;
        case XK_F20:            key = JES_KEY_F20;       break;
        case XK_F21:            key = JES_KEY_F21;       break;
        case XK_F22:            key = JES_KEY_F22;       break;
        case XK_F23:            key = JES_KEY_F23;       break;
        case XK_F24:            key = JES_KEY_F24;       break;
        case XK_F25:            key = JES_KEY_F25;       break;
        case XK_F26:            key = JES_KEY_F26;       break;
        case XK_F27:            key = JES_KEY_F27;       break;
        case XK_F28:            key = JES_KEY_F28;       break;
        case XK_F29:            key = JES_KEY_F29;       break;
        case XK_F30:            key = JES_KEY_F30;       break;
        case XK_F31:            key = JES_KEY_F31;       break;

        case XK_Shift_L:        key = JES_KEY_SHIFT_L;   break;
        case XK_Shift_R:        key = JES_KEY_SHIFT_R;   break;
        case XK_Control_L:      key = JES_KEY_CTRL_L;    break;
        case XK_Control_R:      key = JES_KEY_CTRL_R;    break;
        case XK_Caps_Lock:      key = JES_KEY_CAPS_LOCK; break;
        case XK_Shift_Lock:     key = JES_KEY_SHIFT_LOCK;break;

        case XK_Meta_L:         key = JES_KEY_META_L;    break;
        case XK_Meta_R:         key = JES_KEY_META_R;    break;
        case XK_Alt_L:          key = JES_KEY_ALT_L;     break;
        case XK_Alt_R:          key = JES_KEY_ALT_R;     break;
        case XK_Super_L:        key = JES_KEY_SUPER_L;   break;
        case XK_Super_R:        key = JES_KEY_SUPER_R;   break;

        case XK_Home:           key = JES_KEY_HOME; break;
        case XK_Left:           key = JES_KEY_LEFT; break;
        case XK_Up:             key = JES_KEY_UP; break;
        case XK_Right:          key = JES_KEY_RIGHT; break;
        case XK_Down:           key = JES_KEY_DOWN; break;
        case XK_Page_Up:        key = JES_KEY_PAGE_UP; break;
        case XK_Page_Down:      key = JES_KEY_PAGE_DOWN; break;
        case XK_End:            key = JES_KEY_END; break;
        case XK_Begin:          key = JES_KEY_BEGIN; break;

        case XK_Select:         key = JES_KEY_SELECT; break;
        case XK_Print:          key = JES_KEY_PRINT; break;
        case XK_Execute:        key = JES_KEY_EXECUTE; break;
        case XK_Insert:         key = JES_KEY_INSERT; break;
        case XK_Undo:           key = JES_KEY_UNDO; break;
        case XK_Redo:           key = JES_KEY_REDO; break;
        case XK_Menu:           key = JES_KEY_MENU; break;
        case XK_Find:           key = JES_KEY_FIND; break;
        case XK_Cancel:         key = JES_KEY_CANCEL; break;
        case XK_Help:           key = JES_KEY_HELP; break;
        case XK_Break:          key = JES_KEY_BREAK; break;
        case XK_Num_Lock:       key = JES_KEY_NUM_LOCK; break;

        case XK_KP_Space:       key = JES_KEY_KP_SPACE; break;
        case XK_KP_Tab:         key = JES_KEY_KP_TAB; break;
        case XK_KP_Enter:       key = JES_KEY_KP_ENTER; break;
        case XK_KP_F1:          key = JES_KEY_KP_F1; break;
        case XK_KP_F2:          key = JES_KEY_KP_F2; break;
        case XK_KP_F3:          key = JES_KEY_KP_F3; break;
        case XK_KP_F4:          key = JES_KEY_KP_F4; break;
        case XK_KP_Home:        key = JES_KEY_KP_HOME; break;
        case XK_KP_Left:        key = JES_KEY_KP_LEFT; break;
        case XK_KP_Up:          key = JES_KEY_KP_UP; break;
        case XK_KP_Right:       key = JES_KEY_KP_RIGHT; break;
        case XK_KP_Down:        key = JES_KEY_KP_DOWN; break;
        case XK_KP_Page_Up:     key = JES_KEY_KP_PAGE_UP; break;
        case XK_KP_Page_Down:   key = JES_KEY_KP_PAGE_DOWN; break;
        case XK_KP_End:         key = JES_KEY_KP_END; break;
        case XK_KP_Begin:       key = JES_KEY_KP_BEGIN; break;
        case XK_KP_Insert:      key = JES_KEY_KP_INSERT; break;
        case XK_KP_Delete:      key = JES_KEY_KP_DELETE; break;
        case XK_KP_Equal:       key = JES_KEY_KP_EQUAL; break;
        case XK_KP_Multiply:    key = JES_KEY_KP_MULTIPLY; break;
        case XK_KP_Add:         key = JES_KEY_KP_ADD; break;
        case XK_KP_Separator:   key = JES_KEY_KP_SEPARATOR; break;
        case XK_KP_Subtract:    key = JES_KEY_KP_SUBTRACT; break;
        case XK_KP_Decimal:     key = JES_KEY_KP_DECIMAL; break;
        case XK_KP_Divide:      key = JES_KEY_KP_DIVIDE; break;

        case XK_KP_0:           key = JES_KEY_KP_0; break;
        case XK_KP_1:           key = JES_KEY_KP_1; break;
        case XK_KP_2:           key = JES_KEY_KP_2; break;
        case XK_KP_3:           key = JES_KEY_KP_3; break;
        case XK_KP_4:           key = JES_KEY_KP_4; break;
        case XK_KP_5:           key = JES_KEY_KP_5; break;
        case XK_KP_6:           key = JES_KEY_KP_6; break;
        case XK_KP_7:           key = JES_KEY_KP_7; break;
        case XK_KP_8:           key = JES_KEY_KP_8; break;
        case XK_KP_9:           key = JES_KEY_KP_9; break;
        }
    }

    /* OR in the modifier states */
    key |= XStateToJtkKeyMods(x_state);

    return key;
}

#if 0
static Bool KeyRepeatCheckIfEvent(Display * display, XEvent * chkev,
    XPointer arg)
{
    XEvent * release_event = (XEvent *)arg;
    if (chkev->type == KeyPress &&
        chkev->xkey.keycode == release_event->xkey.keycode &&
        chkev->xkey.time - release_event->xkey.time < 2)
        return True;
    return False;
}

/**
 * Check to see if this is a repeated key.
 */
static bool IsRepeatKey(Display * display, XEvent * event)
{
    XEvent dummyev;
    if (XPending(display))
    {
        if (XCheckIfEvent(display, &dummyev, KeyRepeatCheckIfEvent,
                    (XPointer)event) == True)
        {
            return true;
        }
    }
    return false;
}
#endif

static Bool MatchKeyPress(Display * display, XEvent * event, XPointer arg)
{
    XEvent * match_event = (XEvent *)arg;
    return (event->type == match_event->type) &&
        (event->xkey.window == match_event->xkey.window) &&
        (event->xkey.state == match_event->xkey.state) &&
        (event->xkey.keycode == match_event->xkey.keycode);
}

/**
 * Process an X key press event.
 *
 * @param x_event
 *   Pointer to the X event.
 * @param event
 *   Pointer to the Jtk event.
 */
static bool ProcessXKeyPressEvent(XEvent * x_event, Jtk_Event * event)
{
    unsigned int x_keycode = x_event->xkey.keycode;
    event->type = JTK_EVENT_KEY_PRESS;
    event->key.repeat = false;
    event->key.key = XKeyToJtkKey(x_keycode, x_event->xkey.state);
    event->key.x_keycode = x_keycode;
    /* Remove any following keypress events for the same keycode from the X
     * queue. */
    XEvent remove_event;
    while (XCheckIfEvent(g_display, &remove_event, MatchKeyPress, (XPointer)x_event) == True)
    {
    }
    return true;
}

/**
 * Process an X key release event.
 *
 * @param x_event
 *   Pointer to the X event.
 * @param event
 *   Pointer to the Jtk event.
 */
static bool ProcessXKeyReleaseEvent(XEvent * x_event, Jtk_Event * event)
{
#if 0
    if (IsRepeatKey(g_display, x_event))
    {
        return false;
    }
    StopKeyRepeat(x_event->xkey.keycode);
#endif
    return false;
}

static Bool MatchButtonPress(Display * display, XEvent * event, XPointer arg)
{
    XEvent * match_event = (XEvent *)arg;
    return (event->type == match_event->type) &&
        (event->xbutton.window == match_event->xbutton.window) &&
        (event->xbutton.state == match_event->xbutton.state) &&
        (event->xbutton.button == match_event->xbutton.button);
}

/**
 * Process an X button press event.
 */
static bool ProcessXButtonPressEvent(XEvent * x_event, Jtk_Event * event)
{
    event->type = JTK_EVENT_BUTTON_PRESS;
    event->button.mods = XStateToJtkKeyMods(x_event->xbutton.state);
    event->button.button = x_event->xbutton.button;
    /* If this is a mouse wheel scroll event, remove any following scroll
     * events. */
    if ((event->button.button == 4) || (event->button.button == 5))
    {
        XEvent remove_event;
        while (XCheckIfEvent(g_display, &remove_event, MatchButtonPress, (XPointer)x_event) == True)
        {
        }
    }
    return true;
}

/**
 * Process an X configure event.
 *
 * @param x_event
 *   Pointer to the X event.
 * @param event
 *   Pointer to the Jtk event.
 */
static bool ProcessConfigureEvent(XEvent * x_event, Jtk_Event * event)
{
    event->type = JTK_EVENT_WINDOW_RESIZE;
    event->resize.width = x_event->xconfigure.width;
    event->resize.height = x_event->xconfigure.height;
    return true;
}

/**
 * Process an X ClientMessage event.
 *
 * @param x_event
 *   Pointer to the X event.
 * @param event
 *   Pointer to the Jtk event.
 */
static bool ProcessXClientMessageEvent(XEvent * x_event, Jtk_Event * event)
{
    Atom wm_delete_window_atom = XInternAtom(g_display, "WM_DELETE_WINDOW", False);
    if (x_event->xclient.data.l[0] == (long)wm_delete_window_atom)
    {
        event->type = JTK_EVENT_WINDOW_CLOSE;
        return true;
    }
    return false;
}

/**
 * Process an X event.
 *
 * @param x_event
 *   Pointer to the X event.
 * @param event
 *   Pointer to the Jtk event.
 *
 * @retval true
 *   The event should be passed to the user and event has been filled in.
 * @retval false
 *   The event should not be passed to the user.
 */
static bool ProcessXEvent(XEvent * x_event, Jtk_Event * event)
{
    switch (x_event->type)
    {
    case KeyPress:
        return ProcessXKeyPressEvent(x_event, event);

    case KeyRelease:
        return ProcessXKeyReleaseEvent(x_event, event);

    case ButtonPress:
        return ProcessXButtonPressEvent(x_event, event);

    case ButtonRelease:
        break;

    case Expose:
        event->type = JTK_EVENT_WINDOW_EXPOSE;
        return true;

    case GraphicsExpose:
        event->type = JTK_EVENT_WINDOW_EXPOSE;
        return true;

    case MapNotify:
        event->type = JTK_EVENT_WINDOW_EXPOSE;
        return true;

    case ConfigureNotify:
        return ProcessConfigureEvent(x_event, event);

    case ClientMessage:
        return ProcessXClientMessageEvent(x_event, event);

    case MappingNotify:
        XRefreshKeyboardMapping(&x_event->xmapping);
        return false;
    }

    return false;
}

bool Jtk_CheckEvent(Jtk_Event * event)
{
    /* First check for an X event. */
    while (XPending(g_display) > 0)
    {
        XEvent x_event;
        XNextEvent(g_display, &x_event);
        if (ProcessXEvent(&x_event, event))
            return true;
    }

    /* Next check if any timer has expired. */
    size_t timer_id = Jtk_GetExpiredTimer();
    if (timer_id != (size_t)-1)
    {
#if 0
        auto it = g_key_repeat_timers.find(timer_id);
        if (it != g_key_repeat_timers.end())
        {
            event->type = JTK_EVENT_KEY_PRESS;
            event->key.repeat = true;
            event->key.key = XKeyToJtkKey(it->second);
            event->key.x_keycode = it->second;
        }
        else
        {
#endif
            event->type = JTK_EVENT_TIMER;
            event->timer.timer_id = timer_id;
            event->timer.user1 = Jtk_GetTimerUserData(timer_id, 0u);
            event->timer.user2 = Jtk_GetTimerUserData(timer_id, 1u);
#if 0
        }
#endif
        Jtk_ServiceTimer(timer_id);
        return true;
    }

    return false;
}

void Jtk_WaitEvent(Jtk_Event * event)
{
    for (;;)
    {
        if (Jtk_CheckEvent(event))
            return;

        /* Wait for something to happen. */
        uint64_t time_to_wait = Jtk_TimeToNextTimerExpiration();
        if (time_to_wait > MAX_WAIT_TIME)
        {
            time_to_wait = MAX_WAIT_TIME;
        }

        int x_fd = ConnectionNumber(g_display);
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(x_fd, &fds);
        struct timeval tv;
        tv.tv_sec = time_to_wait / 1000000u;
        tv.tv_usec = time_to_wait % 1000000u;
        select(x_fd + 1, &fds, nullptr, nullptr, &tv);
    }
}

#if 0
void Jtk_BeginKeyRepeat(Jtk_KeyEvent * key_event, uint32_t delay, uint32_t interval)
{
    StopKeyRepeat(key_event->x_keycode);
    size_t timer_id = Jtk_AddTimer(delay, interval, nullptr, nullptr);
    g_repeating_keys[key_event->x_keycode] = timer_id;
    g_key_repeat_timers[timer_id] = key_event->x_keycode;
}
#endif

#endif
