#include "XcbKeyboard.h"

// Header is c-like, uses explicit as a var
#define explicit explicit_
#include <xcb/xkb.h>
#undef explicit
#include <xkbcommon/xkbcommon-x11.h>

#include "FileLogger.h"

XcbKeyboard::XcbKeyboard(xcb_connection_t* connection)
    : Keyboard(),
      mConnection(connection)
{
    mXkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
    if (!mXkbContext)
    {
        MLOG_ERROR("Failed to get XKB context");
    }
}

std::unique_ptr<XcbKeyboard> XcbKeyboard::Create(xcb_connection_t* connection)
{
    return std::make_unique<XcbKeyboard>(connection);
}

void XcbKeyboard::updateState(xcb_xkb_state_notify_event_t *state)
{
    xkb_state_update_mask(mXkbState, state->baseMods,
                                      state->latchedMods,
                                      state->lockedMods,
                                      state->baseGroup,
                                      state->latchedGroup,
                                      state->lockedGroup);
}

Keyboard::Function XcbKeyboard::getFunction(Keyboard::KeyCode code)
{
    if (!mXkbState)
    {
        getKeyMap();
    }

    xkb_keysym_t sym = xkb_state_key_get_one_sym(mXkbState, code);
    if (sym == XKB_KEY_Return)
    {
        return Keyboard::Function::ENTER;
    }
    else if(sym == XKB_KEY_BackSpace)
    {
        return Keyboard::Function::BACKSPACE;
    }
    return Keyboard::Function::UNSET;
}

std::string XcbKeyboard::getKeyString(KeyCode keyCode)
{
    if (!mXkbState)
    {
        getKeyMap();
    }

    auto size = xkb_state_key_get_utf8(mXkbState, keyCode, nullptr, 0) + 1;
    if (size <= 1)
    {
        return {};
    }

    char buffer[size];
    xkb_state_key_get_utf8(mXkbState, keyCode, buffer, size);

    std::string ret(buffer);
    return ret;
}

void XcbKeyboard::getKeyMap()
{
    auto device_id = xkb_x11_get_core_keyboard_device_id(mConnection);
    if (device_id == -1)
    {
        MLOG_ERROR("Failed to get connection for keymap");
    }

    auto xkb_keymap = xkb_x11_keymap_new_from_device(mXkbContext, mConnection, device_id, XKB_KEYMAP_COMPILE_NO_FLAGS);

    struct xkb_state* xkb_state = xkb_x11_state_new_from_device(xkb_keymap, mConnection, device_id);
    xkb_keymap_unref(mXkbKeymap);
    xkb_state_unref(mXkbState);
    mXkbKeymap = xkb_keymap;
    mXkbState = xkb_state;

    xcb_xkb_select_events_details_t details;
    auto cookie = xcb_xkb_select_events(mConnection, XCB_XKB_ID_USE_CORE_KBD,
            XCB_XKB_EVENT_TYPE_STATE_NOTIFY, 0, XCB_XKB_EVENT_TYPE_STATE_NOTIFY, 0, 0, &details);

    // Check errors
    auto error = xcb_request_check(mConnection, cookie);
    if (error)
    {
        MLOG_ERROR("Failed to select XKB events");
    }
}
