/*****************************************************************************************
 *                                                                                       *
 * OpenSpace                                                                             *
 *                                                                                       *
 * Copyright (c) 2014-2023                                                               *
 *                                                                                       *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this  *
 * software and associated documentation files (the "Software"), to deal in the Software *
 * without restriction, including without limitation the rights to use, copy, modify,    *
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to    *
 * permit persons to whom the Software is furnished to do so, subject to the following   *
 * conditions:                                                                           *
 *                                                                                       *
 * The above copyright notice and this permission notice shall be included in all copies *
 * or substantial portions of the Software.                                              *
 *                                                                                       *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,   *
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A         *
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT    *
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF  *
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE  *
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                         *
 ****************************************************************************************/

#include <modules/cefwebgui/cefwebguimodule.h>

#include <modules/webbrowser/webbrowsermodule.h>
#include <modules/cefwebgui/include/guirenderhandler.h>
#include <modules/cefwebgui/include/guikeyboardhandler.h>
#include <modules/webbrowser/include/browserinstance.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/globalscallbacks.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/engine/windowdelegate.h>
#include <ghoul/fmt.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/dictionary.h>
#include <ghoul/misc/profiling.h>

namespace {
    constexpr openspace::properties::Property::PropertyInfo EnabledInfo = {
        "Enabled",
        "Enabled",
        "This setting determines whether the browser should be enabled or not",
        openspace::properties::Property::Visibility::AdvancedUser
    };

    constexpr openspace::properties::Property::PropertyInfo ReloadInfo = {
        "Reload",
        "Reload",
        "Trigger this property to reload the browser",
        // @VISIBILITY(2.2)
        openspace::properties::Property::Visibility::User
    };

    constexpr openspace::properties::Property::PropertyInfo VisibleInfo = {
        "Visible",
        "Is Visible",
        "This setting determines whether the browser should be visible or not",
        // @VISIBILITY(2.75)
        openspace::properties::Property::Visibility::User
    };

    constexpr openspace::properties::Property::PropertyInfo GuiUrlInfo = {
        "GuiUrl",
        "GUI URL",
        "The URL of the webpage that is used to load the WebGUI from",
        // @VISIBILITY(3.5)
        openspace::properties::Property::Visibility::AdvancedUser
    };

    constexpr openspace::properties::Property::PropertyInfo GuiScaleInfo = {
        "GuiScale",
        "Gui Scale",
        "GUI scale multiplier",
        openspace::properties::Property::Visibility::Always
    };
} // namespace

namespace openspace {

CefWebGuiModule::CefWebGuiModule()
    : OpenSpaceModule(CefWebGuiModule::Name)
    , _enabled(EnabledInfo, true)
    , _visible(VisibleInfo, true)
    , _reload(ReloadInfo)
    , _url(GuiUrlInfo, "")
    , _guiScale(GuiScaleInfo, 1.f, 0.1f, 3.f)
{
    addProperty(_enabled);
    addProperty(_visible);
    addProperty(_reload);
    addProperty(_url);
    addProperty(_guiScale);
}

void CefWebGuiModule::startOrStopGui() {
    ZoneScoped;

    WebBrowserModule* webBrowserModule = global::moduleEngine->module<WebBrowserModule>();

    const bool isMaster = global::windowDelegate->isMaster();

    if (_enabled && isMaster) {
        LDEBUGC("WebBrowser", fmt::format("Loading GUI from {}", _url));

        if (!_instance) {
            _instance = std::make_unique<BrowserInstance>(
                new GUIRenderHandler,
                new GUIKeyboardHandler
            );
            _instance->initialize();
            _instance->reshape(static_cast<glm::ivec2>(
                static_cast<glm::vec2>(global::windowDelegate->guiWindowResolution()) *
                global::windowDelegate->dpiScaling()
            ));
            if (!_url.value().empty()) {
                _instance->loadUrl(_url);
            }
        }
        if (_visible) {
            webBrowserModule->attachEventHandler(_instance.get());
        }

        _instance->setZoom(_guiScale);

        webBrowserModule->addBrowser(_instance.get());
    }
    else if (_instance) {
        _instance->close(true);
        webBrowserModule->removeBrowser(_instance.get());
        webBrowserModule->detachEventHandler();
        _instance.reset();
    }
}

void CefWebGuiModule::internalInitialize(const ghoul::Dictionary& configuration) {
    ZoneScoped;

    WebBrowserModule* webBrowserModule =
        global::moduleEngine->module<WebBrowserModule>();

    bool available = webBrowserModule && webBrowserModule->isEnabled();

    if (!available) {
        return;
    }

    _enabled.onChange([this]() {
        ZoneScopedN("CefWebGuiModule::enabled");

        startOrStopGui();
    });

    _url.onChange([this]() {
        ZoneScopedN("CefWebGuiModule::url");

        if (_instance) {
            _instance->loadUrl(_url);
        }
    });

    _reload.onChange([this]() {
        ZoneScopedN("CefWebGuiModule::reload");

        if (_instance) {
            _instance->reloadBrowser();
        }
    });

    _guiScale.onChange([this]() {
        ZoneScopedN("CefWebGuiModule::guiScale");

        if (_instance) {
            _instance->setZoom(_guiScale);
        }
    });

    _visible.onChange([this, webBrowserModule]() {
        ZoneScopedN("CefWebGuiModule::visible");

        if (_visible && _instance) {
            webBrowserModule->attachEventHandler(_instance.get());
        }
        else {
            webBrowserModule->detachEventHandler();
        }
    });

    // We need this to make sure that the browser is reloaded
    // once the endpoint comes online, on OpenSpace startup.

    // TODO: See if the hardcoded endpoint `frontend` below can be removed.
    // Possible fix: Reload browser if cefwebgui is routed to localhost
    // and the same endpoint that just came online.
    WebGuiModule* webGuiModule = global::moduleEngine->module<WebGuiModule>();

    _endpointCallback = webGuiModule->addEndpointChangeCallback(
        [this](const std::string& endpoint, bool exists) {
            ZoneScopedN("CefWebGuiModule::endpointCallback");
            if (exists && endpoint == "frontend" && _instance) {
                _instance->reloadBrowser();
            }
        }
    );

    if (configuration.hasValue<double>(GuiScaleInfo.identifier)) {
        _guiScale = static_cast<float>(
            configuration.value<double>(GuiScaleInfo.identifier)
        );
    }

    _enabled = configuration.hasValue<bool>(EnabledInfo.identifier) &&
               configuration.value<bool>(EnabledInfo.identifier);

    _visible = configuration.hasValue<bool>(VisibleInfo.identifier) &&
               configuration.value<bool>(VisibleInfo.identifier);

    global::callback::initializeGL->emplace_back([this]() {
        startOrStopGui();
    });

    global::callback::draw2D->emplace_back([this](){
        ZoneScopedN("CefWebGuiModule");

        const bool isGuiWindow =
            global::windowDelegate->hasGuiWindow() ?
            global::windowDelegate->isGuiWindow() :
            global::windowDelegate->currentWindowId()
                == global::windowDelegate->firstWindowId();
        const bool isMaster = global::windowDelegate->isMaster();

        if (isGuiWindow && isMaster && _instance) {
            if (global::windowDelegate->windowHasResized() || _instance->_shouldReshape) {
                glm::ivec2 csws = global::windowDelegate->guiWindowResolution();
                _instance->reshape(static_cast<glm::ivec2>(
                    static_cast<glm::vec2>(csws) * global::windowDelegate->dpiScaling()
                ));
                _instance->_shouldReshape = false;
            }
            if (_visible) {
                _instance->draw();
            }
        }
    });

    global::callback::deinitializeGL->emplace_back([this]() {
        ZoneScopedN("CefWebGuiModule");

        if (_endpointCallback != -1) {
            WebGuiModule* m = global::moduleEngine->module<WebGuiModule>();
            m->removeEndpointChangeCallback(_endpointCallback);
            _endpointCallback = -1;
        }
        _enabled = false;
        startOrStopGui();
    });
}

} // namespace openspace
