/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */

#ifndef OSGEARTH_UTIL_RTT_PICKER_H
#define OSGEARTH_UTIL_RTT_PICKER_H 1

#include <osgEarth/Common>
#include <osgEarth/Picker>
#include <osgEarth/VirtualProgram>
#include <osg/Group>
#include <osg/Image>
#include <osg/Texture2D>
#include <osgViewer/View>
#include <queue>
#include <list>

namespace osgEarth { namespace Util
{
    /**
     * Picks objects using an RTT camera and Vertex Attributes.
     *
     * Note. The Picker will change the View Slave configuration in OSG,
     * so you should call Viewer::stopThreading() before adding or
     * removing a picker, and Viewer::startThreading when you're done.
     */
    class OSGEARTH_EXPORT RTTPicker : public osgEarth::Picker
    {
    public:
        /**
         * Creates a new RTT-based object picker.
         * @param cameraSize Size of the RTT picking viewpoint.
         */
        RTTPicker(int cameraSize =256);

        /**
         * Number of pixels on each side of the clicked pixel to check for hits.
         */
        void setBuffer(int value) { _buffer = value; }
        int getBuffer() const { return _buffer; }
        
        /**
         * Sets a default callback to use when installing the Picker as an EventHandler
         * or when calling pick() with no callback.
         */
        void setDefaultCallback(Callback* value) { _defaultCallback = value; }

        /**
         * Convenience function that invokes "pick" with no callback, which will cause
         * this picker to use the default callback installed with setDefaultCallback.
         */
        bool pick(osg::View* view, float mouseX, float mouseY);


    public: // osgEarth::Picker

        /**
         * Starts a pick operation. When the operation completes, invokes the callback
         * with the results. You can use this method if you want to use the picker directly
         * WITHOUT installing it as an EventHandler. If you use it as an EventHandler, you
         * do NOT need to call this method directly; the Picker will call it upon handling
         * a pick event (i.e., when Callback::accept returns true).
         *
         * Returns true if the pick was successfully queued; false if not.
         */
        virtual bool pick(osg::View* view, float mouseX, float mouseY, Callback* callback);


    public: // osgGA::GUIEventHandler

        virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);


    public: // simulate osg::Group

        virtual bool addChild(osg::Node* child);
        virtual bool insertChild(unsigned i, osg::Node* child);
        virtual bool removeChild(osg::Node* child);
        virtual bool replaceChild(osg::Node* oldChild, osg::Node* newChild);

    public: // simulate osg::Camera cull mask methods

        /** Specifies the cull mask for the RTT camera */
        void setCullMask(osg::Node::NodeMask nm);
        osg::Node::NodeMask getCullMask() const { return _cullMask; }


    public: // for debugging

        /** For debugging only - creates (if nec.) and returns a texture that captures
            the RTT image so you can display it. */
        osg::Texture2D* getOrCreateTexture(osg::View* View);

    protected:

        /** dtor */
        virtual ~RTTPicker();

        // builds the shaders for rendering to the pick camera. 
        VirtualProgram* createRTTProgram();
        
        int                    _rttSize;     // size of the RTT image (pixels per side)
        int                    _buffer;      // buffer around pick point to check (pixels)
        osg::Node::NodeMask    _cullMask;    // cull mask applied to the camera
        osg::ref_ptr<Callback> _defaultCallback;

        // Associates a view and a pick camera for that view.
        struct PickContext
        {
            osg::observer_ptr<osg::View> _view;
            osg::ref_ptr<osg::Camera>    _pickCamera;
            osg::ref_ptr<osg::Image>     _image;
            osg::ref_ptr<osg::Texture2D> _tex;
            int _numPicks;
        };
        // use a container that does not invalidate iters on insertion, since we hold
        // pointers to PickContext objects in the _picks member
        typedef std::list<PickContext> PickContexts;
        PickContexts _pickContexts;
        
        // Creates a new pick context on demand.
        PickContext& getOrCreatePickContext(osg::View* view);
        
        // A single pick operation (within a pick context).
        struct Pick
        {
            float                  _u, _v;
            osg::ref_ptr<Callback> _callback;
            unsigned               _frame;
            PickContext*           _context;
        };
        std::vector<Pick> _picks;

        // Runs the queue of picks given a frame number.
        void runPicks(unsigned frameNumber);

        // Checks to see if a pick succeeded and fires appropriate callback.
        bool checkForPickResult(Pick& pick, unsigned frameNumber);

        // container for common RTT pick camera children (see addChild et al.)
        osg::ref_ptr<osg::Group> _group;
    };
} }

#endif // OSGEARTH_UTIL_RTT_PICKER_H
