#include "../../../oden_plugin_rs/plugin.h"

#pragma warning(push)
#pragma warning(disable : 4100)
#pragma warning(disable : 4081)
#include "include/cef_app.h"
#include "include/cef_client.h"
#include "include/cef_command_line.h"
#include "include/cef_render_handler.h"
#include "include/wrapper/cef_helpers.h"

// clang-format off
#ifdef _WIN32
#include <windows.h>
#include <shellapi.h>
#endif
// clang-format on

#pragma warning(pop)

#include <atomic>
#include <chrono>
#include <cinttypes>
#include <cstdint>
#include <cstdio>
#include <thread>

#define ARRAY_COUNT(arr) (sizeof(arr) / sizeof((arr)[0]))

namespace
{
    struct MouseState
    {
        int32_t x;
        int32_t y;
    };

    enum ProcessType
    {
        PROCESS_TYPE_BROWSER,
        PROCESS_TYPE_RENDERER,
        PROCESS_TYPE_OTHER,
    };

} // namespace

extern "C"
{
    void oden_cef_publish_new_image(int32_t width, int32_t height, const uint8_t *data);
    bool oden_cef_should_run();
    bool oden_cef_get_mouse_state(MouseState *mouseState);
    bool oden_cef_get_events(OdenWindowEvent *event_buffer, uint32_t *buffer_count);
    bool oden_cef_get_address(uint8_t *buffer, int32_t buffer_len);
    bool oden_cef_get_resolution(int32_t *width, int32_t *height);
    bool oden_cef_resolution_changed();
    void oden_cef_log(const char *buffer);
    bool oden_cef_should_refresh();
    bool oden_cef_should_navigate_back();
    bool oden_cef_should_navigate_forward();
    int64_t oden_cef_time();
    const char *oden_cef_js_api();
    const char *oden_cef_temp_path();
    void oden_cef_start_ipc(uint16_t port);
} // extern "C"

CefRefPtr<CefCommandLine> create_command_line(const CefMainArgs &main_args)
{
    (void)main_args;
    CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
#ifdef _WIN32
    command_line->InitFromString(::GetCommandLineW());
#else
    command_line->InitFromArgv(main_args.argc, main_args.argv);
#endif
    return command_line;
}

class App
    : public CefApp,
      public CefRenderProcessHandler
{
public:
    App() {}

    virtual void OnBeforeCommandLineProcessing(
        const CefString &process_type, CefRefPtr<CefCommandLine> command_line)
    {
        (void)process_type;
        command_line->AppendSwitchWithValue(CefString("enable-media-stream"), CefString("true"));
        command_line->AppendSwitchWithValue(CefString("use-fake-ui-for-media-stream"), CefString("true"));
    }

    IMPLEMENT_REFCOUNTING(App);
    DISALLOW_COPY_AND_ASSIGN(App);
};

class RenderHandler final : public CefRenderHandler
{
public:
    RenderHandler() : lastWidth(0), lastHeight(0) {}

    virtual ~RenderHandler() override {}

    virtual void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect) override
    {
        (void)browser;
        int32_t width = 0;
        int32_t height = 0;
        if (oden_cef_get_resolution(&width, &height))
        {

            if (width < 1)
            {
                width = 1;
            }
            if (height < 1)
            {
                height = 1;
            }

            lastWidth = width;
            lastHeight = height;
        }
        else
        {
            width = lastWidth;
            height = lastHeight;
        }

        rect.Set(0, 0, width, height);
    }

    virtual void OnPaint(
        CefRefPtr<CefBrowser> browser,
        PaintElementType type,
        const RectList &dirtyRects,
        const void *buffer,
        int paintWidth,
        int paintHeight) override
    {
        (void)browser;
        (void)type;
        (void)dirtyRects;

        oden_cef_publish_new_image(paintWidth, paintHeight, reinterpret_cast<const uint8_t *>(buffer));
    }

    int32_t lastWidth;
    int32_t lastHeight;

    IMPLEMENT_REFCOUNTING(RenderHandler);
    DISALLOW_COPY_AND_ASSIGN(RenderHandler);
};

class DisplayHandler final : public CefDisplayHandler
{
public:
    DisplayHandler() {}

    virtual ~DisplayHandler() override {}

    virtual bool OnConsoleMessage(
        CefRefPtr<CefBrowser> browser,
        cef_log_severity_t level,
        const CefString &message,
        const CefString &source,
        int line)
    {

        (void)browser;
        (void)level;

        std::string messageString = message.ToString();
        std::string sourceString = source.ToString();

        char buffer[16 * 4096] = {};
        snprintf(buffer, sizeof(buffer), "%s:%d %s", sourceString.c_str(), line, messageString.c_str());

        oden_cef_log(buffer);

        return true;
    }

    IMPLEMENT_REFCOUNTING(DisplayHandler);
    DISALLOW_COPY_AND_ASSIGN(DisplayHandler);
};

class LoadHandler final : public CefLoadHandler
{
public:
    LoadHandler(const char *odenJsIn) : odenJs(odenJsIn) {}

    virtual ~LoadHandler() override {}

    virtual void OnLoadStart(
        CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, TransitionType transition_type) override
    {
        (void)browser;
        (void)transition_type;

        if (frame->IsMain())
        {
            frame->ExecuteJavaScript(odenJs, "", 0);
        }
    }

    const char *odenJs;

    IMPLEMENT_REFCOUNTING(LoadHandler);
    DISALLOW_COPY_AND_ASSIGN(LoadHandler);
};

class BrowserClient final : public CefClient
{
public:
    BrowserClient(const char *odenJs)
        : renderHandler(new RenderHandler()), displayHandler(new DisplayHandler()), loadHandler(new LoadHandler(odenJs)) {}

    virtual ~BrowserClient() override {}

    virtual CefRefPtr<CefRenderHandler> GetRenderHandler() override { return renderHandler; }
    virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return displayHandler; }
    virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override { return loadHandler; }

    CefRefPtr<RenderHandler> renderHandler;
    CefRefPtr<DisplayHandler> displayHandler;
    CefRefPtr<LoadHandler> loadHandler;

    IMPLEMENT_REFCOUNTING(BrowserClient);
    DISALLOW_COPY_AND_ASSIGN(BrowserClient);
};

static cef_mouse_event_t mouse_event(MouseState mouseState, bool buttons[3])
{
    cef_mouse_event_t mouseEvent = {};
    mouseEvent.x = mouseState.x;
    mouseEvent.y = mouseState.y;
    if (buttons[0])
    {
        mouseEvent.modifiers = EVENTFLAG_LEFT_MOUSE_BUTTON;
    }
    if (buttons[1])
    {
        mouseEvent.modifiers = EVENTFLAG_RIGHT_MOUSE_BUTTON;
    }
    if (buttons[2])
    {
        mouseEvent.modifiers = EVENTFLAG_MIDDLE_MOUSE_BUTTON;
    }
    return mouseEvent;
}

static int32_t translate_vk(int32_t key)
{
    switch (key)
    {
    case PLUGIN_KEY_SPACE:
        return 0x20; // VK_SPACE;
    case PLUGIN_KEY_APOSTROPHE:
        return 0xDE; // VK_OEM_7;
    case PLUGIN_KEY_COMMA:
        return 0xBC; // VK_OEM_COMMA;
    case PLUGIN_KEY_MINUS:
        return 0xBD; // VK_OEM_MINUS;
    case PLUGIN_KEY_PERIOD:
        return 0xBE; // VK_OEM_PERIOD;
    case PLUGIN_KEY_SLASH:
        return 0xBF; // VK_OEM_2;
    case PLUGIN_KEY_0:
        return '0';
    case PLUGIN_KEY_1:
        return '1';
    case PLUGIN_KEY_2:
        return '2';
    case PLUGIN_KEY_3:
        return '3';
    case PLUGIN_KEY_4:
        return '4';
    case PLUGIN_KEY_5:
        return '5';
    case PLUGIN_KEY_6:
        return '6';
    case PLUGIN_KEY_7:
        return '7';
    case PLUGIN_KEY_8:
        return '8';
    case PLUGIN_KEY_9:
        return '9';
    case PLUGIN_KEY_SEMICOLON:
        return 0xE2; // VK_OEM_1;
    case PLUGIN_KEY_EQUAL:
        return 0xBB; // VK_OEM_PLUS;
    case PLUGIN_KEY_A:
        return 'A';
    case PLUGIN_KEY_B:
        return 'B';
    case PLUGIN_KEY_C:
        return 'C';
    case PLUGIN_KEY_D:
        return 'D';
    case PLUGIN_KEY_E:
        return 'E';
    case PLUGIN_KEY_F:
        return 'F';
    case PLUGIN_KEY_G:
        return 'G';
    case PLUGIN_KEY_H:
        return 'H';
    case PLUGIN_KEY_I:
        return 'I';
    case PLUGIN_KEY_J:
        return 'J';
    case PLUGIN_KEY_K:
        return 'K';
    case PLUGIN_KEY_L:
        return 'L';
    case PLUGIN_KEY_M:
        return 'M';
    case PLUGIN_KEY_N:
        return 'N';
    case PLUGIN_KEY_O:
        return 'O';
    case PLUGIN_KEY_P:
        return 'P';
    case PLUGIN_KEY_Q:
        return 'Q';
    case PLUGIN_KEY_R:
        return 'R';
    case PLUGIN_KEY_S:
        return 'S';
    case PLUGIN_KEY_T:
        return 'T';
    case PLUGIN_KEY_U:
        return 'U';
    case PLUGIN_KEY_V:
        return 'V';
    case PLUGIN_KEY_W:
        return 'W';
    case PLUGIN_KEY_X:
        return 'X';
    case PLUGIN_KEY_Y:
        return 'Y';
    case PLUGIN_KEY_Z:
        return 'Z';
    case PLUGIN_KEY_LEFT_BRACKET:
        return 0xDB; // VK_OEM_4;
    case PLUGIN_KEY_BACKSLASH:
        return 0xDC; // VK_OEM_5;
    case PLUGIN_KEY_RIGHT_BRACKET:
        return 0xDD; // VK_OEM_6;
    case PLUGIN_KEY_GRAVE_ACCENT:
        return 0xC0; // VK_OEM_3;
    case PLUGIN_KEY_WORLD_1:
        return 0xDF; // VK_OEM_8;
    case PLUGIN_KEY_ESCAPE:
        return 0x1B; // VK_ESCAPE;
    case PLUGIN_KEY_ENTER:
        return 0x0D; // VK_RETURN;
    case PLUGIN_KEY_TAB:
        return 0x09; // VK_TAB;
    case PLUGIN_KEY_BACKSPACE:
        return 0x08; // VK_BACK;
    case PLUGIN_KEY_INSERT:
        return 0x2D; // VK_INSERT;
    case PLUGIN_KEY_DELETE:
        return 0x2E; // VK_DELETE;
    case PLUGIN_KEY_RIGHT:
        return 0x27; // VK_RIGHT;
    case PLUGIN_KEY_LEFT:
        return 0x25; // VK_LEFT;
    case PLUGIN_KEY_DOWN:
        return 0x28; // VK_DOWN;
    case PLUGIN_KEY_UP:
        return 0x26; // VK_UP;
    case PLUGIN_KEY_PAGE_UP:
        return 0x21; // VK_PRIOR;
    case PLUGIN_KEY_PAGE_DOWN:
        return 0x22; // VK_NEXT;
    case PLUGIN_KEY_HOME:
        return 0x24; // VK_HOME;
    case PLUGIN_KEY_END:
        return 0x23; // VK_END;
    case PLUGIN_KEY_CAPS_LOCK:
        return 0x14; // VK_CAPITAL;
    case PLUGIN_KEY_SCROLL_LOCK:
        return 0x91; // VK_SCROLL;
    case PLUGIN_KEY_NUM_LOCK:
        return 0x90; // VK_NUMLOCK;
    case PLUGIN_KEY_PRINT_SCREEN:
        return 0x2C; // VK_SNAPSHOT;
    case PLUGIN_KEY_PAUSE:
        return 0x13; // VK_PAUSE;
    case PLUGIN_KEY_F1:
        return 0x70; // VK_F1;
    case PLUGIN_KEY_F2:
        return 0x71; // VK_F2;
    case PLUGIN_KEY_F3:
        return 0x72; // VK_F3;
    case PLUGIN_KEY_F4:
        return 0x73; // VK_F4;
    case PLUGIN_KEY_F5:
        return 0x74; // VK_F5;
    case PLUGIN_KEY_F6:
        return 0x75; // VK_F6;
    case PLUGIN_KEY_F7:
        return 0x76; // VK_F7;
    case PLUGIN_KEY_F8:
        return 0x77; // VK_F8;
    case PLUGIN_KEY_F9:
        return 0x78; // VK_F9;
    case PLUGIN_KEY_F10:
        return 0x79; // VK_F10;
    case PLUGIN_KEY_F11:
        return 0x7a; // VK_F11;
    case PLUGIN_KEY_F12:
        return 0x7b; // VK_F12;
    case PLUGIN_KEY_F13:
        return 0x7c; // VK_F13;
    case PLUGIN_KEY_F14:
        return 0x7d; // VK_F14;
    case PLUGIN_KEY_F15:
        return 0x7e; // VK_F15;
    case PLUGIN_KEY_F16:
        return 0x7f; // VK_F16;
    case PLUGIN_KEY_F17:
        return 0x80; // VK_F17;
    case PLUGIN_KEY_F18:
        return 0x81; // VK_F18;
    case PLUGIN_KEY_F19:
        return 0x82; // VK_F19;
    case PLUGIN_KEY_F20:
        return 0x83; // VK_F20;
    case PLUGIN_KEY_F21:
        return 0x84; // VK_F21;
    case PLUGIN_KEY_F22:
        return 0x85; // VK_F22;
    case PLUGIN_KEY_F23:
        return 0x86; // VK_F23;
    case PLUGIN_KEY_F24:
        return 0x87; // VK_F24;
    case PLUGIN_KEY_KP_0:
        return 0x60; // VK_NUMPAD0;
    case PLUGIN_KEY_KP_1:
        return 0x61; // VK_NUMPAD1;
    case PLUGIN_KEY_KP_2:
        return 0x62; // VK_NUMPAD2;
    case PLUGIN_KEY_KP_3:
        return 0x63; // VK_NUMPAD3;
    case PLUGIN_KEY_KP_4:
        return 0x64; // VK_NUMPAD4;
    case PLUGIN_KEY_KP_5:
        return 0x65; // VK_NUMPAD5;
    case PLUGIN_KEY_KP_6:
        return 0x66; // VK_NUMPAD6;
    case PLUGIN_KEY_KP_7:
        return 0x67; // VK_NUMPAD7;
    case PLUGIN_KEY_KP_8:
        return 0x68; // VK_NUMPAD8;
    case PLUGIN_KEY_KP_9:
        return 0x69; // VK_NUMPAD9;
    case PLUGIN_KEY_KP_DECIMAL:
        return 0x6E; // VK_DECIMAL;
    case PLUGIN_KEY_KP_DIVIDE:
        return 0x6F; // VK_DIVIDE;
    case PLUGIN_KEY_KP_MULTIPLY:
        return 0x6A; // VK_MULTIPLY;
    case PLUGIN_KEY_KP_SUBTRACT:
        return 0x6D; // VK_SUBTRACT;
    case PLUGIN_KEY_KP_ADD:
        return 0x6B; // VK_ADD;
    case PLUGIN_KEY_KP_ENTER:
        return 0x0D; // VK_RETURN;
    case PLUGIN_KEY_KP_EQUAL:
        return 0xBB; // VK_OEM_PLUS;
    case PLUGIN_KEY_LEFT_SHIFT:
        return 0xA0; // VK_LSHIFT;
    case PLUGIN_KEY_LEFT_CONTROL:
        return 0xA2; // VK_LCONTROL;
    case PLUGIN_KEY_LEFT_ALT:
        return 0x12; // VK_MENU;
    case PLUGIN_KEY_LEFT_SUPER:
        return 0x5B; // VK_LWIN;
    case PLUGIN_KEY_RIGHT_SHIFT:
        return 0xA1; // VK_RSHIFT;
    case PLUGIN_KEY_RIGHT_CONTROL:
        return 0xA3; // VK_RCONTROL;
    case PLUGIN_KEY_RIGHT_ALT:
        return 0x12; // VK_MENU;
    case PLUGIN_KEY_RIGHT_SUPER:
        return 0x5C; // VK_RWIN;
    case PLUGIN_KEY_MENU:
        return 0x5D; // VK_APPS;
    }

    return -1;
}

static uint32_t translate_modifiers(int32_t mods)
{
    uint32_t res = {};

    if (mods & 0x0001)
    {
        res |= EVENTFLAG_SHIFT_DOWN;
    }

    if (mods & 0x0002)
    {
        res |= EVENTFLAG_CONTROL_DOWN;
    }

    if (mods & 0x0004)
    {
        res |= EVENTFLAG_ALT_DOWN;
    }

    if (mods & 0x0008)
    {
        res |= EVENTFLAG_COMMAND_DOWN;
    }

    if (mods & 0x0010)
    {
        res |= EVENTFLAG_CAPS_LOCK_ON;
    }

    if (mods & 0x0020)
    {
        res |= EVENTFLAG_NUM_LOCK_ON;
    }

    return res;
}

std::string replace_all(const std::string &in, const std::string &from, const std::string &to)
{
    std::string str = in;

    size_t start_pos = 0;

    while ((start_pos = str.find(from, start_pos)) != std::string::npos)
    {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length();
    }

    return str;
}

#ifdef _WIN32
int APIENTRY WinMain(HINSTANCE, HINSTANCE, PSTR, int)
#else
int main(int argc, char *const *const argv)
#endif
{
#ifdef _WIN32
    CefMainArgs main_args(GetModuleHandle(nullptr));
#else
    CefMainArgs main_args(argc, const_cast<char **>(argv));
#endif

    CefRefPtr<CefCommandLine> command_line = create_command_line(main_args);

    CefRefPtr<CefApp> app = CefRefPtr<CefApp>(new App());

    uint16_t ipc_port = 0;
    uint16_t ws_port = 0;

#ifdef _WIN32
    LPWSTR *argv = nullptr;
    int argc = 0;

    argv = CommandLineToArgvW(GetCommandLineW(), &argc);

    if (argc == 4 && wcscmp(argv[1], L"ODEN_CEF_CHILD_PROCESS") == 0)
    {
        ipc_port = static_cast<uint16_t>(_wtoi(argv[2]));
        ws_port = static_cast<uint16_t>(_wtoi(argv[3]));
    }

    LocalFree(argv);
#else
    if (argc == 4 && strcmp(argv[1], "ODEN_CEF_CHILD_PROCESS") == 0)
    {
        ipc_port = static_cast<uint16_t>(atoi(argv[2]));
        ws_port = static_cast<uint16_t>(atoi(argv[3]));
    }
#endif

    int32_t exitCode = CefExecuteProcess(main_args, app, nullptr);

    if (exitCode >= 0)
    {
        return exitCode;
    }

    CefSettings settings;
    settings.remote_debugging_port = 9222;

    CefString(&settings.cache_path) = oden_cef_temp_path();
    CefString(&settings.root_cache_path) = oden_cef_temp_path();

    if (!CefInitialize(main_args, settings, app.get(), nullptr))
    {
        fprintf(stderr, "Failed to initialize cef");
        return 0;
    }

    if (ipc_port == 0 && ws_port == 0)
    {
        return 0;
    }

    oden_cef_start_ipc(ipc_port);

    std::string oden_js_api = replace_all(oden_cef_js_api(), "%WS_PORT%", std::to_string(ws_port));

    CefRefPtr<BrowserClient> client = new BrowserClient(oden_js_api.c_str());

    CefWindowInfo windowInfo;
#ifdef _WIN32
    windowInfo.SetAsWindowless(nullptr);
#else
    windowInfo.SetAsWindowless(0);
#endif

    char addressBuffer[4096] = {};
    oden_cef_get_address(reinterpret_cast<uint8_t *>(&addressBuffer[0]), sizeof(addressBuffer));

    CefBrowserSettings browserSettings;
    browserSettings.windowless_frame_rate = 60;

    CefRefPtr<CefBrowser> browser = CefBrowserHost::CreateBrowserSync(
        windowInfo, client.get(), addressBuffer, browserSettings, nullptr, nullptr);

    bool mouseButtons[3] = {};
    MouseState mouseState = {};
    int32_t lastClickX = 0;
    int32_t lastClickY = 0;
    int32_t lastClickButton = 0;
    int32_t lastClickCount = 1;
    int64_t lastClickTime = 0;
    bool cancelPreviousClick = false;
    int64_t currentTime = 0;

    while (oden_cef_should_run())
    {
        currentTime = oden_cef_time();

        CefDoMessageLoopWork();

        browser->GetHost()->SetFocus(true);

        if (oden_cef_should_refresh())
        {
            browser->Reload();
        }

        if (oden_cef_should_navigate_back())
        {
            browser->GoBack();
        }

        if (oden_cef_should_navigate_forward())
        {
            browser->GoForward();
        }

        if (oden_cef_resolution_changed())
        {
            browser->GetHost()->WasResized();
        }

        char newAddressBuffer[4096] = {};
        if (oden_cef_get_address(
                reinterpret_cast<uint8_t *>(&newAddressBuffer[0]), sizeof(newAddressBuffer)))
        {
            if (strcmp(newAddressBuffer, addressBuffer) != 0 && strlen(newAddressBuffer) > 0)
            {
                memcpy(addressBuffer, newAddressBuffer, sizeof(addressBuffer));
                browser->GetMainFrame()->LoadURL(addressBuffer);
            }
        }

        while (oden_cef_get_mouse_state(&mouseState))
        {
            browser->GetHost()->SendMouseMoveEvent(mouse_event(mouseState, mouseButtons), false);
        }

#ifdef _WIN32
        cancelPreviousClick = (abs(lastClickX - mouseState.x) > (GetSystemMetrics(SM_CXDOUBLECLK) / 2)) ||
                              (abs(lastClickY - mouseState.y) > (GetSystemMetrics(SM_CYDOUBLECLK) / 2)) ||
                              ((currentTime - lastClickTime) / 1'000'000 > GetDoubleClickTime());
#else
        cancelPreviousClick = (abs(lastClickX - mouseState.x) > 5) ||
                              (abs(lastClickY - mouseState.y) > 5) ||
                              ((currentTime - lastClickTime) / 1'000'000 > 250);
#endif

        if (cancelPreviousClick)
        {
            lastClickCount = 1;
            lastClickX = 0;
            lastClickY = 0;
            lastClickTime = 0;
        }

        OdenWindowEvent events[4096] = {};
        uint32_t buffer_count = ARRAY_COUNT(events);
        while (oden_cef_get_events(&events[0], &buffer_count))
        {
            for (int32_t i = 0; i < static_cast<int32_t>(buffer_count); ++i)
            {
                const OdenWindowEvent &event = events[i];

                switch (event.type)
                {
                case OdenEventTypeKey:
                {
                    {
                        cef_key_event_t keyEvent = {};
                        keyEvent.windows_key_code = translate_vk(event.k.key);
                        keyEvent.native_key_code = event.k.scancode << 16;
                        keyEvent.modifiers = translate_modifiers(event.k.mods);

                        switch (event.k.actions)
                        {
                        case PLUGIN_KEY_ACTION_RELEASE:
                        {
                            keyEvent.type = KEYEVENT_KEYUP;
                            break;
                        }
                        case PLUGIN_KEY_ACTION_PRESS:
                        case PLUGIN_KEY_ACTION_REPEAT:
                        {
                            keyEvent.type = KEYEVENT_RAWKEYDOWN;
                            break;
                        }
                        default:
                        {
                            break;
                        }
                        }

                        if (event.k.actions == PLUGIN_KEY_ACTION_REPEAT)
                        {
                            keyEvent.modifiers |= EVENTFLAG_IS_REPEAT;
                        }

                        browser->GetHost()->SendKeyEvent(keyEvent);
                    }

#ifdef _WIN32
                    if ((event.k.key == PLUGIN_KEY_ENTER || event.k.key == PLUGIN_KEY_KP_ENTER) &&
                        (event.k.actions == PLUGIN_KEY_ACTION_PRESS ||
                         event.k.actions == PLUGIN_KEY_ACTION_REPEAT))
                    {
                        cef_key_event_t keyEvent = {};
                        keyEvent.windows_key_code = VK_RETURN;
                        keyEvent.type = KEYEVENT_CHAR;

                        browser->GetHost()->SendKeyEvent(keyEvent);
                    }
#endif

                    break;
                }
                case OdenEventTypeChar:
                {

                    cef_key_event_t keyEvent = {};
                    keyEvent.windows_key_code = event.c.c;
                    keyEvent.character = static_cast<char16>(event.c.c);
                    keyEvent.type = KEYEVENT_CHAR;

                    browser->GetHost()->SendKeyEvent(keyEvent);

                    break;
                }
                case OdenEventTypeScroll:
                {
                    browser->GetHost()->SendMouseWheelEvent(
                        mouse_event(mouseState, mouseButtons),
                        static_cast<int32_t>(event.s.x * 100.0),
                        static_cast<int32_t>(event.s.y * 100.0));

                    break;
                }
                case OdenEventTypeMouseButton:
                {
                    // Right and middle click makes no sens
                    if (event.mb.key == 0)
                    {
                        mouseButtons[event.mb.key] = event.mb.actions;

                        cef_mouse_button_type_t button = MBT_LEFT;

                        switch (event.mb.key)
                        {
                        case PLUGIN_MOUSE_BUTTON_LEFT:
                        {
                            button = MBT_LEFT;
                            break;
                        }
                        case PLUGIN_MOUSE_BUTTON_RIGHT:
                        {
                            button = MBT_RIGHT;
                            break;
                        }
                        case PLUGIN_MOUSE_BUTTON_MIDDLE:
                        {
                            button = MBT_MIDDLE;
                            break;
                        }
                        }

                        browser->GetHost()->SendMouseClickEvent(
                            mouse_event(mouseState, mouseButtons), button, !event.mb.actions, std::min(lastClickCount, 2));

                        if (!cancelPreviousClick && event.mb.key == lastClickButton)
                        {
                            lastClickCount++;
                        }
                        else
                        {
                            lastClickCount = 1;
                            lastClickX = mouseState.x;
                            lastClickY = mouseState.y;
                        }

                        lastClickButton = event.mb.key;
                        lastClickTime = currentTime;
                    }

                    if (event.mb.key == 3 && event.mb.actions == PLUGIN_KEY_ACTION_PRESS)
                    {
                        browser->GoBack();
                    }

                    if (event.mb.key == 4 && event.mb.actions == PLUGIN_KEY_ACTION_PRESS)
                    {
                        browser->GoForward();
                    }

                    break;
                }
                default:
                {
                    break;
                }
                }
            }

            buffer_count = ARRAY_COUNT(events);
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }

    browser->GetHost()->CloseBrowser(true);
    browser = nullptr;

    CefShutdown();

    return 0;
}