#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"

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

/**
 * Process an X keyboard event.
 *
 * @param x_event
 *   Pointer to the X event.
 * @param event
 *   Pointer to the Jtk event.
 */
static bool ProcessXKeyEvent(XEvent * x_event, Jtk_Event * event)
{
    char buffer;
    KeySym keysym;
    /* Make a copy of x_event and turn off the ControlMask bit for lookup
     * up keycodes. We'll handle control keys ourselves. */
    XEvent x_event_for_key_lookup = *x_event;
    x_event_for_key_lookup.xkey.state &= ~ControlMask;
    if (XLookupString(&x_event_for_key_lookup.xkey, &buffer, 1, &keysym, nullptr) > 0)
    {
        if (buffer == '\r')
        {
            event->key.key = '\n';
        }
        else
        {
            event->key.key = buffer;
        }
    }
    else
    {
        switch (keysym)
        {
        case XK_F1:             event->key.key = JTK_KEY_F1;        break;
        case XK_F2:             event->key.key = JTK_KEY_F2;        break;
        case XK_F3:             event->key.key = JTK_KEY_F3;        break;
        case XK_F4:             event->key.key = JTK_KEY_F4;        break;
        case XK_F5:             event->key.key = JTK_KEY_F5;        break;
        case XK_F6:             event->key.key = JTK_KEY_F6;        break;
        case XK_F7:             event->key.key = JTK_KEY_F7;        break;
        case XK_F8:             event->key.key = JTK_KEY_F8;        break;
        case XK_F9:             event->key.key = JTK_KEY_F9;        break;
        case XK_F10:            event->key.key = JTK_KEY_F10;       break;
        case XK_F11:            event->key.key = JTK_KEY_F11;       break;
        case XK_F12:            event->key.key = JTK_KEY_F12;       break;
        case XK_F13:            event->key.key = JTK_KEY_F13;       break;
        case XK_F14:            event->key.key = JTK_KEY_F14;       break;
        case XK_F15:            event->key.key = JTK_KEY_F15;       break;
        case XK_F16:            event->key.key = JTK_KEY_F16;       break;
        case XK_F17:            event->key.key = JTK_KEY_F17;       break;
        case XK_F18:            event->key.key = JTK_KEY_F18;       break;
        case XK_F19:            event->key.key = JTK_KEY_F19;       break;
        case XK_F20:            event->key.key = JTK_KEY_F20;       break;
        case XK_F21:            event->key.key = JTK_KEY_F21;       break;
        case XK_F22:            event->key.key = JTK_KEY_F22;       break;
        case XK_F23:            event->key.key = JTK_KEY_F23;       break;
        case XK_F24:            event->key.key = JTK_KEY_F24;       break;
        case XK_F25:            event->key.key = JTK_KEY_F25;       break;
        case XK_F26:            event->key.key = JTK_KEY_F26;       break;
        case XK_F27:            event->key.key = JTK_KEY_F27;       break;
        case XK_F28:            event->key.key = JTK_KEY_F28;       break;
        case XK_F29:            event->key.key = JTK_KEY_F29;       break;
        case XK_F30:            event->key.key = JTK_KEY_F30;       break;
        case XK_F31:            event->key.key = JTK_KEY_F31;       break;

        case XK_Shift_L:        event->key.key = JTK_KEY_SHIFT_L;   break;
        case XK_Shift_R:        event->key.key = JTK_KEY_SHIFT_R;   break;
        case XK_Control_L:      event->key.key = JTK_KEY_CTRL_L;    break;
        case XK_Control_R:      event->key.key = JTK_KEY_CTRL_R;    break;
        case XK_Caps_Lock:      event->key.key = JTK_KEY_CAPS_LOCK; break;
        case XK_Shift_Lock:     event->key.key = JTK_KEY_SHIFT_LOCK;break;

        case XK_Meta_L:         event->key.key = JTK_KEY_META_L;    break;
        case XK_Meta_R:         event->key.key = JTK_KEY_META_R;    break;
        case XK_Alt_L:          event->key.key = JTK_KEY_ALT_L;     break;
        case XK_Alt_R:          event->key.key = JTK_KEY_ALT_R;     break;
        case XK_Super_L:        event->key.key = JTK_KEY_SUPER_L;   break;
        case XK_Super_R:        event->key.key = JTK_KEY_SUPER_R;   break;

        case XK_Home:           event->key.key = JTK_KEY_HOME; break;
        case XK_Left:           event->key.key = JTK_KEY_LEFT; break;
        case XK_Up:             event->key.key = JTK_KEY_UP; break;
        case XK_Right:          event->key.key = JTK_KEY_RIGHT; break;
        case XK_Down:           event->key.key = JTK_KEY_DOWN; break;
        case XK_Page_Up:        event->key.key = JTK_KEY_PAGE_UP; break;
        case XK_Page_Down:      event->key.key = JTK_KEY_PAGE_DOWN; break;
        case XK_End:            event->key.key = JTK_KEY_END; break;
        case XK_Begin:          event->key.key = JTK_KEY_BEGIN; break;

        case XK_Select:         event->key.key = JTK_KEY_SELECT; break;
        case XK_Print:          event->key.key = JTK_KEY_PRINT; break;
        case XK_Execute:        event->key.key = JTK_KEY_EXECUTE; break;
        case XK_Insert:         event->key.key = JTK_KEY_INSERT; break;
        case XK_Undo:           event->key.key = JTK_KEY_UNDO; break;
        case XK_Redo:           event->key.key = JTK_KEY_REDO; break;
        case XK_Menu:           event->key.key = JTK_KEY_MENU; break;
        case XK_Find:           event->key.key = JTK_KEY_FIND; break;
        case XK_Cancel:         event->key.key = JTK_KEY_CANCEL; break;
        case XK_Help:           event->key.key = JTK_KEY_HELP; break;
        case XK_Break:          event->key.key = JTK_KEY_BREAK; break;
        case XK_Num_Lock:       event->key.key = JTK_KEY_NUM_LOCK; break;

        case XK_KP_Space:       event->key.key = JTK_KEY_KP_SPACE; break;
        case XK_KP_Tab:         event->key.key = JTK_KEY_KP_TAB; break;
        case XK_KP_Enter:       event->key.key = JTK_KEY_KP_ENTER; break;
        case XK_KP_F1:          event->key.key = JTK_KEY_KP_F1; break;
        case XK_KP_F2:          event->key.key = JTK_KEY_KP_F2; break;
        case XK_KP_F3:          event->key.key = JTK_KEY_KP_F3; break;
        case XK_KP_F4:          event->key.key = JTK_KEY_KP_F4; break;
        case XK_KP_Home:        event->key.key = JTK_KEY_KP_HOME; break;
        case XK_KP_Left:        event->key.key = JTK_KEY_KP_LEFT; break;
        case XK_KP_Up:          event->key.key = JTK_KEY_KP_UP; break;
        case XK_KP_Right:       event->key.key = JTK_KEY_KP_RIGHT; break;
        case XK_KP_Down:        event->key.key = JTK_KEY_KP_DOWN; break;
        case XK_KP_Page_Up:     event->key.key = JTK_KEY_KP_PAGE_UP; break;
        case XK_KP_Page_Down:   event->key.key = JTK_KEY_KP_PAGE_DOWN; break;
        case XK_KP_End:         event->key.key = JTK_KEY_KP_END; break;
        case XK_KP_Begin:       event->key.key = JTK_KEY_KP_BEGIN; break;
        case XK_KP_Insert:      event->key.key = JTK_KEY_KP_INSERT; break;
        case XK_KP_Delete:      event->key.key = JTK_KEY_KP_DELETE; break;
        case XK_KP_Equal:       event->key.key = JTK_KEY_KP_EQUAL; break;
        case XK_KP_Multiply:    event->key.key = JTK_KEY_KP_MULTIPLY; break;
        case XK_KP_Add:         event->key.key = JTK_KEY_KP_ADD; break;
        case XK_KP_Separator:   event->key.key = JTK_KEY_KP_SEPARATOR; break;
        case XK_KP_Subtract:    event->key.key = JTK_KEY_KP_SUBTRACT; break;
        case XK_KP_Decimal:     event->key.key = JTK_KEY_KP_DECIMAL; break;
        case XK_KP_Divide:      event->key.key = JTK_KEY_KP_DIVIDE; break;

        case XK_KP_0:           event->key.key = JTK_KEY_KP_0; break;
        case XK_KP_1:           event->key.key = JTK_KEY_KP_1; break;
        case XK_KP_2:           event->key.key = JTK_KEY_KP_2; break;
        case XK_KP_3:           event->key.key = JTK_KEY_KP_3; break;
        case XK_KP_4:           event->key.key = JTK_KEY_KP_4; break;
        case XK_KP_5:           event->key.key = JTK_KEY_KP_5; break;
        case XK_KP_6:           event->key.key = JTK_KEY_KP_6; break;
        case XK_KP_7:           event->key.key = JTK_KEY_KP_7; break;
        case XK_KP_8:           event->key.key = JTK_KEY_KP_8; break;
        case XK_KP_9:           event->key.key = JTK_KEY_KP_9; break;

        default:
            return false;
        }
    }

    /* OR in the modifier states */
    if (x_event->xkey.state & ShiftMask)
    {
        event->key.key |= JTK_KEY_MODS_SHIFT;
    }
    if (x_event->xkey.state & LockMask)
    {
        event->key.key |= JTK_KEY_MODS_LOCK;
    }
    if (x_event->xkey.state & ControlMask)
    {
        event->key.key |= JTK_KEY_MODS_CTRL;
    }

    return true;
}

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;
}

/**
 * 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:
        event->type = JTK_EVENT_KEY_PRESS;
        return ProcessXKeyEvent(x_event, event);

    case KeyRelease:
        if (IsRepeatKey(g_display, x_event)) {
            return false;
        }
        event->type = JTK_EVENT_KEY_RELEASE;
        return ProcessXKeyEvent(x_event, event);

    case ButtonPress:
        break;

    case ButtonRelease:
        break;

    case Expose:
        break;

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

    return false;
}

void Jtk_WaitEvent(Jtk_Event * event)
{
    for (;;)
    {
        /* First check if any timer has expired. */
        size_t timer_id = Jtk_GetExpiredTimer();
        if (timer_id != (size_t)-1)
        {
            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);
            Jtk_ServiceTimer(timer_id);
            return;
        }

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

        /* Finally, 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);
    }
}

#endif
