//
// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Copyright (c) 2023 BlackBerry Limited
//

// QNXWindow.cpp: Implementation of OSWindow for QNX

#include "qnx/QNXWindow.h"

#include <assert.h>
#include <pthread.h>
#include <sys/keycodes.h>

#include "aemu/base/system/System.h"

namespace {
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static screen_context_t g_screen_ctx;
static int FAKE_MOUSE_BUTTON_FOR_TRIGGER_TEST_EVENT = 5;

static void screen_init(void) {
    /* initialize the global screen context */
    screen_create_context(&g_screen_ctx, SCREEN_APPLICATION_CONTEXT);
}

static screen_context_t get_screen_context() {
    pthread_once(&once_control, screen_init);
    return g_screen_ctx;
}

Key QNXCodeToKey(unsigned keycode) {
    switch (keycode) {
        case KEYCODE_RETURN:
        case KEYCODE_KP_ENTER:
            return KEY_RETURN;
        case KEYCODE_BACKSPACE:
            return KEY_BACK;
        case KEYCODE_DELETE:
            return KEY_DELETE;
        case KEYCODE_ESCAPE:
            return KEY_ESCAPE;
        case KEYCODE_SPACE:
            return KEY_SPACE;
        case KEYCODE_ZERO:
        case KEYCODE_RIGHT_PAREN:
            return KEY_NUM0;
        case KEYCODE_ONE:
        case KEYCODE_EXCLAM:
            return KEY_NUM1;
        case KEYCODE_TWO:
        case KEYCODE_AT:
            return KEY_NUM2;
        case KEYCODE_THREE:
        case KEYCODE_NUMBER:
            return KEY_NUM3;
        case KEYCODE_FOUR:
        case KEYCODE_DOLLAR:
            return KEY_NUM4;
        case KEYCODE_FIVE:
        case KEYCODE_PERCENT:
            return KEY_NUM5;
        case KEYCODE_SIX:
        case KEYCODE_CIRCUMFLEX:
            return KEY_NUM6;
        case KEYCODE_SEVEN:
        case KEYCODE_AMPERSAND:
            return KEY_NUM7;
        case KEYCODE_EIGHT:
        case KEYCODE_ASTERISK:
            return KEY_NUM8;
        case KEYCODE_NINE:
        case KEYCODE_LEFT_PAREN:
            return KEY_NUM9;
        case KEYCODE_A:
        case KEYCODE_CAPITAL_A:
            return KEY_A;
        case KEYCODE_B:
        case KEYCODE_CAPITAL_B:
            return KEY_B;
        case KEYCODE_C:
        case KEYCODE_CAPITAL_C:
            return KEY_C;
        case KEYCODE_D:
        case KEYCODE_CAPITAL_D:
            return KEY_D;
        case KEYCODE_E:
        case KEYCODE_CAPITAL_E:
            return KEY_E;
        case KEYCODE_F:
        case KEYCODE_CAPITAL_F:
            return KEY_F;
        case KEYCODE_G:
        case KEYCODE_CAPITAL_G:
            return KEY_G;
        case KEYCODE_H:
        case KEYCODE_CAPITAL_H:
            return KEY_H;
        case KEYCODE_I:
        case KEYCODE_CAPITAL_I:
            return KEY_I;
        case KEYCODE_J:
        case KEYCODE_CAPITAL_J:
            return KEY_J;
        case KEYCODE_K:
        case KEYCODE_CAPITAL_K:
            return KEY_K;
        case KEYCODE_L:
        case KEYCODE_CAPITAL_L:
            return KEY_L;
        case KEYCODE_M:
        case KEYCODE_CAPITAL_M:
            return KEY_M;
        case KEYCODE_N:
        case KEYCODE_CAPITAL_N:
            return KEY_N;
        case KEYCODE_O:
        case KEYCODE_CAPITAL_O:
            return KEY_O;
        case KEYCODE_P:
        case KEYCODE_CAPITAL_P:
            return KEY_P;
        case KEYCODE_Q:
        case KEYCODE_CAPITAL_Q:
            return KEY_Q;
        case KEYCODE_R:
        case KEYCODE_CAPITAL_R:
            return KEY_R;
        case KEYCODE_S:
        case KEYCODE_CAPITAL_S:
            return KEY_S;
        case KEYCODE_T:
        case KEYCODE_CAPITAL_T:
            return KEY_T;
        case KEYCODE_U:
        case KEYCODE_CAPITAL_U:
            return KEY_U;
        case KEYCODE_V:
        case KEYCODE_CAPITAL_V:
            return KEY_V;
        case KEYCODE_W:
        case KEYCODE_CAPITAL_W:
            return KEY_W;
        case KEYCODE_X:
        case KEYCODE_CAPITAL_X:
            return KEY_X;
        case KEYCODE_Y:
        case KEYCODE_CAPITAL_Y:
            return KEY_Y;
        case KEYCODE_Z:
        case KEYCODE_CAPITAL_Z:
            return KEY_Z;
        case KEYCODE_PLUS:
        case KEYCODE_EQUAL:
            return KEY_EQUAL;
        case KEYCODE_MINUS:
        case KEYCODE_UNDERSCORE:
            return KEY_SUBTRACT;
        case KEYCODE_LESS_THAN:
        case KEYCODE_COMMA:
            return KEY_COMMA;
        case KEYCODE_GREATER_THAN:
        case KEYCODE_PERIOD:
            return KEY_PERIOD;
        case KEYCODE_COLON:
        case KEYCODE_SEMICOLON:
            return KEY_SEMICOLON;
        case KEYCODE_SLASH:
        case KEYCODE_QUESTION:
            return KEY_SLASH;
        case KEYCODE_TILDE:
        case KEYCODE_GRAVE:
            return KEY_TILDE;
        case KEYCODE_LEFT_BRACE:
        case KEYCODE_LEFT_BRACKET:
            return KEY_LBRACKET;
        case KEYCODE_BAR:
        case KEYCODE_BACK_SLASH:
            return KEY_BACKSLASH;
        case KEYCODE_RIGHT_BRACE:
        case KEYCODE_RIGHT_BRACKET:
            return KEY_RBRACKET;
        case KEYCODE_QUOTE:
        case KEYCODE_APOSTROPHE:
            return KEY_QUOTE;
        case KEYCODE_PAUSE:
            return KEY_PAUSE;
        case KEYCODE_TAB:
        case KEYCODE_BACK_TAB:
            return KEY_TAB;
        case KEYCODE_LEFT:
            return KEY_LEFT;
        case KEYCODE_KP_LEFT:
            return KEY_NUMPAD4;
        case KEYCODE_KP_FIVE:
            return KEY_NUMPAD5;
        case KEYCODE_RIGHT:
            return KEY_RIGHT;
        case KEYCODE_KP_RIGHT:
            return KEY_NUMPAD6;
        case KEYCODE_UP:
            return KEY_UP;
        case KEYCODE_KP_UP:
            return KEY_NUMPAD8;
        case KEYCODE_DOWN:
            return (Key)(KEY_UP + 1);  // avoid name collision
        case KEYCODE_KP_DOWN:
            return KEY_NUMPAD2;
        case KEYCODE_MENU:
        case KEYCODE_LEFT_ALT:
        case KEYCODE_RIGHT_ALT:
            return KEY_MENU;
        case KEYCODE_HOME:
            return KEY_HOME;
        case KEYCODE_END:
            return KEY_END;
        case KEYCODE_LEFT_SHIFT:
            return KEY_LSHIFT;
        case KEYCODE_RIGHT_SHIFT:
            return KEY_RSHIFT;
        case KEYCODE_LEFT_CTRL:
            return KEY_LCONTROL;
        case KEYCODE_RIGHT_CTRL:
            return KEY_RCONTROL;
        case KEYCODE_KP_PLUS:
            return KEY_ADD;
        case KEYCODE_KP_MINUS:
            return KEY_SUBTRACT;
        case KEYCODE_KP_MULTIPLY:
            return KEY_MULTIPLY;
        case KEYCODE_KP_DIVIDE:
            return KEY_DIVIDE;
        case KEYCODE_F1:
            return KEY_F1;
        case KEYCODE_F2:
            return KEY_F2;
        case KEYCODE_F3:
            return KEY_F3;
        case KEYCODE_F4:
            return KEY_F4;
        case KEYCODE_F5:
            return KEY_F5;
        case KEYCODE_F6:
            return KEY_F6;
        case KEYCODE_F7:
            return KEY_F7;
        case KEYCODE_F8:
            return KEY_F8;
        case KEYCODE_F9:
            return KEY_F9;
        case KEYCODE_F10:
            return KEY_F10;
        case KEYCODE_F11:
            return KEY_F11;
        case KEYCODE_F12:
            return KEY_F12;
        default:
            return KEY_UNKNOWN;
    }
    return KEY_UNKNOWN;
}

MouseButton QNXCodeToButton(int button) {
    MouseButton target_button = MOUSEBUTTON_UNKNOWN;
    switch (button) {
        case SCREEN_LEFT_MOUSE_BUTTON:
            target_button = MOUSEBUTTON_LEFT;
            break;
        case SCREEN_MIDDLE_MOUSE_BUTTON:
            target_button = MOUSEBUTTON_MIDDLE;
            break;
        case SCREEN_RIGHT_MOUSE_BUTTON:
            target_button = MOUSEBUTTON_RIGHT;
            break;
        default:
            break;
    }
    return target_button;
}
}  // namespace

QNXWindow::QNXWindow() : mWindow(0), mVisible(false), mPid(getpid()) {}

QNXWindow::~QNXWindow() { destroy(); }

bool QNXWindow::initialize(const std::string& name, size_t width, size_t height) {
    screen_context_t screen_ctx;
    screen_window_t screen_window;
    int rc;

    screen_ctx = get_screen_context();
    if (screen_ctx == nullptr) {
        perror("No screen context");
        return false;
    }

    rc = screen_create_window_type(&screen_window, screen_ctx, SCREEN_APPLICATION_WINDOW);
    if (rc) {
        perror("screen_create_window");
        return false;
    }

    int alpha_mode = SCREEN_PRE_MULTIPLIED_ALPHA;
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_ALPHA_MODE, &alpha_mode);

    int usage = SCREEN_USAGE_NATIVE | SCREEN_USAGE_OPENGL_ES2;
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_USAGE, &usage);

    int interval = 1;
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_SWAP_INTERVAL, &interval);

    int format = SCREEN_FORMAT_RGBA8888;
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_FORMAT, &format);

    int transp = SCREEN_TRANSPARENCY_NONE;
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_TRANSPARENCY, &transp);

    int pos[2] = {0, 0};
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_POSITION, pos);

    int size[2] = {static_cast<int>(width), static_cast<int>(height)};
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_SIZE, size);

    size[0] = width;
    size[1] = height;
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_BUFFER_SIZE, size);

    rc = screen_create_window_buffers(screen_window, 2);
    if (rc) {
        perror("screen_create_window_buffers");
        screen_destroy_window(screen_window);
        return false;
    }

    char group_name[] = "gfx";
    rc = screen_create_window_group(screen_window, group_name);
    if (rc) {
        perror("screen_create_window_group");
        screen_destroy_window(screen_window);
        return false;
    }

    mWindow = screen_window;

    int sensitivity = SCREEN_SENSITIVITY_TEST;
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_SENSITIVITY, &sensitivity);

    screen_set_window_property_cv(screen_window, SCREEN_PROPERTY_ID_STRING, name.size(),
                                  name.c_str());

    int visible = mVisible ? 1 : 0;
    screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_VISIBLE, &visible);

    rc = screen_flush_context(screen_ctx, SCREEN_WAIT_IDLE);
    if (rc) {
        perror("flush");
    }

    return true;
}

void QNXWindow::destroy() {
    if (mWindow) screen_destroy_window(mWindow);
}

EGLNativeWindowType QNXWindow::getNativeWindow() const { return mWindow; }

EGLNativeDisplayType QNXWindow::getNativeDisplay() const {
    // TODO: yodai
    return 0;
}

void* QNXWindow::getFramebufferNativeWindow() const { return mWindow; }

void QNXWindow::messageLoop() {
    screen_event_t screen_event;
    int rc = screen_create_event(&screen_event);
    if (rc) {
        perror("screen_create_event");
        return;
    }

    while (!screen_get_event(get_screen_context(), screen_event, 0)) {
        int event_type;
        rc = screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &event_type);
        if (rc || event_type == SCREEN_EVENT_NONE) {
            break;
        }

        switch (event_type) {
            case SCREEN_EVENT_KEYBOARD:
                processKeyEvent(screen_event);
                break;
            case SCREEN_EVENT_POINTER:
                processMouseEvent(screen_event);
                break;
            case SCREEN_EVENT_PROPERTY:
                processPropertyChangedEvent(screen_event);
                break;
            case SCREEN_EVENT_INPUT_CONTROL:
                processInputControlEvent(screen_event);
                break;
            case SCREEN_EVENT_CLOSE:
                processCloseEvent(screen_event);
                break;
            default:
                break;
        }
    }
    screen_destroy_event(screen_event);
}

void QNXWindow::setMousePosition(int x, int y) {
    screen_event_t screen_event;
    if (screen_create_event(&screen_event)) return;

    int param = SCREEN_EVENT_POINTER;
    if (screen_set_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &param)) return;

    if (screen_set_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void**)&mWindow))
        return;
    int coords[] = {x, y};
    screen_set_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, coords);
    screen_send_event(get_screen_context(), screen_event, mPid);
}

OSWindow* CreateOSWindow() { return new QNXWindow(); }

bool QNXWindow::setPosition(int x, int y) {
    int pos[2] = {x, y};
    screen_set_window_property_iv(mWindow, SCREEN_PROPERTY_POSITION, pos);
    screen_context_t screen_ctx = get_screen_context();
    screen_flush_context(screen_ctx, 0);
    return true;
}

bool QNXWindow::resize(int width, int height) {
    int size[2] = {width, height};
    screen_set_window_property_iv(mWindow, SCREEN_PROPERTY_SIZE, size);
    size[0] = width;
    size[1] = height;
    screen_set_window_property_iv(mWindow, SCREEN_PROPERTY_BUFFER_SIZE, size);
    return true;
}

void QNXWindow::setVisible(bool isVisible) {
    if (mVisible == isVisible) {
        return;
    }
    int visible = isVisible ? 1 : 0;
    if (!screen_set_window_property_iv(mWindow, SCREEN_PROPERTY_VISIBLE, &visible))
        mVisible = isVisible;
}

void QNXWindow::signalTestEvent() {
    screen_event_t screen_event;
    if (screen_create_event(&screen_event)) return;

    int param = SCREEN_EVENT_POINTER;
    if (screen_set_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &param)) return;

    if (screen_set_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void**)&mWindow))
        return;
    int button = FAKE_MOUSE_BUTTON_FOR_TRIGGER_TEST_EVENT;
    screen_set_event_property_iv(screen_event, SCREEN_PROPERTY_BUTTONS, &button);
    screen_send_event(get_screen_context(), screen_event, mPid);
}

void QNXWindow::processMouseEvent(const screen_event_t& screen_event) {
    static int s_lastButtonState = 0;
    int buttonState = 0;
    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_BUTTONS, &buttonState);
    Event event;
    if (buttonState == FAKE_MOUSE_BUTTON_FOR_TRIGGER_TEST_EVENT) {
        event.Type = Event::EVENT_TEST;
        pushEvent(event);
        return;
    }

    int lastButtonState = s_lastButtonState;

    s_lastButtonState = buttonState;
    int wheel_ticks = 0;
    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_MOUSE_WHEEL, &wheel_ticks);
    if (wheel_ticks)
        event.Type = Event::EVENT_MOUSE_WHEEL_MOVED;
    else if (buttonState == lastButtonState)
        event.Type = Event::EVENT_MOUSE_MOVED;
    else if (buttonState > lastButtonState)
        event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
    else
        event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;

    switch (event.Type) {
        case Event::EVENT_MOUSE_WHEEL_MOVED:
            event.MouseWheel.Delta = wheel_ticks;
            pushEvent(event);
            break;
        case Event::EVENT_MOUSE_MOVED:
            int position[2];
            screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, position);
            event.MouseMove.X = position[0];
            event.MouseMove.Y = position[1];
            pushEvent(event);
            break;
        case Event::EVENT_MOUSE_BUTTON_RELEASED:
        case Event::EVENT_MOUSE_BUTTON_PRESSED:
            event.MouseButton.Button = event.Type == Event::EVENT_MOUSE_BUTTON_RELEASED
                                           ? QNXCodeToButton(lastButtonState)
                                           : QNXCodeToButton(buttonState);
            if (event.MouseButton.Button != MOUSEBUTTON_UNKNOWN) {
                // int position[2];
                screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION,
                                             position);
                event.MouseButton.X = position[0];
                event.MouseButton.Y = position[1];
                pushEvent(event);
            }
            break;
    }
}

void QNXWindow::processKeyEvent(const screen_event_t& screen_event) {
    int modifiers = 0;
    int flags = 0;
    int cap = 0;

    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_MODIFIERS, &modifiers);
    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_FLAGS, &flags);
    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_KEY_CAP, &cap);

    Event event;
    event.Key.Code = QNXCodeToKey(cap);
    event.Type = flags & KEY_DOWN || flags & KEY_REPEAT ? Event::EVENT_KEY_PRESSED
                                                        : Event::EVENT_KEY_RELEASED;
    event.Key.Shift = modifiers & KEYMOD_SHIFT;
    event.Key.Control = modifiers & KEYMOD_CTRL;
    event.Key.Alt = modifiers & KEYMOD_ALT;
    event.Key.System = 0;
    pushEvent(event);
}

void QNXWindow::processPropertyChangedEvent(const screen_event_t& event) {
    int property = 0;
    screen_get_event_property_iv(event, SCREEN_PROPERTY_NAME, &property);
    int type = 0;
    screen_get_event_property_iv(event, SCREEN_PROPERTY_OBJECT_TYPE, &type);
    if (type != SCREEN_OBJECT_TYPE_WINDOW) return;

    if (property == SCREEN_PROPERTY_SIZE) {
        screen_window_t screenWindow = 0;
        if (screen_get_event_property_pv(event, SCREEN_PROPERTY_WINDOW, (void**)&screenWindow))
            return;
        if (screenWindow != mWindow) return;

        int size[2] = {0, 0};
        if (screen_get_window_property_iv(screenWindow, SCREEN_PROPERTY_SIZE, size)) return;
        Event event;
        event.Type = Event::EVENT_RESIZED;
        event.Size.Width = size[0];
        event.Size.Height = size[1];
        pushEvent(event);
    } else if (property == SCREEN_PROPERTY_FOCUS) {
        screen_window_t screenWindow = 0;
        if (screen_get_event_property_pv(event, SCREEN_PROPERTY_WINDOW, (void**)&screenWindow))
            return;
        if (screenWindow != mWindow) return;
        int value;
        if (screen_get_window_property_iv(screenWindow, property, &value)) return;
        Event event;
        event.Type = value ? Event::EVENT_GAINED_FOCUS : Event::EVENT_LOST_FOCUS;
    }
}

void QNXWindow::processInputControlEvent(const screen_event_t& screen_event) {
    int val;
    if (screen_get_event_property_iv(screen_event, SCREEN_INPUT_CONTROL_POINTER_STOP, &val)) return;
    if (!val) return;
    screen_window_t screenWindow = 0;
    if (screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void**)&screenWindow))
        return;
    if (screenWindow != mWindow) return;
    Event event;
    event.Type = Event::EVENT_MOUSE_LEFT;
    pushEvent(event);
}

void QNXWindow::processCloseEvent(const screen_event_t& screen_event) {
    screen_window_t screenWindow = 0;
    if (screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void**)&screenWindow))
        return;
    if (screenWindow != mWindow) return;
    Event event;
    event.Type = Event::EVENT_CLOSED;
    pushEvent(event);
}
