#ifdef USE_GLFW
    #if USE_GLFW == 2
        #include <GL/glfw.h>
    #else
        #include <GLFW/glfw3.h>
    #endif
    #include <stdio.h>
    #include <emscripten.h>

    #define MULTILINE(...) #__VA_ARGS__
    #define WIDTH 640
    #define HEIGHT 480

    // Setup tests
    typedef struct {
        int mouse;
        double x, y;
        int button;
        int action;
        int modify;
        int character;
    } test_args_t;

    typedef struct {
        char cmd[80];
        test_args_t args;
    } test_t;

    // Javascript event.button 0 = left, 1 = middle, 2 = right
    test_t g_tests[] = {
        { "Module.injectMouseEvent(10.0, 10.0, 'mousedown', 0)", { 1, 10.0, 10.0, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, -1 } },
        { "Module.injectMouseEvent(10.0, 20.0, 'mouseup', 0)", { 1, 10.0, 20.0, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, -1 } },
        { "Module.injectMouseEvent(10.0, 30.0, 'mousedown', 1)", { 1, 10.0, 30.0, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, -1 } },
        { "Module.injectMouseEvent(10.0, 40.0, 'mouseup', 1)", { 1, 10.0, 40.0, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_RELEASE, -1 } },
        { "Module.injectMouseEvent(10.0, 30.0, 'mousedown', 2)", { 1, 10.0, 30.0, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, -1 } },
        { "Module.injectMouseEvent(10.0, 40.0, 'mouseup', 2)", { 1, 10.0, 40.0, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, -1 } },
        //{ "Module.injectMouseEvent(10.0, 50.0, 'mousewheel', 0)", { 10.0, 50.0, -1, -1, -1 } },
        //{ "Module.injectMouseEvent(10.0, 60.0, 'mousemove', 0)", { 10.0, 60.0, -1, -1, -1 } }

        { "Module.injectKeyEvent('keydown', 8)", { 0, 0.0, 0.0, GLFW_KEY_BACKSPACE, GLFW_PRESS, -1 } },
        { "Module.injectKeyEvent('keyup', 8)", { 0, 0.0, 0.0, GLFW_KEY_BACKSPACE, GLFW_RELEASE, -1 } },
        { "Module.injectKeyEvent('keydown', 9)", { 0, 0.0, 0.0, GLFW_KEY_TAB, GLFW_PRESS, -1 } },
        { "Module.injectKeyEvent('keyup', 9)", { 0, 0.0, 0.0, GLFW_KEY_TAB, GLFW_RELEASE, -1 } },
        { "Module.injectKeyEvent('keydown', 112)", { 0, 0.0, 0.0, GLFW_KEY_F1, GLFW_PRESS, -1 } },
        { "Module.injectKeyEvent('keyup', 112)", { 0, 0.0, 0.0, GLFW_KEY_F1, GLFW_RELEASE, -1 } },
        { "Module.injectKeyEvent('keydown', 37)", { 0, 0.0, 0.0, GLFW_KEY_LEFT, GLFW_PRESS, -1 } },
        { "Module.injectKeyEvent('keyup', 37)", { 0, 0.0, 0.0, GLFW_KEY_LEFT, GLFW_RELEASE, -1 } },
        { "Module.injectKeyEvent('keydown', 39)", { 0, 0.0, 0.0, GLFW_KEY_RIGHT, GLFW_PRESS, -1 } },
        { "Module.injectKeyEvent('keyup', 39)", { 0, 0.0, 0.0, GLFW_KEY_RIGHT, GLFW_RELEASE, -1 } },
        { "Module.injectKeyEvent('keydown', 38)", { 0, 0.0, 0.0, GLFW_KEY_UP, GLFW_PRESS, -1 } },
        { "Module.injectKeyEvent('keyup', 38)", { 0, 0.0, 0.0, GLFW_KEY_UP, GLFW_RELEASE, -1 } },
        { "Module.injectKeyEvent('keydown', 40)", { 0, 0.0, 0.0, GLFW_KEY_DOWN, GLFW_PRESS, -1 } },
        { "Module.injectKeyEvent('keyup', 40)", { 0, 0.0, 0.0, GLFW_KEY_DOWN, GLFW_RELEASE, -1 } },

        #if USE_GLFW == 2
            { "Module.injectKeyEvent('keydown', 27)", { 0, 0.0, 0.0, GLFW_KEY_ESC, GLFW_PRESS, -1 } },
            { "Module.injectKeyEvent('keyup', 27)", { 0, 0.0, 0.0, GLFW_KEY_ESC, GLFW_RELEASE, -1 } },

            { "Module.injectKeyEvent('keydown', 65)", { 0, 0.0, 0.0, 'A', GLFW_PRESS, -1, 'A' } },
            { "Module.injectKeyEvent('keypress', 65, {charCode: 65})", { 0, 0.0, 0.0, -1, -1, -1, 'A' } },
            { "Module.injectKeyEvent('keyup', 65)", { 0, 0.0, 0.0, 'A', GLFW_RELEASE, -1, 'A' } },

            { "Module.injectKeyEvent('keydown', 65, {ctrlKey: true})", { 0, 0.0, 0.0, 'A', GLFW_PRESS, -1, 'A' } },
            { "Module.injectKeyEvent('keypress', 65, {ctrlKey: true, charCode: 65})", { 0, 0.0, 0.0, -1, -1, -1, -1 } },
            { "Module.injectKeyEvent('keyup', 65, {ctrlKey: true})", { 0, 0.0, 0.0, 'A', GLFW_RELEASE, -1, 'A' } },
        #else
            { "Module.injectKeyEvent('keydown', 27)", { 0, 0.0, 0.0, GLFW_KEY_ESCAPE, GLFW_PRESS, -1 } },
            { "Module.injectKeyEvent('keyup', 27)", { 0, 0.0, 0.0, GLFW_KEY_ESCAPE, GLFW_RELEASE, -1 } },

            { "Module.injectKeyEvent('keydown', 65)", { 0, 0.0, 0.0, GLFW_KEY_A, GLFW_PRESS, -1 } },
            { "Module.injectKeyEvent('keypress', 65, {charCode: 65})", { 0, 0.0, 0.0, -1, -1, -1, 'A' } },
            { "Module.injectKeyEvent('keyup', 65)", { 0, 0.0, 0.0, GLFW_KEY_A, GLFW_RELEASE, -1 } },

            { "Module.injectKeyEvent('keydown', 65, {ctrlKey: true})", { 0, 0.0, 0.0, GLFW_KEY_A, GLFW_PRESS, -1, 'A' } },
            { "Module.injectKeyEvent('keypress', 65, {ctrlKey: true, charCode: 65})", { 0, 0.0, 0.0, -1, -1, -1, -1 } },
            { "Module.injectKeyEvent('keyup', 65, {ctrlKey: true})", { 0, 0.0, 0.0, GLFW_KEY_A, GLFW_RELEASE, -1, 'A' } },
        #endif
    };

    static unsigned int g_test_actual = 0;
    static unsigned int g_test_count = sizeof(g_tests) / sizeof(test_t);
    static unsigned int g_state = 0;

    #if USE_GLFW == 2
        static void on_mouse_button_callback(int button, int action)
    #else
        static void on_mouse_button_callback(GLFWwindow* window, int button, int action, int modify)
    #endif
    {
        test_args_t args = g_tests[g_test_actual].args;
        if (args.button == button && args.action == action)
        {
            g_state |= 1 << g_test_actual;
        }
        else
        {
            printf("Test %d: FAIL\n", g_test_actual);
        }
    }

    #if USE_GLFW == 2
        static void on_mouse_move(int x, int y)
    #else
        static void on_mouse_move(GLFWwindow* window, double x, double y)
    #endif
    {
        test_args_t args = g_tests[g_test_actual].args;
        if (args.x == x && args.y == y)
        {
            g_state |= 1 << g_test_actual;
        }
        else
        {
            printf("Test %d: FAIL\n", g_test_actual);
        }
    }

    #if USE_GLFW == 2
        static void on_key_callback(int key, int action)
    #else
        static void on_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
    #endif
    {
        test_args_t args = g_tests[g_test_actual].args;
        if (args.button == key && args.action == action)
        {
            g_state |= 1 << g_test_actual;
        }
        else
        {
            printf("Test %d: FAIL\n", g_test_actual);
        }
    }

    #if USE_GLFW == 2
        static void on_char_callback(int character, int action)
    #else
        static void on_char_callback(GLFWwindow* window, unsigned int character)
    #endif
    {
        test_args_t args = g_tests[g_test_actual].args;
        if (args.character != -1 && args.character == character)
        {
            g_state |= 1 << g_test_actual;
        }
        else
        {
            printf("Test %d: FAIL\n", g_test_actual);
        }

    }

    #if USE_GLFW == 3
        static void on_mouse_wheel(GLFWwindow* window, double x, double y)
        {
            test_args_t args = g_tests[g_test_actual].args;
            if (args.x == x && args.y == y)
            {
                g_state |= 1 << g_test_actual;
            }
            else
            {
                printf("Test %d: FAIL\n", g_test_actual);
            }
        }

        static void on_error(int error, const char *msg)
        {
            printf("%d: %s\n", error, msg);
        }
    #endif

    int main()
    {
        int result = 1;
        unsigned int success = (1 << (sizeof(g_tests) / sizeof(test_t))) - 1; // (2^count)-1;

        emscripten_run_script(MULTILINE(
            Module.injectMouseEvent = function(x, y, event_, button) {
                var canvas = Module['canvas'];
                var event = new MouseEvent(event_, {
                    'view': window,
                    'bubbles': true,
                    'cancelable': true,
                    'screenX': canvas.offsetLeft + x,
                    'screenY': canvas.offsetTop + y,
                    'clientX': canvas.offsetLeft + x,
                    'clientY': canvas.offsetTop + y,
                    'button': button
                });
                canvas.dispatchEvent(event);

                //var event = document.createEvent("MouseEvents");
                //var canvas = Module['canvas'];
                //event.initMouseEvent(event_, true, true, window, 0, canvas.offsetLeft + x, canvas.offsetTop + y, canvas.offsetLeft + x, canvas.offsetTop + y, 0, 0, 0, 0, button, null);
                //canvas.dispatchEvent(event);
            };

            Module.injectKeyEvent = function(type, keyCode, options) {
                // KeyboardEvent constructor always returns 0 keyCode on Chrome, so use generic events
                //var keyboardEvent = new KeyboardEvent(type, Object.assign({ keyCode: keyCode}, options));
                var keyboardEvent = document.createEventObject ?
                        document.createEventObject() : document.createEvent('Events');
                keyboardEvent.initEvent(type, true, true);
                keyboardEvent.keyCode = keyCode;
                keyboardEvent = Object.assign(keyboardEvent,  options);

                canvas.dispatchEvent(keyboardEvent);
            };
        ));
        
        glfwInit();

        #if USE_GLFW == 2
            glfwOpenWindow(WIDTH, HEIGHT, 5, 6, 5, 0, 0, 0, GLFW_WINDOW); // != GL_TRUE)
            
            glfwSetMousePosCallback(on_mouse_move);
            glfwSetCharCallback(on_char_callback);
        #else
            glfwSetErrorCallback(on_error);
            printf("%s\n", glfwGetVersionString());

            GLFWwindow * _mainWindow = NULL;
            glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
            _mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "glfw3_events", NULL, NULL);
            glfwMakeContextCurrent(_mainWindow);

            glfwSetCursorPosCallback(_mainWindow, on_mouse_move);
            glfwSetScrollCallback(_mainWindow, on_mouse_wheel);
            glfwSetCharCallback(_mainWindow, on_char_callback);
        #endif

        for (int p = 0; p < 2 && result; ++p) // 2 passes, with and without callbacks.
        {
            printf("Running Test pass %d\n", p);

        #if USE_GLFW == 2
            glfwSetMouseButtonCallback(p == 0 ? NULL : on_mouse_button_callback);
            glfwSetKeyCallback(p == 0 ? NULL : on_key_callback);
        #else
            glfwSetMouseButtonCallback(_mainWindow, p == 0 ? NULL : on_mouse_button_callback);
            glfwSetKeyCallback(_mainWindow, p == 0 ? NULL : on_key_callback);
        #endif
            g_state = p == 0 ? success : 0;

            for (int i = 0; i < g_test_count; ++i)
            {
                g_test_actual = i;
                test_t test = g_tests[g_test_actual];

                if (test.args.character == -1) {
                     g_state |= 1 << g_test_actual;
                }

                emscripten_run_script(test.cmd);

                if (test.args.mouse) {
                #if USE_GLFW == 2
                    if (glfwGetMouseButton(test.args.button) != test.args.action)
                #else
                    if (glfwGetMouseButton(_mainWindow, test.args.button) != test.args.action)
                #endif
                    {
                        printf("Test %d: FAIL\n", g_test_actual);
                        g_state &= ~(1 << g_test_actual);
                    }
                } else {
                    // Keyboard.
                #if USE_GLFW == 2
                    if (test.args.action != -1 && glfwGetKey(test.args.button) != test.args.action)
                #else
                    if (test.args.action != -1 && glfwGetKey(_mainWindow, test.args.button) != test.args.action)
                #endif
                    {
                        printf("Test %d: FAIL\n", g_test_actual);
                        g_state &= ~(1 << g_test_actual);
                    }
                }
            }
            result = g_state == success;
        }

        glfwTerminate();

    #ifdef REPORT_RESULT
        REPORT_RESULT(result);
    #else
        printf("%d == %d = %d", g_state, success, g_state == success);
    #endif

        return 0;
    }
#endif