/*
 * DISTRHO Cardinal Plugin
 * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the LICENSE file.
 */

#include "RemoteUI.hpp"

#include <asset.hpp>
// #include <random.hpp>
#include <patch.hpp>
#include <settings.hpp>
#include <system.hpp>

#include <app/Scene.hpp>
#include <engine/Engine.hpp>

#include "AsyncDialog.hpp"

// --------------------------------------------------------------------------------------------------------------------

CardinalRemoteUI::CardinalRemoteUI(Window& window, const std::string& templatePath)
    : NanoTopLevelWidget(window)
{
    CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(rack::contextGet());
    context->nativeWindowId = window.getNativeWindowHandle();
    context->tlw = this;

    // --------------------------------------------------------------------------

    rack::window::WindowSetPluginRemote(context->window, this);

    // hide "Browse VCV Library" button
    rack::widget::Widget* const browser = context->scene->browser->children.back();
    rack::widget::Widget* const headerLayout = browser->children.front();
    rack::widget::Widget* const libraryButton = headerLayout->children.back();
    libraryButton->hide();

    // Report to user if something is wrong with the installation
    std::string errorMessage;

    if (rack::asset::systemDir.empty())
    {
        errorMessage = "Failed to locate Cardinal plugin bundle.\n"
                       "Install Cardinal with its plugin bundle folder intact and try again.";
    }
    else if (! rack::system::exists(rack::asset::systemDir))
    {
        errorMessage = rack::string::f("System directory \"%s\" does not exist. "
                                       "Make sure Cardinal was downloaded and installed correctly.",
                                       rack::asset::systemDir.c_str());
    }

    if (! errorMessage.empty())
        asyncDialog::create(errorMessage.c_str());

    context->window->step();

    WindowParametersSetCallback(context->window, this);

    // --------------------------------------------------------------------------

    addIdleCallback(this);
}

CardinalRemoteUI::~CardinalRemoteUI()
{
    removeIdleCallback(this);

    // --------------------------------------------------------------------------

    CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(rack::contextGet());

    context->nativeWindowId = 0;

    rack::window::WindowSetPluginRemote(context->window, nullptr);
}

void CardinalRemoteUI::onNanoDisplay()
{
    CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(rack::contextGet());
    const ScopedContext sc(context);
    context->window->step();
}

void CardinalRemoteUI::idleCallback()
{
    /*
    if (filebrowserhandle != nullptr && fileBrowserIdle(filebrowserhandle))
    {
        {
            const char* const path = fileBrowserGetPath(filebrowserhandle);

            const ScopedContext sc(this);
            filebrowseraction(path != nullptr ? strdup(path) : nullptr);
        }

        fileBrowserClose(filebrowserhandle);
        filebrowseraction = nullptr;
        filebrowserhandle = nullptr;
    }
    */

    if (windowParameters.rateLimit != 0 && ++rateLimitStep % (windowParameters.rateLimit * 2))
        return;

    rateLimitStep = 0;
    repaint();
}

void CardinalRemoteUI::WindowParametersChanged(const WindowParameterList param, float value)
{
    float mult = 1.0f;

    switch (param)
    {
    case kWindowParameterShowTooltips:
        windowParameters.tooltips = value > 0.5f;
        break;
    case kWindowParameterCableOpacity:
        mult = 100.0f;
        windowParameters.cableOpacity = value;
        break;
    case kWindowParameterCableTension:
        mult = 100.0f;
        windowParameters.cableTension = value;
        break;
    case kWindowParameterRackBrightness:
        mult = 100.0f;
        windowParameters.rackBrightness = value;
        break;
    case kWindowParameterHaloBrightness:
        mult = 100.0f;
        windowParameters.haloBrightness = value;
        break;
    case kWindowParameterKnobMode:
        switch (static_cast<int>(value + 0.5f))
        {
        case rack::settings::KNOB_MODE_LINEAR:
            value = 0;
            windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
            break;
        case rack::settings::KNOB_MODE_ROTARY_ABSOLUTE:
            value = 1;
            windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
            break;
        case rack::settings::KNOB_MODE_ROTARY_RELATIVE:
            value = 2;
            windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
            break;
        }
        break;
    case kWindowParameterWheelKnobControl:
        windowParameters.knobScroll = value > 0.5f;
        break;
    case kWindowParameterWheelSensitivity:
        mult = 1000.0f;
        windowParameters.knobScrollSensitivity = value;
        break;
    case kWindowParameterLockModulePositions:
        windowParameters.lockModules = value > 0.5f;
        break;
    case kWindowParameterUpdateRateLimit:
        windowParameters.rateLimit = static_cast<int>(value + 0.5f);
        rateLimitStep = 0;
        break;
    case kWindowParameterBrowserSort:
        windowParameters.browserSort = static_cast<int>(value + 0.5f);
        break;
    case kWindowParameterBrowserZoom:
        windowParameters.browserZoom = value;
        value = std::pow(2.f, value) * 100.0f;
        break;
    case kWindowParameterInvertZoom:
        windowParameters.invertZoom = value > 0.5f;
        break;
    case kWindowParameterSqueezeModulePositions:
        windowParameters.squeezeModules = value > 0.5f;
        break;
    default:
        return;
    }

    // setParameterValue(kModuleParameters + param + 1, value * mult);
}

// --------------------------------------------------------------------------------------------------------------------

static int glfwMods(const uint mod) noexcept
{
    int mods = 0;

    if (mod & kModifierControl)
        mods |= GLFW_MOD_CONTROL;
    if (mod & kModifierShift)
        mods |= GLFW_MOD_SHIFT;
    if (mod & kModifierAlt)
        mods |= GLFW_MOD_ALT;
    if (mod & kModifierSuper)
        mods |= GLFW_MOD_SUPER;

    /*
    if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
        mods |= GLFW_MOD_SHIFT;
    if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
        mods |= GLFW_MOD_CONTROL;
    if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
        mods |= GLFW_MOD_ALT;
    if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
        mods |= GLFW_MOD_SUPER;
    */

    return mods;
}

bool CardinalRemoteUI::onMouse(const MouseEvent& ev)
{
    if (ev.press)
        getWindow().focus();

    const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
    int mods = glfwMods(ev.mod);

    int button;

    switch (ev.button)
    {
    case 1: button = GLFW_MOUSE_BUTTON_LEFT;   break;
    case 2: button = GLFW_MOUSE_BUTTON_RIGHT;  break;
    case 3: button = GLFW_MOUSE_BUTTON_MIDDLE; break;
    default:
        button = ev.button;
        break;
    }

   #ifdef DISTRHO_OS_MAC
    // Remap Ctrl-left click to right click on macOS
    if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
        button = GLFW_MOUSE_BUTTON_RIGHT;
        mods &= ~GLFW_MOD_CONTROL;
    }
    // Remap Ctrl-shift-left click to middle click on macOS
    if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
        button = GLFW_MOUSE_BUTTON_MIDDLE;
        mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
    }
   #endif

    CardinalPluginContext* context = static_cast<CardinalPluginContext*>(rack::contextGet());
    const ScopedContext sc(context, mods);
    return context->event->handleButton(lastMousePos, button, action, mods);
}

bool CardinalRemoteUI::onMotion(const MotionEvent& ev)
{
    const rack::math::Vec mousePos = rack::math::Vec(ev.pos.getX(), ev.pos.getY()).div(getScaleFactor()).round();
    const rack::math::Vec mouseDelta = mousePos.minus(lastMousePos);

    lastMousePos = mousePos;

    CardinalPluginContext* context = static_cast<CardinalPluginContext*>(rack::contextGet());
    const ScopedContext sc(context);
    return context->event->handleHover(mousePos, mouseDelta);
}

bool CardinalRemoteUI::onScroll(const ScrollEvent& ev)
{
    rack::math::Vec scrollDelta = rack::math::Vec(ev.delta.getX(), ev.delta.getY());
   #ifndef DISTRHO_OS_MAC
    scrollDelta = scrollDelta.mult(50.0);
   #endif

    const int mods = glfwMods(ev.mod);

    CardinalPluginContext* context = static_cast<CardinalPluginContext*>(rack::contextGet());
    const ScopedContext sc(context, mods);
    return context->event->handleScroll(lastMousePos, scrollDelta);
}

bool CardinalRemoteUI::onCharacterInput(const CharacterInputEvent& ev)
{
    if (ev.character < ' ' || ev.character >= kKeyDelete)
        return false;

    const int mods = glfwMods(ev.mod);

    CardinalPluginContext* context = static_cast<CardinalPluginContext*>(rack::contextGet());
    const ScopedContext sc(context, mods);
    return context->event->handleText(lastMousePos, ev.character);
}

bool CardinalRemoteUI::onKeyboard(const KeyboardEvent& ev)
{
    const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
    const int mods = glfwMods(ev.mod);

    /* These are unsupported in pugl right now
    #define GLFW_KEY_KP_0               320
    #define GLFW_KEY_KP_1               321
    #define GLFW_KEY_KP_2               322
    #define GLFW_KEY_KP_3               323
    #define GLFW_KEY_KP_4               324
    #define GLFW_KEY_KP_5               325
    #define GLFW_KEY_KP_6               326
    #define GLFW_KEY_KP_7               327
    #define GLFW_KEY_KP_8               328
    #define GLFW_KEY_KP_9               329
    #define GLFW_KEY_KP_DECIMAL         330
    #define GLFW_KEY_KP_DIVIDE          331
    #define GLFW_KEY_KP_MULTIPLY        332
    #define GLFW_KEY_KP_SUBTRACT        333
    #define GLFW_KEY_KP_ADD             334
    #define GLFW_KEY_KP_ENTER           335
    #define GLFW_KEY_KP_EQUAL           336
    */

    int key;
    switch (ev.key)
    {
    case '\r': key = GLFW_KEY_ENTER; break;
    case '\t': key = GLFW_KEY_TAB; break;
    case kKeyBackspace: key = GLFW_KEY_BACKSPACE; break;
    case kKeyEscape: key = GLFW_KEY_ESCAPE; break;
    case kKeyDelete: key = GLFW_KEY_DELETE; break;
    case kKeyF1: key = GLFW_KEY_F1; break;
    case kKeyF2: key = GLFW_KEY_F2; break;
    case kKeyF3: key = GLFW_KEY_F3; break;
    case kKeyF4: key = GLFW_KEY_F4; break;
    case kKeyF5: key = GLFW_KEY_F5; break;
    case kKeyF6: key = GLFW_KEY_F6; break;
    case kKeyF7: key = GLFW_KEY_F7; break;
    case kKeyF8: key = GLFW_KEY_F8; break;
    case kKeyF9: key = GLFW_KEY_F9; break;
    case kKeyF10: key = GLFW_KEY_F10; break;
    case kKeyF11: key = GLFW_KEY_F11; break;
    case kKeyF12: key = GLFW_KEY_F12; break;
    case kKeyLeft: key = GLFW_KEY_LEFT; break;
    case kKeyUp: key = GLFW_KEY_UP; break;
    case kKeyRight: key = GLFW_KEY_RIGHT; break;
    case kKeyDown: key = GLFW_KEY_DOWN; break;
    case kKeyPageUp: key = GLFW_KEY_PAGE_UP; break;
    case kKeyPageDown: key = GLFW_KEY_PAGE_DOWN; break;
    case kKeyHome: key = GLFW_KEY_HOME; break;
    case kKeyEnd: key = GLFW_KEY_END; break;
    case kKeyInsert: key = GLFW_KEY_INSERT; break;
    case kKeyShiftL: key = GLFW_KEY_LEFT_SHIFT; break;
    case kKeyShiftR: key = GLFW_KEY_RIGHT_SHIFT; break;
    case kKeyControlL: key = GLFW_KEY_LEFT_CONTROL; break;
    case kKeyControlR: key = GLFW_KEY_RIGHT_CONTROL; break;
    case kKeyAltL: key = GLFW_KEY_LEFT_ALT; break;
    case kKeyAltR: key = GLFW_KEY_RIGHT_ALT; break;
    case kKeySuperL: key = GLFW_KEY_LEFT_SUPER; break;
    case kKeySuperR: key = GLFW_KEY_RIGHT_SUPER; break;
    case kKeyMenu: key = GLFW_KEY_MENU; break;
    case kKeyCapsLock: key = GLFW_KEY_CAPS_LOCK; break;
    case kKeyScrollLock: key = GLFW_KEY_SCROLL_LOCK; break;
    case kKeyNumLock: key = GLFW_KEY_NUM_LOCK; break;
    case kKeyPrintScreen: key = GLFW_KEY_PRINT_SCREEN; break;
    case kKeyPause: key = GLFW_KEY_PAUSE; break;
    default:
        // glfw expects uppercase
        if (ev.key >= 'a' && ev.key <= 'z')
            key = ev.key - ('a' - 'A');
        else
            key = ev.key;
        break;
    }

    CardinalPluginContext* context = static_cast<CardinalPluginContext*>(rack::contextGet());
    const ScopedContext sc(context, mods);
    return context->event->handleKey(lastMousePos, key, ev.keycode, action, mods);
}

void CardinalRemoteUI::onResize(const ResizeEvent& ev)
{
    NanoTopLevelWidget::onResize(ev);

    CardinalPluginContext* context = static_cast<CardinalPluginContext*>(rack::contextGet());
    WindowSetInternalSize(context->window, rack::math::Vec(ev.size.getWidth(), ev.size.getHeight()));
}

// --------------------------------------------------------------------------------------------------------------------
