/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
 * Copyright 2018 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
#ifndef OSGEARTH_IMGUI_MAP_LAYERS_GUI
#define OSGEARTH_IMGUI_MAP_LAYERS_GUI

#include "ImGui"
#include <osgEarth/MapNode>
#include <osgEarth/EarthManipulator>
#include <osgEarth/ViewFitter>
#include <osgEarth/ThreeDTilesLayer>
#include <osgEarth/ImageLayer>
#include <osgEarth/MapboxGLImageLayer>
#include <osgEarth/FeatureModelLayer>
#include <osgEarth/ModelLayer>
#include <osgEarth/TerrainConstraintLayer>
#include <osgEarth/NodeUtils>
#include <osgEarth/TerrainEngineNode>
#include <osgEarth/ExampleResources>
#include <osgEarth/Sky>
#include <osg/Camera>

#ifdef HAVE_OSGEARTHCESIUM
#include <osgEarthCesium/CesiumLayer>
#endif

#ifndef NOMINMAX
#define NOMINMAX
#endif
#if defined(__has_include)
#if __has_include(<third_party/portable-file-dialogs/portable-file-dialogs.h>)
#include <third_party/portable-file-dialogs/portable-file-dialogs.h>
#define HAS_PFD
#endif
#endif

#include <osgEarth/Registry>
#include <osgEarth/XYZ>
#include <osgEarth/TMS>
#include <osgEarth/GDAL>
#include <osgEarth/WMS>
#include <osgEarth/DebugImageLayer>

#ifdef OSGEARTH_HAVE_MBTILES
#include <osgEarth/MBTiles>
#endif

#ifdef HAVE_OSGEARTHPROCEDURAL
#include <osgEarthProcedural/LifeMapLayer>
#include <osgEarthProcedural/TextureSplattingLayer>
#include <osgEarthProcedural/VegetationLayer>
#include <osgEarthProcedural/BiomeLayer>
#endif

namespace osgEarth
{
    namespace GUI
    {
        using namespace osgEarth;
        using namespace osgEarth::Contrib;
        using namespace osgEarth::Util;

        struct AddWMSDialog
        {
            AddWMSDialog()
            {
                strcpy(url, "http://readymap.org/readymap/tiles");
                memset(name, 0, sizeof(name));
            }

            void draw(osgEarth::MapNode* mapNode)
            {
                if (!visible) return;

                ImGui::Begin("Add WMS", &visible);
                ImGui::InputText("URL", url, IM_ARRAYSIZE(url));
                const std::string wmsVersion = "1.1.1";

                if (ImGui::Button("Fetch layers from server"))
                {
                    std::string wmsString = std::string(url);
                    char sep = wmsString.find_first_of('?') == std::string::npos ? '?' : '&';

                    std::string capUrl = wmsString +
                        sep +
                        std::string("SERVICE=WMS") +
                        std::string("&VERSION=") + wmsVersion +
                        std::string("&REQUEST=GetCapabilities");

                    capabilities = WMS::CapabilitiesReader::read(capUrl, nullptr);
                }

                static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
                ImVec2 outer_size = ImVec2(0.0f, 300.0f);
                if (ImGui::BeginTable("wms_layers", 3, flags, outer_size))
                {
                    // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On
                    ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
                    ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_NoHide);
                    ImGui::TableSetupColumn("Abstract", ImGuiTableColumnFlags_NoHide);
                    ImGui::TableHeadersRow();

                    if (capabilities.valid())
                    {
                        for (auto& layer : capabilities->getLayers())
                        {
                            displayWMSLayer(layer);
                        }
                    }
                    ImGui::EndTable();
                }

                ImGui::InputText("Name", name, IM_ARRAYSIZE(name));

                if (ImGui::Button("OK"))
                {
                    if (selectedWMSLayer)
                    {
                        WMSImageLayer* wms = new WMSImageLayer;
                        if (strlen(name) > 0)
                            wms->setName(name);
                        else
                            wms->setName(selectedWMSLayer->getTitle());
                        wms->setURL(url);
                        wms->setLayers(selectedWMSLayer->getName());
                        mapNode->getMap()->addLayer(wms);
                    }
                    visible = false;
                }

                ImGui::SameLine();
                if (ImGui::Button("Cancel"))
                {
                    visible = false;
                }
                ImGui::End();
            }

            void displayWMSLayer(WMS::Layer* layer)
            {
                ImGui::TableNextRow();
                ImGui::TableNextColumn();
                const bool is_folder = (layer->getLayers().size() > 0);
                if (is_folder)
                {
                    bool open = ImGui::TreeNodeEx(layer->getName().c_str(), ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen);
                    ImGui::TableNextColumn();
                    ImGui::TextDisabled(layer->getTitle().c_str());
                    ImGui::TableNextColumn();
                    ImGui::Text(layer->getAbstract().c_str());
                    if (open)
                    {
                        for (auto& l : layer->getLayers())
                        {
                            displayWMSLayer(l);
                        }
                        ImGui::TreePop();
                    }
                }
                else
                {
                    ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen;
                    if (layer == selectedWMSLayer)
                    {
                        node_flags |= ImGuiTreeNodeFlags_Selected;
                    }
                    ImGui::TreeNodeEx(layer->getName().c_str(), node_flags);
                    if (ImGui::IsItemClicked())
                    {
                        selectedWMSLayer = layer;
                        strcpy(name, selectedWMSLayer->getTitle().c_str());
                    }
                    ImGui::TableNextColumn();
                    ImGui::Text(layer->getTitle().c_str());
                    ImGui::TableNextColumn();
                    ImGui::Text(layer->getAbstract().c_str());
                }
            }

            bool visible = false;
            char url[128];
            char name[1024];
            osg::ref_ptr<WMS::Capabilities> capabilities;
            osg::ref_ptr< WMS::Layer > selectedWMSLayer;
        };


        struct AddTMSDialog
        {
            void draw(MapNode* mapNode)
            {
                if (!visible) return;

                ImGui::Begin("Add TMS", &visible);
                ImGui::InputText("Name", name, IM_ARRAYSIZE(name));
                ImGui::InputText("URL", url, IM_ARRAYSIZE(url));
                ImGui::Checkbox("Treat as Elevation", &isElevation);
                if (ImGui::Button("OK"))
                {
                    if (isElevation)
                    {
                        TMSElevationLayer* tms = new TMSElevationLayer;
                        tms->setName(name);
                        tms->setURL(url);
                        mapNode->getMap()->addLayer(tms);
                    }
                    else
                    {
                        TMSImageLayer* tms = new TMSImageLayer;
                        tms->setName(name);
                        tms->setURL(url);
                        mapNode->getMap()->addLayer(tms);
                    }
                    visible = false;
                }
                ImGui::SameLine();
                if (ImGui::Button("Cancel"))
                {
                    visible = false;
                }
                ImGui::End();
            }

            bool visible = false;
            char url[128] = "http://readymap.org/readymap/tiles/1.0.0/7/";
            char name[64] = "New Layer";
            bool isElevation = false;
        };

        struct AddXYZDialog
        {
            void draw(MapNode* mapNode)
            {
                if (!visible) return;

                ImGui::Begin("Add XYZ", &visible);
                ImGui::InputText("Name", name, IM_ARRAYSIZE(name));
                ImGui::InputText("URL", url, IM_ARRAYSIZE(url));

                static int profile = 1;
                ImGui::Text("Profile");
                if (ImGui::RadioButton("Global Geodetic", profile == 0)) { profile = 0; } ImGui::SameLine();
                if (ImGui::RadioButton("Spherical Mercator", profile == 1)) { profile = 1; }

                ImGui::Checkbox("Treat as Elevation", &isElevation);
                if (ImGui::Button("OK"))
                {
                    if (isElevation)
                    {
                        XYZElevationLayer* xyz = new XYZElevationLayer;
                        xyz->setName(name);
                        xyz->setURL(url);
                        if (profile == 0)      xyz->setProfile(osgEarth::Registry::instance()->getGlobalGeodeticProfile());
                        else if (profile == 1) xyz->setProfile(osgEarth::Registry::instance()->getSphericalMercatorProfile());
                        mapNode->getMap()->addLayer(xyz);
                    }
                    else
                    {
                        XYZImageLayer* xyz = new XYZImageLayer;
                        xyz->setName(name);
                        xyz->setURL(url);
                        if (profile == 0)      xyz->setProfile(osgEarth::Registry::instance()->getGlobalGeodeticProfile());
                        else if (profile == 1) xyz->setProfile(osgEarth::Registry::instance()->getSphericalMercatorProfile());
                        xyz->setProfile(osgEarth::Registry::instance()->getSphericalMercatorProfile());
                        mapNode->getMap()->addLayer(xyz);
                    }
                    visible = false;
                }
                ImGui::SameLine();
                if (ImGui::Button("Cancel"))
                {
                    visible = false;
                }
                ImGui::End();
            }

            bool visible = false;
            char url[128] = "http://[abc].tile.openstreetmap.org/{z}/{x}/{y}.png";
            char name[64] = "New Layer";
            bool isElevation = false;
        };

        class LayersGUI : public BaseGUI
        {
        private:
            osg::observer_ptr<MapNode> _mapNode;
            bool _showDisabled = false;
            bool _sortByCat = false;
            LayerVector _layers;
            //std::vector<bool> _layerExpanded;
            std::unordered_map<Layer*, bool> _layerExpanded;
            int _mapRevision = -1;
            bool _first = true;
            std::unordered_map<Layer*, float> _maxMaxRanges;
            std::unordered_map<Layer*, float> _maxMinRanges;
            std::unordered_map<Layer*, float> _maxAttenRanges;
            bool _addXYZ = false;
            ImageLayer* _mouseOverImageLayer = nullptr;

            static const int NUM_CATS = 8;

            enum LayerCat {
                IMAGE = 0,
                ELEVATION,
                FEATURE,
                MODEL,
                PROCEDURAL,
                CONSTRAINT,
                DATA,
                OTHER
            };
            const std::string _layerCatName[NUM_CATS] = {
                "Image",
                "Elevation",
                "Feature",
                "Model",
                "Procedural",
                "Constraint",
                "Data",
                "Other"
            };

            std::vector<osg::ref_ptr<Layer>> _layersByCat[NUM_CATS];

            AddWMSDialog _addWMSDialog;
            AddTMSDialog _addTMSDialog;
            AddXYZDialog _addXYZDialog;

            using ValueUnderMouse = struct {
                osg::Vec4 pixel;
                GLenum pixelFormat;
                GLenum dataType;
            };
            Future<Result<ValueUnderMouse>> _imageLayerValueUnderMouse;

            std::function<bool(const Layer*)>
                _showPred,
                _showVisibleLayers,
                _showImageLayers,
                _showTerrainSurfaceLayers,
                _showFeatureModelLayers,
                _showConstraintLayers;

        public:

            LayersGUI() : BaseGUI("Map")
            {
                _showVisibleLayers = [this](const Layer* layer)
                    {
                        return
                            dynamic_cast<const VisibleLayer*>(layer) &&
                            layer->getUserProperty("show_in_ui", true);
                    };

                _showImageLayers = [this](const Layer* layer)
                    {
                        return
                            dynamic_cast<const ImageLayer*>(layer) &&
                            layer->getUserProperty("show_in_ui", true);
                    };

                _showTerrainSurfaceLayers = [this](const Layer* layer)
                    {
                        return
                            layer->getRenderType() == layer->RENDERTYPE_TERRAIN_SURFACE &&
                            layer->getUserProperty("show_in_ui", true);
                    };

                _showFeatureModelLayers = [this](const Layer* layer)
                    {
                        return
                            dynamic_cast<const FeatureModelLayer*>(layer) &&
                            layer->getUserProperty("show_in_ui", true);
                    };

                _showConstraintLayers = [this](const Layer* layer)
                    {
                        return
                            dynamic_cast<const TerrainConstraintLayer*>(layer) &&
                            layer->getUserProperty("show_in_ui", true);
                    };

                _showPred = _showVisibleLayers;
            }

            //! Sets a predicate that decides whether to include a layer in the GUI
            void setShowPredicate(std::function<bool(const Layer*)> func)
            {
                _showPred = func;
            }

            void load(const Config& conf) override
            {
                conf.get("ShowDisabled", _showDisabled);
                conf.get("SortByCategory", _sortByCat);
            }

            void save(Config& conf) override
            {
                conf.set("ShowDisabled", _showDisabled);
                conf.set("SortByCategory", _sortByCat);
            }

            void draw(osg::RenderInfo& ri) override
            {
                if (!isVisible())
                    return;

                bool mapNodeWasValid = _mapNode.valid();

                if (!findNodeOrHide(_mapNode, ri))
                    return;

                if (_first)
                {
                    EventRouter::get(view(ri))
                        .onMove([&](osg::View* v, float x, float y) { onMove(v, x, y); });
                    _first = false;
                }

                refreshMap(ri, mapNodeWasValid);

                if (ImGui::Begin(name(), visible(), ImGuiWindowFlags_MenuBar))
                {
                    drawAddLayerMenu(ri);

                    // Map name:
                    if (_mapNode->getMap()->getName().empty() == false)
                    {
                        ImGui::TextColored(ImVec4(1, 1, 0, 1), _mapNode->getMap()->getName().c_str());
                    }

                    ImGui::SameLine();

                    if (ImGui::Checkbox("Sort", &_sortByCat))
                        dirtySettings();

                    ImGui::SameLine();
                    if (ImGui::Checkbox("Closed", &_showDisabled))
                        dirtySettings();

                    ImGui::Separator();

                    if (_sortByCat)
                    {
                        for (int cat = 0; cat < NUM_CATS; ++cat)
                        {
                            auto& layers = _layersByCat[cat];
                            if (layers.empty())
                                continue;

                            if (ImGui::TreeNode(std::string(_layerCatName[cat] + " Layers").c_str()))
                            {
                                drawLayers(layers, ri);
                                ImGui::TreePop();
                            }
                        }
                    }
                    else
                    {
                        drawLayers(_layers, ri);
                    }

                    ImGui::End();
                }
            }

            void refreshMap(osg::RenderInfo& ri, bool mapNodeWasValid)
            {
                const Map* map = _mapNode->getMap();

                Revision rev = map->getDataModelRevision();
                if (rev != _mapRevision || !mapNodeWasValid)
                {
                    _layers.clear();
                    _layerExpanded.clear();

                    if (_showPred)
                        _mapRevision = map->getLayers(_layers, _showPred);
                    else
                        _mapRevision = map->getLayers(_layers);

                    for (auto& layer : _layers)
                        _layerExpanded[layer.get()] = false;

                    _maxMaxRanges.clear();
                    _maxMinRanges.clear();
                    _maxAttenRanges.clear();
                    for (auto layer : _layers)
                    {
                        VisibleLayer* v = dynamic_cast<VisibleLayer*>(layer.get());
                        if (v)
                        {
                            _maxMaxRanges[layer.get()] = v->getMaxVisibleRange() * 2.0f;
                            _maxMinRanges[layer.get()] = v->getMinVisibleRange() * 2.0f;
                            _maxAttenRanges[layer.get()] = v->getAttenuationRange() * 2.0f;
                        }
                    }

                    for (int cat = 0; cat < NUM_CATS; ++cat)
                        _layersByCat[cat].clear();

                    for (auto& layer : _layers)
                    {
#ifdef HAVE_OSGEARTHPROCEDURAL
                        if (dynamic_cast<osgEarth::Procedural::LifeMapLayer*>(layer.get()) ||
                            dynamic_cast<osgEarth::Procedural::VegetationLayer*>(layer.get()) ||
                            dynamic_cast<osgEarth::Procedural::TextureSplattingLayer*>(layer.get()) ||
                            dynamic_cast<osgEarth::Procedural::BiomeLayer*>(layer.get()))
                            _layersByCat[PROCEDURAL].push_back(layer);
                        else
#endif
                        if (dynamic_cast<ImageLayer*>(layer.get()))
                            _layersByCat[IMAGE].push_back(layer);
                        else if (dynamic_cast<ElevationLayer*>(layer.get()))
                            _layersByCat[ELEVATION].push_back(layer);
                        else if (dynamic_cast<FeatureModelLayer*>(layer.get()))
                            _layersByCat[FEATURE].push_back(layer);
                        else if (dynamic_cast<ModelLayer*>(layer.get()))
                            _layersByCat[MODEL].push_back(layer);
                        else if (dynamic_cast<TerrainConstraintLayer*>(layer.get()))
                            _layersByCat[CONSTRAINT].push_back(layer);
                        else if (dynamic_cast<FeatureSource*>(layer.get()))
                            _layersByCat[DATA].push_back(layer);
                        else
                            _layersByCat[OTHER].push_back(layer);
                    }
                }
            }

            void drawLayers(std::vector<osg::ref_ptr<Layer>>& layers, osg::RenderInfo& ri)
            {
                auto camera = view(ri)->getCamera();
                auto map = _mapNode->getMap();

                for (int i = layers.size() - 1; i >= 0; --i)
                {
                    osgEarth::Layer* layer = layers[i];

                    if (!_showDisabled && !layer->isOpen() && !layer->getOpenAutomatically())
                        continue;

                    ImGui::PushID(layer);
                    bool stylePushed = false;

                    bool error =
                        layer->getStatus().isError() &&
                        layer->getStatus().message() != "Layer closed" &&
                        layer->getStatus().message() != "Layer disabled";

                    if (error)
                        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(ImColor(255, 72, 72))), stylePushed = true;
                    else if (!layer->isOpen())
                        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(ImColor(127, 127, 127))), stylePushed = true;

                    auto visibleLayer = dynamic_cast<osgEarth::VisibleLayer*>(layer);

                    if (visibleLayer)
                    {
                        if (layer->isOpen())
                        {
                            bool visible = visibleLayer->getVisible();
                            if (ImGui::Checkbox("", &visible))
                            {
                                visibleLayer->setVisible(visible);
                            }
                        }
                        else // closed? the checkbox will try to open it.
                        {
                            bool dummy = false;
                            if (ImGui::Checkbox("", &dummy))
                            {
                                Registry::instance()->clearBlacklist();
                                layer->open();
                            }
                        }

                        ImGui::SameLine();

                        ImGui::PushID("selectable");
                        bool layerNameClicked = false;
                        if (layer->isOpen())
                        {
                            ImGui::Selectable(layer->getName().c_str(), &layerNameClicked);
                        }
                        else
                        {
                            std::string text = layer->getName() + " (closed)";
                            ImGui::Selectable(text.c_str(), &layerNameClicked);
                        }

                        if (layerNameClicked)
                        {
                            _layerExpanded[layer] = !_layerExpanded[layer];
                        }
                        ImGui::PopID(); // "selectable"
                    }
                    else
                    {
                        ImGui::Text(layer->getName().c_str());
                    }

                    if (_layerExpanded[layer])
                    {
                        ImGui::Indent();

                        ImGui::TextColored(ImVec4(.8, .8, .8, 1), "%s", layer->className());

                        if (layer->isOpen())
                        {
                            auto visibleLayer = dynamic_cast<osgEarth::VisibleLayer*>(layer);
                            auto tileLayer = dynamic_cast<osgEarth::TileLayer*>(layer);
                            auto imageLayer = dynamic_cast<osgEarth::ImageLayer*>(layer);
                            auto elevationLayer = dynamic_cast<osgEarth::ElevationLayer*>(layer);

                            if (tileLayer)
                            {
                                const Profile* profile = tileLayer->getProfile();
                                if (profile)
                                {
                                    std::string srsname = profile->getSRS()->getName();
                                    if (srsname == "unknown")
                                        srsname = profile->getSRS()->getHorizInitString();
                                    ImGui::TextColored(ImVec4(.8, .8, .8, 1), "%s", srsname.c_str());

                                    if (elevationLayer)
                                    {
                                        ImGui::SameLine();
                                        auto vdatum = profile->getSRS()->getVertInitString();
                                        if (vdatum.empty()) vdatum = "geodetic";
                                        ImGui::TextColored(ImVec4(1, .8, .8, 1), " (vdatum = %s)", vdatum.c_str());
                                    }
                                }
                            }

                            const GeoExtent& extent = layer->getExtent();
                            if (extent.isValid())
                            {
                                const std::string fmt[] = { "%5.1f", "%4.1f  *  %4.1f", "%1.f", "%.1f * %.1f" };
                                int i = _mapNode->getMapSRS()->isGeographic() ? 0 : 2;
                                ImGuiUtil::TextCentered(fmt[i].c_str(), extent.north());
                                ImGuiUtil::TextCentered(fmt[i + 1].c_str(), extent.west(), extent.east());
                                ImGuiUtil::TextCentered(fmt[i].c_str(), extent.south());
                            }

                            ImGuiLTable::Begin("Layer");

                            if (visibleLayer && !elevationLayer)
                            {
                                float opacity = visibleLayer->getOpacity();
                                if (ImGuiLTable::SliderFloat("Opacity", &opacity, 0.0f, 1.0f))
                                    visibleLayer->setOpacity(opacity);

                                if (visibleLayer->options().maxVisibleRange().isSet())
                                {
                                    float value = visibleLayer->getMaxVisibleRange();
                                    if (value < FLT_MAX) {
                                        if (ImGuiLTable::SliderFloat("Max range", &value, 0.0f, _maxMaxRanges[layer]))
                                            visibleLayer->setMaxVisibleRange(value);
                                    }
                                }

                                if (visibleLayer->options().minVisibleRange().isSet())
                                {
                                    float value = visibleLayer->getMinVisibleRange();
                                    if (ImGuiLTable::SliderFloat("Min range", &value, 0.0f, _maxMinRanges[layer]))
                                        visibleLayer->setMinVisibleRange(value);
                                }

                                if (visibleLayer->options().attenuationRange().isSet())
                                {
                                    float value = visibleLayer->getAttenuationRange();
                                    if (ImGuiLTable::SliderFloat("Attenuation range", &value, 0.0f, _maxAttenRanges[layer]))
                                        visibleLayer->setAttenuationRange(value);
                                }

                                bool debugView = visibleLayer->getEnableDebugView();
                                if (ImGuiLTable::Checkbox("Highlight", &debugView)) {
                                    visibleLayer->setEnableDebugView(debugView);
                                }
                            }

#ifdef HAVE_OSGEARTHCESIUM
                             auto cesiumNativeLayer = dynamic_cast<osgEarth::Cesium::CesiumNative3DTilesLayer*>(layer);
                            if (cesiumNativeLayer)
                            {
                                float sse = cesiumNativeLayer->getMaximumScreenSpaceError();
                                ImGui::PushID("sse");
                                ImGuiLTable::SliderFloat("SSE", &sse, 0.0f, 50.0f);
                                cesiumNativeLayer->setMaximumScreenSpaceError(sse);
                                ImGui::PopID();
                            }
#endif

                            auto threedTiles = dynamic_cast<osgEarth::Contrib::ThreeDTilesLayer*>(layer);
                            if (threedTiles)
                            {
                                float sse = threedTiles->getMaximumScreenSpaceError();
                                ImGui::PushID("sse");
                                ImGuiLTable::SliderFloat("SSE", &sse, 0.0f, 50.0f);
                                threedTiles->setMaximumScreenSpaceError(sse);
                                ImGui::PopID();

                                ImGui::PushID("debugVolumes");
                                bool showBoundingVolumes = threedTiles->getTilesetNode()->getShowBoundingVolumes();
                                ImGuiLTable::Checkbox("Show debug volumes", &showBoundingVolumes);
                                threedTiles->getTilesetNode()->setShowBoundingVolumes(showBoundingVolumes);
                                ImGui::PopID();

                                ImGui::PushID("debugColors");
                                bool colorPerTile = threedTiles->getTilesetNode()->getColorPerTile();
                                ImGuiLTable::Checkbox("Show color per tile", &colorPerTile);
                                threedTiles->getTilesetNode()->setColorPerTile(colorPerTile);
                                ImGui::PopID();
                            }

                            auto mapboxGLLayer = dynamic_cast<MapBoxGLImageLayer*>(layer);
                            if (mapboxGLLayer)
                            {
                                bool disableText = mapboxGLLayer->getDisableText();
                                if (ImGuiLTable::Checkbox("Disable text", &disableText))
                                {
                                    mapboxGLLayer->setDisableText(disableText);
                                }

                                float pixelScale = mapboxGLLayer->getPixelScale();
                                if (ImGuiLTable::InputFloat("Pixel Scale", &pixelScale))
                                {
                                    mapboxGLLayer->setPixelScale(pixelScale);
                                }
                            }

                            if (tileLayer)
                            {
                                if (tileLayer->options().minLevel().isSet())
                                {
                                    ImGuiLTable::Text("Min level", "%d", tileLayer->getMinLevel());
                                }
                                if (tileLayer->options().maxLevel().isSet())
                                {
                                    ImGuiLTable::Text("Max level", "%d", tileLayer->getMaxLevel());
                                }
                                if (tileLayer->options().maxDataLevel().isSet())
                                {
                                    ImGuiLTable::Text("Max data level", "%d", tileLayer->getMaxDataLevel());
                                }

                                if (tileLayer->options().upsample().isSet())
                                {
                                    bool upsampling = tileLayer->options().upsample().value();
                                    ImGuiLTable::Text("Upsampling", "%s", (upsampling ? "ON" : "off"));
                                }
                            }
                            
                            auto report = layer->reportStats();
                            for (auto& kv : report)
                            {
                                ImGuiLTable::Text(kv.first.c_str(), "%s", kv.second.c_str()); // (kv.first + ": " + kv.second).c_str());
                            }

                            const DateTimeExtent& dtextent = layer->getDateTimeExtent();
                            if (dtextent.valid())
                            {
                                //ImGui::Text("Time Series:");
                                ImGuiLTable::Text("Start time", "%s", dtextent.getStart().asISO8601().c_str());
                                ImGuiLTable::Text("End time", "%s", dtextent.getEnd().asISO8601().c_str());
                            }

                            ImGuiLTable::End();

                            if (imageLayer)
                            {
                                bool queryOn = (_mouseOverImageLayer == imageLayer);
                                if (ImGui::Checkbox("Show value under mouse", &queryOn))
                                {
                                    _mouseOverImageLayer = queryOn ? imageLayer : nullptr;
                                }

                                if (_mouseOverImageLayer == imageLayer)
                                {
                                    if (_imageLayerValueUnderMouse.isAvailable())
                                    {
                                        if (_imageLayerValueUnderMouse->isOK())
                                        {
                                            auto& value = _imageLayerValueUnderMouse->value();
                                            if (value.dataType == GL_UNSIGNED_BYTE)
                                            {
                                                auto& p = value.pixel;
                                                if (value.pixelFormat == GL_RED)
                                                {
                                                    ImGui::Text(" f32 (%.2f)", p.r());
                                                    ImGui::Text(" int (%d)", (int)(255.f * p.r()));
                                                    ImGui::Text(" hex (%.2X)", (int)(255.f * p.r()));
                                                }
                                                if (value.pixelFormat == GL_RG)
                                                {
                                                    ImGui::Text(" f32 (%.2f %.2f)", p.r(), p.g());
                                                    ImGui::Text(" int (%d %d)", (int)(255.f * p.r()), (int)(255.f * p.g()));
                                                    ImGui::Text(" hex (%.2X %.2X)", (int)(255.f * p.r()), (int)(255.f * p.g()));
                                                }
                                                else if (value.pixelFormat == GL_RGB)
                                                {
                                                    ImGui::Text(" f32 (%.2f %.2f %.2f)", p.r(), p.g(), p.b());
                                                    ImGui::Text(" int (%d %d %d)", (int)(255.f * p.r()), (int)(255.f * p.g()), (int)(255.0f * p.b()));
                                                    ImGui::Text(" hex (%.2X %.2X %.2X)", (int)(255.f * p.r()), (int)(255.f * p.g()), (int)(255.0f * p.b()));
                                                }
                                                else
                                                {
                                                    ImGui::Text(" f32 (%.2f %.2f %.2f %.2f)", p.r(), p.g(), p.b(), p.a());
                                                    ImGui::Text(" int (%d %d %d %d)", (int)(255.f * p.r()), (int)(255.f * p.g()), (int)(255.0f * p.b()), (int)(255.0f * p.a()));
                                                    ImGui::Text(" hex (%.2X %.2X %.2X %.2X)", (int)(255.f * p.r()), (int)(255.f * p.g()), (int)(255.0f * p.b()), (int)(255.0f * p.a()));
                                                }
                                            }
                                            else if (value.dataType == GL_FLOAT)
                                            {
                                                int v = (int)value.pixel.r();
                                                ImGui::Text(" f32 (%0.4f)", value.pixel.r());
                                                ImGui::Text(" int (%d)", v);
                                                ImGui::Text(" hex (%#.8X)", v);
                                            }
                                        }
                                        else
                                        {
                                            ImGui::Text("%s", _imageLayerValueUnderMouse->message().c_str());
                                        }
                                    }
                                    else
                                    {
                                        ImGui::Text("  (searching)");
                                        ImGui::Text("");
                                    }
                                }
                            }

                            if (visibleLayer)
                            {
                                if ((extent.isValid() && !extent.isWholeEarth()) ||
                                    (layer->getNode() && layer->getNode()->getBound().valid()) ||
                                    (dtextent.valid()))
                                {
                                    if (ImGui::Button("Zoom"))
                                    {
                                        if (extent.isValid())
                                        {
                                            std::vector<GeoPoint> points;
                                            points.push_back(GeoPoint(extent.getSRS(), extent.west(), extent.south()));
                                            points.push_back(GeoPoint(extent.getSRS(), extent.east(), extent.north()));

                                            ViewFitter fitter(_mapNode->getMap()->getSRS(), camera);
                                            Viewpoint vp;
                                            if (fitter.createViewpoint(points, vp))
                                            {
                                                auto manip = dynamic_cast<EarthManipulator*>(view(ri)->getCameraManipulator());
                                                if (manip) manip->setViewpoint(vp, 2.0);
                                            }
                                        }
                                        else if (layer->getNode())
                                        {
                                            ViewFitter fitter(map->getSRS(), camera);
                                            Viewpoint vp;
                                            if (fitter.createViewpoint(layer->getNode(), vp))
                                            {
                                                auto manip = dynamic_cast<EarthManipulator*>(view(ri)->getCameraManipulator());
                                                if (manip) manip->setViewpoint(vp, 2.0);
                                            }
                                        }

                                        if (dtextent.valid())
                                        {
                                            auto sky = osgEarth::findRelativeNodeOfType<SkyNode>(_mapNode.get());
                                            if (sky)
                                            {
                                                sky->setDateTime(dtextent.getStart());
                                            }
                                        }
                                    }

                                    ImGui::SameLine();
                                }

                                if (ImGui::Button("Refresh"))
                                {                                    
                                    layer->dirty();
                                    auto cp = layer->getCachePolicy();
                                    cp.minTime() = DateTime().asTimeStamp();
                                    layer->setCachePolicy(cp);
                                    std::vector<const Layer*> layers = { layer };
                                    _mapNode->getTerrainEngine()->invalidateRegion(layers, GeoExtent());
                                }

                                if (layer->isOpen())
                                {
                                    ImGui::SameLine();
                                    if (ImGui::Button("Close"))
                                    {
                                        layer->close();
                                    }
                                }

                                ImGui::SameLine();
                                if (ImGui::Button("JSON"))
                                {
                                    auto conf = layer->getConfig();
                                    std::cout << conf.toJSON(true) << std::endl;
                                }
                            }
                        }

                        else if (layer->getStatus().isError() && layer->getStatus().message() != "Layer closed")
                        {
                            ImGui::TextWrapped(layer->getStatus().message().c_str());
                        }

                        ImGui::Unindent();
                    }

                    ImGui::PopID();

                    if (stylePushed)
                        ImGui::PopStyleColor(1);

                    ImGui::Separator();
                }
            }

            void drawAddLayerMenu(osg::RenderInfo& ri)
            {
                osg::Camera* camera = ri.getCurrentCamera();
                if (ImGui::BeginMenuBar())
                {
                    if (ImGui::BeginMenu("Add Layer"))
                    {
#ifdef HAS_PFD
                        if (ImGui::MenuItem("Local File"))
                        {
                            auto f = pfd::open_file("Choose files to read", pfd::path::home(),
                                { "All Files", "*" },
                                pfd::opt::multiselect);

                            if (f.result().size() > 0)
                            {
                                auto m = pfd::message("Imagery",
                                    "Are these files imagery?  Select No for elevation.",
                                    pfd::choice::yes_no,
                                    pfd::icon::question);
                                bool imagery = m.result() == pfd::button::yes;

                                _mapNode->getMap()->beginUpdate();

                                if (imagery)
                                {
                                    for (auto const& name : f.result())
                                    {
                                        std::string ext = osgDB::getLowerCaseFileExtension(name);
                                        if (ext == "mbtiles")
                                        {
#ifdef OSGEARTH_HAVE_MBTILES
                                            MBTilesImageLayer* mbtilesImage = new MBTilesImageLayer;
                                            mbtilesImage->setName(osgDB::getSimpleFileName(name));
                                            mbtilesImage->setURL(name);
                                            _mapNode->getMap()->addLayer(mbtilesImage);
#else
                                            OE_WARN << "MBTiles driver not available" << std::endl;
#endif
                                        }
                                        else

                                        {
                                            GDALImageLayer* gdalImage = new GDALImageLayer;
                                            gdalImage->setName(osgDB::getSimpleFileName(name));
                                            gdalImage->setURL(name);
                                            _mapNode->getMap()->addLayer(gdalImage);
                                        }
                                    }
                                }
                                else
                                {
                                    for (auto const& name : f.result())
                                    {
                                        std::string ext = osgDB::getLowerCaseFileExtension(name);
                                        if (ext == "mbtiles")
                                        {
#ifdef OSGEARTH_HAVE_MBTILES
                                            MBTilesElevationLayer* mbtilesElevation = new MBTilesElevationLayer;
                                            mbtilesElevation->setName(osgDB::getSimpleFileName(name));
                                            mbtilesElevation->setURL(name);
                                            _mapNode->getMap()->addLayer(mbtilesElevation);
#else
                                            OE_WARN << "MBTiles driver not available" << std::endl;
#endif
                                        }
                                        else
                                        {
                                            GDALElevationLayer* gdalElevation = new GDALElevationLayer;
                                            gdalElevation->setName(osgDB::getSimpleFileName(name));
                                            gdalElevation->setURL(name);
                                            _mapNode->getMap()->addLayer(gdalElevation);
                                        }
                                    }
                                }
                                _mapNode->getMap()->endUpdate();
                            }
                        }
#endif
                        if (ImGui::MenuItem("TMS")) _addTMSDialog.visible = true;
                        if (ImGui::MenuItem("XYZ")) _addXYZDialog.visible = true;
                        if (ImGui::MenuItem("WMS")) _addWMSDialog.visible = true;

                        drawUsefulLayers();

                        ImGui::EndMenu();
                    }
                    ImGui::EndMenuBar();
                }

                // Draw the add dialogs
                _addXYZDialog.draw(_mapNode.get());
                _addTMSDialog.draw(_mapNode.get());
                _addWMSDialog.draw(_mapNode.get());
            }

            void drawUsefulLayers()
            {
                if (ImGui::BeginMenu("Useful Layers"))
                {
                    // ReadyMap Imagery
                    if (ImGui::MenuItem("ReadyMap Imagery"))
                    {
                        TMSImageLayer* readymap = new TMSImageLayer();
                        readymap->setName("ReadyMap Imagery");
                        readymap->setURL("http://readymap.org/readymap/tiles/1.0.0/7/");
                        _mapNode->getMap()->addLayer(readymap);
                    }

                    // ReadyMap Elevation
                    if (ImGui::MenuItem("ReadyMap Elevation"))
                    {
                        TMSElevationLayer* readymap = new TMSElevationLayer();
                        readymap->setName("ReadyMap Elevation");
                        readymap->setURL("http://readymap.org/readymap/tiles/1.0.0/116/");
                        _mapNode->getMap()->addLayer(readymap);
                    }

                    // OpenStreetMap
                    if (ImGui::MenuItem("OpenStreetMap"))
                    {
                        XYZImageLayer* osm = new XYZImageLayer();
                        osm->setName("OpenStreetMap");
                        osm->setURL("http://[abc].tile.openstreetmap.org/{z}/{x}/{y}.png");
                        osm->setProfile(osgEarth::Registry::instance()->getSphericalMercatorProfile());
                        osm->setAttribution("&#169;OpenStreetMap contributors");
                        _mapNode->getMap()->addLayer(osm);
                    }

                    if (ImGui::MenuItem("Debug"))
                    {
                        DebugImageLayer* debugImage = new DebugImageLayer;
                        debugImage->setName("Debug");
                        _mapNode->getMap()->addLayer(debugImage);
                    }

                    ImGui::EndMenu();
                }
            }

            void onMove(osg::View* view, float x, float y)
            {
                if (_mouseOverImageLayer)
                {
                    _imageLayerValueUnderMouse.reset();

                    TerrainTile* tile = _mapNode->getTerrain()->getTerrainTileUnderMouse(view, x, y);
                    if (tile)
                    {
                        GeoPoint p = _mapNode->getGeoPointUnderMouse(view, x, y);
                        TileKey key = _mouseOverImageLayer->getProfile()->createTileKey(p, tile->getKey().getLOD());
                        key = _mouseOverImageLayer->getBestAvailableTileKey(key);

                        if (key.valid())
                        {
                            _imageLayerValueUnderMouse = Job().dispatch<Result<ValueUnderMouse>>([this, key, p](Cancelable* c)
                                {
                                    ValueUnderMouse value;
                                    osg::ref_ptr<ProgressCallback> prog = new ProgressCallback(c);
                                    GeoImage g = _mouseOverImageLayer->createImage(key, prog.get());
                                    if (g.valid())
                                    {
                                        g.getReader().setBilinear(false);
                                        if (g.read(value.pixel, p))
                                        {
                                            value.pixelFormat = g.getImage()->getPixelFormat();
                                            value.dataType = g.getImage()->getDataType();
                                            return Result<ValueUnderMouse>(value);
                                        }
                                    }
                                    return Result<ValueUnderMouse>(Status::Error("No value"));
                                });
                        }
                    }
                }
            }
        };
    }
}

#endif // OSGEARTH_IMGUI_MAP_LAYERS_GUI
