/*****************************************************************************************
 *                                                                                       *
 * OpenSpace                                                                             *
 *                                                                                       *
 * Copyright (c) 2014-2020                                                               *
 *                                                                                       *
 * 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/imgui/include/guiglobebrowsingcomponent.h>

#include <modules/globebrowsing/globebrowsingmodule.h>
#include <modules/globebrowsing/src/renderableglobe.h>
#include <modules/imgui/include/imgui_include.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/interaction/navigationhandler.h>
#include <openspace/interaction/orbitalnavigator.h>
#include <openspace/rendering/renderable.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/scripting/scriptengine.h>
#include <ghoul/fmt.h>
#include <ghoul/logging/logmanager.h>
#include <numeric>

namespace {
    const ImVec2 WindowSize = ImVec2(350, 500);
} // namespace

namespace openspace::gui {

GuiGlobeBrowsingComponent::GuiGlobeBrowsingComponent()
    : GuiPropertyComponent("GlobeBrowsing", "Globe Browsing")
{}

void GuiGlobeBrowsingComponent::render() {
    GlobeBrowsingModule* module = global::moduleEngine.module<GlobeBrowsingModule>();
    using UrlInfo = GlobeBrowsingModule::UrlInfo;
    using Capabilities = GlobeBrowsingModule::Capabilities;
    using Layer = GlobeBrowsingModule::Layer;

    ImGui::SetNextWindowCollapsed(_isCollapsed);

    bool e = _isEnabled;

    ImGui::Begin("Globe Browsing", &e, WindowSize, 0.5f);
    _isEnabled = e;
    _isCollapsed = ImGui::IsWindowCollapsed();
    defer { ImGui::End(); };

    // Render the list of planets
    std::vector<SceneGraphNode*> nodes =
        global::renderEngine.scene()->allSceneGraphNodes();

    nodes.erase(
        std::remove_if(
            nodes.begin(),
            nodes.end(),
            [](SceneGraphNode* n) {
                using namespace globebrowsing;
                const Renderable* r = n->renderable();
                const RenderableGlobe* rg = dynamic_cast<const RenderableGlobe*>(r);
                return rg == nullptr;
            }
        ),
        nodes.end()
    );
    std::sort(
        nodes.begin(),
        nodes.end(),
        [module](SceneGraphNode* lhs, SceneGraphNode* rhs) {
            const bool lhsHasUrl = module->hasUrlInfo(lhs->identifier());
            const bool rhsHasUrl = module->hasUrlInfo(rhs->identifier());

            if (lhsHasUrl && !rhsHasUrl) {
                return true;
            }
            if (!lhsHasUrl && rhsHasUrl) {
                return false;
            }

            return lhs->guiName() < rhs->guiName();
        }
    );


    auto firstWithoutUrl = std::find_if(
        nodes.begin(),
        nodes.end(),
        [module](SceneGraphNode* n) {
            return !module->hasUrlInfo(n->identifier());
        }
    );
    nodes.insert(firstWithoutUrl, nullptr);

    std::string nodeNames;
    for (SceneGraphNode* n : nodes) {
        // Add separator between nodes with URL and nodes without
        if (n) {
            nodeNames += n->identifier() + '\0';
        }
        else {
            nodeNames += std::string("===== =====") + '\0';
        }
    }

    int iNode = -1;
    if (_currentNode.empty()) {
        // We haven't selected a node yet, so first instinct is to use the current focus
        // node

        // Check if the focus node is a RenderableGlobe
        const SceneGraphNode* const focus =
            global::navigationHandler.orbitalNavigator().anchorNode();

        const auto it = std::find(nodes.cbegin(), nodes.cend(), focus);
        if (it != nodes.end()) {
            _currentNode = focus->identifier();
            iNode = static_cast<int>(std::distance(nodes.cbegin(), it));
        }
    }
    else {
        const auto it = std::find_if(
            nodes.cbegin(),
            nodes.cend(),
            [this](SceneGraphNode* lhs) {
                return lhs && (lhs->identifier() == _currentNode);
            }
        );
        iNode = static_cast<int>(std::distance(nodes.cbegin(), it));
    }

    bool isNodeChanged = ImGui::Combo("Globe", &iNode, nodeNames.c_str());

    ImGui::SameLine();
    bool selectFocusNode = ImGui::Button("From Focus");
    if (selectFocusNode) {
        const SceneGraphNode* const focus =
            global::navigationHandler.orbitalNavigator().anchorNode();

        const auto it = std::find(nodes.cbegin(), nodes.cend(), focus);
        if (it != nodes.end()) {
            _currentNode = focus->identifier();
            iNode = static_cast<int>(std::distance(nodes.cbegin(), it));
            isNodeChanged = true;
        }
    }

    if (iNode == -1) {
        // This should only occur if the Focusnode is not a RenderableGlobe
        // or if there are no nodes
        return;
    }

    if (!nodes[iNode]) {
        // The user selected the separator
        return;
    }

    _currentNode = nodes[iNode]->identifier();

    if (isNodeChanged) {
        _currentServer = "";
    }

    ImGui::Separator();

    // Render the list of servers for the planet
    std::vector<UrlInfo> urlInfo = module->urlInfo(_currentNode);

    std::string serverList = std::accumulate(
        urlInfo.cbegin(),
        urlInfo.cend(),
        std::string(),
        [](std::string lhs, const UrlInfo& i) {
            return lhs + i.name + ": (" + i.url + ")" + '\0';
        }
    );

    int iServer = -1;
    if (_currentServer.empty()) {
        // We haven't selected a server yet, so first instinct is to just use the first
        if (!urlInfo.empty()) {
            _currentServer = urlInfo[0].name;
            iServer = 0;
        }
    }
    else {
        const auto it = std::find_if(
            urlInfo.cbegin(),
            urlInfo.cend(),
            [this](const UrlInfo& i) {
                return i.name == _currentServer;
            }
        );
        if (it != urlInfo.end()) {
            iServer = static_cast<int>(std::distance(urlInfo.cbegin(), it));
        }
    }

    ImGui::Combo("Server", &iServer, serverList.c_str());

    ImGui::SameLine(0.f, 60.f);

    if (ImGui::Button("Add Server")) {
        ImGui::OpenPopup("globebrowsing_add_server");
    }

    if (ImGui::BeginPopup("globebrowsing_add_server")) {
        constexpr const int InputBufferSize = 512;
        static char NameInputBuffer[InputBufferSize];
        ImGui::InputText("Server Name", NameInputBuffer, InputBufferSize);

        static char UrlInputBuffer[InputBufferSize];
        ImGui::InputText("Server URL", UrlInputBuffer, InputBufferSize);

        const bool addServer = ImGui::Button("Add Server");
        if (addServer && (!_currentNode.empty())) {
            module->loadWMSCapabilities(
                std::string(NameInputBuffer),
                _currentNode,
                std::string(UrlInputBuffer)
            );
            std::memset(NameInputBuffer, 0, InputBufferSize * sizeof(char));
            std::memset(UrlInputBuffer, 0, InputBufferSize * sizeof(char));

            urlInfo = module->urlInfo(_currentNode);
            _currentServer = urlInfo.back().name;
            --iServer;
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    }
    ImGui::SameLine(0.f, 20.f);

    const bool deleteServer = ImGui::Button("Delete Server");
    if (deleteServer) {
        module->removeWMSServer(_currentServer);
        _currentServer = "";
        iServer = -1;

    }

    if (iServer < 0) {
        return;
    }
    _currentServer = urlInfo[iServer].name;


    // Can't use urlIt here since it might have been invalidated before
    if (urlInfo.empty()) {
        // There are no server so we have to bail
        return;
    }

    ImGui::Separator();

    Capabilities cap = module->capabilities(_currentServer);

    if (cap.empty()) {
        LWARNINGC("GlobeBrowsing", fmt::format("Unknown server: '{}'", _currentServer));
    }

    ImGui::Columns(6, nullptr, false);

    const float width = ImGui::GetWindowWidth();
    constexpr const float ButtonWidth = 60.f;
    ImGui::SetColumnOffset(5, width - 1.5f * ButtonWidth);
    ImGui::SetColumnOffset(4, width - 2.5f * ButtonWidth);
    ImGui::SetColumnOffset(3, width - 3.5f * ButtonWidth);
    ImGui::SetColumnOffset(2, width - 4.5f * ButtonWidth);
    ImGui::SetColumnOffset(1, width - 5.5f * ButtonWidth);
    ImGui::SetColumnOffset(0, 0);

    ImGui::Text("%s", "Layer name");
    ImGui::NextColumn();
    ImGui::Text("%s", "Add as ...");
    ImGui::NextColumn();
    ImGui::NextColumn();
    ImGui::NextColumn();
    ImGui::NextColumn();
    ImGui::NextColumn();
    ImGui::Separator();

    for (const Layer& l : cap) {
        if (l.name.empty() || l.url.empty()) {
            continue;
        }

        ImGui::PushID(l.url.c_str());

        ImGui::Text("%s", l.name.c_str());
        ImGui::NextColumn();

        const bool addColor = ImGui::Button("Color", { ButtonWidth, 25.f });
        ImGui::NextColumn();

        const bool addNight = ImGui::Button("Night", { ButtonWidth, 25.f });
        ImGui::NextColumn();

        const bool addOverlay = ImGui::Button("Overlay", { ButtonWidth, 25.f });
        ImGui::NextColumn();

        const bool addHeight = ImGui::Button("Height", { ButtonWidth, 25.f });
        ImGui::NextColumn();

        const bool addWaterMask = ImGui::Button("Water", { ButtonWidth, 25.f });
        ImGui::NextColumn();

        auto addFunc = [n = _currentNode, &l](const std::string& type) {
            std::string layerName = l.name;
            std::replace(layerName.begin(), layerName.end(), '.', '-');
            layerName.erase(
                std::remove(layerName.begin(), layerName.end(), ' '),
                layerName.end()
            );
            global::scriptEngine.queueScript(
                fmt::format(
                    "openspace.globebrowsing.addLayer(\
                        '{}', \
                        '{}', \
                        {{ \
                            Identifier = '{}',\
                            Name = '{}',\
                            FilePath = '{}',\
                            Enabled = true\
                        }}\
                    );",
                    n,
                    type,
                    layerName,
                    l.name,
                    l.url
                ),
                scripting::ScriptEngine::RemoteScripting::Yes
            );
        };

        if (addColor) {
            addFunc("ColorLayers");
        }
        if (addNight) {
            addFunc("NightLayers");
        }
        if (addOverlay) {
            addFunc("Overlays");
        }
        if (addHeight) {
            addFunc("HeightLayers");
        }
        if (addWaterMask) {
            addFunc("WaterMasks");
        }

        ImGui::PopID();
    }
    ImGui::Columns(1);
}

} // namespace openspace::gui
