/* -*-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/>
 */
#pragma once

#include <osgEarthImGui/ImGuiApp>
#include <osgEarth/ObjectIDPicker>
#include <osgEarth/MetadataNode>

namespace osgEarth
{
    struct PickerGUI : public ImGuiPanel
    {
        PickerGUI() : ImGuiPanel("Picker") { }

        const char* highlight_shader = R"(
            #pragma vp_function check_for_highlight, vertex_clip
            uniform uint objectid_to_highlight;
            uint oe_index_objectid;      // Stage global containing object id
            flat out int selected;
            void check_for_highlight(inout vec4 vertex)
            {
                selected = (objectid_to_highlight > 1u && objectid_to_highlight == oe_index_objectid) ? 1 : 0;
            }

            [break]
            #pragma vp_function highlight_fragment, fragment
            flat in int selected;
            void highlight_fragment(inout vec4 color)
            {
                if ( selected == 1 )
                    color.rgb = mix(color.rgb, clamp(vec3(0.5,2.0,2.0)*(1.0-color.rgb), 0.0, 1.0), 0.5);
            }
        )";

        void installHighlighter()
        {
            osg::StateSet* stateSet = _mapNode->getOrCreateStateSet();
            int attrLocation = Registry::objectIndex()->getObjectIDAttribLocation();

            // This shader program will highlight the selected object.
            VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
            ShaderLoader::load(vp, highlight_shader);

            // Since we're accessing object IDs, we need to load the indexing shader as well:
            Registry::objectIndex()->loadShaders(vp);

            // A uniform that will tell the shader which object to highlight:
            _highlightUniform = new osg::Uniform("objectid_to_highlight", 0u);
            stateSet->addUniform(_highlightUniform);
        }

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

            if (ImGui::Begin(name(), visible()))
            {
                if (!_mapNode.valid())
                {
                    _mapNode = findNode<MapNode>(ri);
                    _installedPicker = false;
                }

                if (!_installedPicker)
                {
                    _picker = new ObjectIDPicker();
                    _picker->setView(view(ri));  // which view to pick?
                    _picker->setGraph(_mapNode.get()); // which graph to pick?
                    _mapNode->addChild(_picker); // put it anywhere in the graph

                    ObjectIDPicker::Function pick = [&](ObjectID id)
                        {
                            if (id > 0)
                            {
                                // Got a pick:
                                FeatureIndex* index = Registry::objectIndex()->get<FeatureIndex>(id).get();
                                Feature* feature = index ? index->getFeature(id) : 0L;
                                _pickedFeature = feature;
                                _pickedAnnotation = Registry::objectIndex()->get<AnnotationNode>(id).get();
                                _highlightUniform->set(id);
                            }
                            else
                            {
                                // No pick:
                                _pickedFeature = nullptr;
                                _pickedAnnotation = nullptr;
                                _highlightUniform->set(0u);
                            }
                        };

                    // Call our handler when hovering over the map
                    _picker->onHover(pick);

                    // Highlight features as we pick'em.
                    installHighlighter();

                    // To display the contents of the pick camera in the imgui panel
                    setupPreviewCamera();

                    _installedPicker = true;
                }

                if (ImGui::Checkbox("Picker active", &_active))
                {
                    _picker->setNodeMask(_active ? ~0 : 0);
                }

                if (_active)
                {
                    if (ImGui::Checkbox("RTT preview", &_preview))
                        dirtySettings();

                    if (_preview && _previewTexture)
                    {
                        osg::Texture2D* pickTex = _picker->getOrCreateTexture();
                        if (pickTex)
                        {
                            if (_previewStateSet->getTextureAttribute(0, osg::StateAttribute::TEXTURE) != pickTex)
                                _previewStateSet->setTextureAttribute(0, pickTex, 1);

                            ImGui::Text("Picker camera preview:");
                            ImGuiEx::OSGTexture(_previewTexture, ri);
                        }
                    }

                    if (_pickedFeature.valid())
                    {
                        ImGui::Separator();
                        ImGui::Text("Picked Feature:");
                        ImGuiLTable::Begin("picked feature", ImGuiTableFlags_Borders);
                        ImGuiLTable::Text("FID", "%ld", _pickedFeature->getFID());
                        for (auto& attr : _pickedFeature->getAttrs())
                        {
                            ImGuiLTable::Text(attr.first.c_str(), "%s", attr.second.getString().c_str());
                        }
                        ImGuiLTable::End();
                    }

                    else if (_pickedAnnotation.valid())
                    {
                        ImGui::Text("Picked Annotation:");
                        ImGui::Indent();
                        {
                            ImGui::Text("Object name = %s", _pickedAnnotation->getName().c_str());
                            ImGui::Text("Object type = %s", typeid(*_pickedAnnotation).name());
                        }
                        ImGui::Unindent();
                    }
                }
            }
            ImGui::End();
        }

        // A lot of code just to re-color the picker's rtt camera into visible colors :)
        // We are just taking the pick texture and re-rendering it to another quad
        // with a new shader so we can amplify the colors.
        void setupPreviewCamera()
        {
            // simple fragment shader to recolor a texture
            const char* pick_preview = R"(
                #pragma vp_function pick_preview_vs, vertex_clip
                out vec2 uv;
                void pick_preview_vs(inout vec4 clip) {
                    const vec2 uvs[6] = vec2[6](
                        vec2(0,0), vec2(1,1), vec2(0,1),
                        vec2(1,1), vec2(0,0), vec2(1,0)
                    );
                    uv = uvs[gl_VertexID];
                    clip = vec4(uv*2-1, 0, 1);
                }

                [break]
                #pragma vp_function pick_preview_fs, fragment_output
                in vec2 uv;
                out vec4 frag;
                uniform sampler2D tex;
                void pick_preview_fs(inout vec4 c) {
                    c = texture(tex, uv);
                    frag = c==vec4(0)? vec4(1) : vec4(vec3((c.r+c.g+c.b+c.a)/4.0),1);
                }
            )";

            osg::Geometry* geom = new osg::Geometry();
            _previewStateSet = geom->getOrCreateStateSet();
            geom->setCullingActive(false);
            geom->setUseVertexBufferObjects(true);
            geom->setUseDisplayList(false);
            geom->setVertexArray(new osg::Vec3Array(6));
            geom->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES, 0, 6));
            _previewStateSet->addUniform(new osg::Uniform("tex", 0));

            VirtualProgram* vp = VirtualProgram::getOrCreate(_previewStateSet);
            ShaderLoader::load(vp, pick_preview);

            _previewTexture = new osg::Texture2D();
            _previewTexture->setTextureSize(256, 256);
            _previewTexture->setSourceFormat(GL_RGBA);
            _previewTexture->setSourceType(GL_UNSIGNED_BYTE);
            _previewTexture->setInternalFormat(GL_RGBA8);

            osg::Camera* cam = new osg::Camera();
            cam->addChild(geom);
            cam->setClearColor(osg::Vec4(1, 0, 0, 1));
            cam->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            cam->setViewport(0, 0, 256, 256);
            cam->setRenderOrder(osg::Camera::POST_RENDER);
            cam->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
            cam->setImplicitBufferAttachmentMask(0, 0);
            cam->attach(osg::Camera::COLOR_BUFFER, _previewTexture);

            _mapNode->addChild(cam);
        }

        bool _active = true;
        bool _preview = false;
        osg::observer_ptr<MapNode> _mapNode;
        bool _installedPicker = false;
        osgEarth::Util::ObjectIDPicker* _picker;
        osg::Uniform* _highlightUniform;
        osg::ref_ptr<const Feature> _pickedFeature;
        osg::ref_ptr<AnnotationNode> _pickedAnnotation;
        osg::ref_ptr<osg::Texture2D> _previewTexture;
        osg::ref_ptr<osg::StateSet> _previewStateSet;
    };
}