/* osgEarth
* Copyright 2025 Pelican Mapping
* MIT License
*/
#pragma once

#include <osgEarth/Common>
#include <osgEarth/Map>
#include <osgEarth/Extension>
#include <osgEarth/TerrainOptions>

namespace osg
{
    class View;
}

namespace osgEarth
{
    class Registry;
    class Terrain;
    class TerrainEngineNode;
    class SpatialReference;
    class DrapableNode;
    class ClampableNode;

    namespace Util
    {
        class DrapingManager;
        class ClampingManager;
        class DrapingTechnique;
        class ClampingTechnique;
    }

    /**
     * OSG Node that forms the root of an osgEarth map.
     * This node is a "view" component that renders data from a "Map" data model.
     */
    class OSGEARTH_EXPORT MapNode : public osg::Group
    {
    public: // serialization data
        class OSGEARTH_EXPORT Options : public ConfigOptions {
        public:
            META_ConfigOptions(osgEarth, Options, ConfigOptions);
            OE_OPTION(int, drapingRenderBinNumber, 1);
            OE_OPTION(bool, enableLighting, true);
            OE_OPTION(bool, overlayBlending, true);
            OE_OPTION(bool, overlayMipMapping, false);
            OE_OPTION(bool, useCascadeDraping, false);
            OE_OPTION(float, overlayResolutionRatio, 3.0f);
            OE_OPTION(float, screenSpaceError, 25.0f);
            OE_OPTION(unsigned, overlayTextureSize, 4096);
            OE_OPTION(std::string, overlayBlendingSource, "alpha");
            OE_OPTION(TerrainOptions, terrain);
            OE_OPTION(ProxySettings, proxySettings);
            OE_OPTION(unsigned, nodePagerConcurrency, 6u);
            virtual Config getConfig() const;
        private:
            void fromConfig(const Config& conf);
        };

    public: // static loaders

        //! Loads a MapNode form a ".earth" file in the arguemnts list
        static MapNode* load(class osg::ArgumentParser& arguments);

        //! Loads a MapNode from a ".earth" file in the arguments list
        static MapNode* load(
            class osg::ArgumentParser& arguments,
            const Options& options);

    public: // constructors

        //! Creates an empty map node (with a default empty Map).
        MapNode();

        //! Creates an empty map node with some intialization options
        MapNode(const Options& options);

        //! Creates a map node that will render the given Map.
        MapNode(Map* map);

        //! Creates a map node that will render the given Map.
        MapNode(Map* map, const Options& options);

    public:

        //! Map this node is rendering.
        Map* getMap();
        const Map* getMap() const;

        //! Screen-space error for geometry level of detail
        void setScreenSpaceError(float sse);
        float getScreenSpaceError() const;

        //! Whether to allow lighting when present
        void setEnableLighting(const bool& value);
        const bool& getEnableLighting() const;

        //! Access to the terrain-specific options API.
        TerrainOptionsAPI getTerrainOptions();

        //! Spatial Reference System of the underlying map.
        const SpatialReference* getMapSRS() const;

        //! API for for querying the in-memory terrain scene graph directly.
        Terrain* getTerrain();
        const Terrain* getTerrain() const;

        /**
         * Finds the topmost Map node in the specified scene graph, or returns NULL if
         * no Map node exists in the graph.
         *
         * @param graph
         *      Node graph in which to search for a MapNode
         * @param travMask
         *      Traversal mask to apply while searching
         */
        static MapNode* findMapNode(osg::Node* graph, unsigned travMask = ~0);
        static MapNode* get(osg::Node* graph, unsigned travMask = ~0) { return findMapNode(graph, travMask); }
        static MapNode* get(osg::ref_ptr<osg::Node>& graph, unsigned travMask = ~0) { return findMapNode(graph.get(), travMask); }

        /**
         * Returns true if the realized terrain model is geocentric, false if
         * it is flat/projected.
         */
        bool isGeocentric() const;

        /**
         * Accesses the group node that contains all the nodes added by Layers.
         */
        osg::Group* getLayerNodeGroup() const;

        /**
         * Gets the underlying terrain engine that renders the terrain surface of the map.
         */
        TerrainEngine* getTerrainEngine() const;

        /**
         * Gets the Config object serializing external data. External data is information
         * that osgEarth itself does not control, but that an app can include in the
         * MapNode for the purposes of including it in a .earth file.
         */
        Config& externalConfig() { return _externalConf; }
        const Config& externalConfig() const { return _externalConf; }

        /**
         * Adds an Extension and calls its startup method with this MapNode.
         */
        void addExtension(Extension* extension, const osgDB::Options* options =0L);

        /**
         * Removes an extension, and calls its shutdown method with this MapNode.
         */
        void removeExtension(Extension* extension);

        /**
         * Removes all extensions, calling each one's shutdown method this this MapNode.
         */
        void clearExtensions();

        /**
         * Access the extensions vector.
         */
        const std::vector< osg::ref_ptr<Extension> >& getExtensions() const { return _extensions; }

        /**
         * Find an extension by type and return it.
         */
        template<typename T>
        T* getExtension() const {
            for(std::vector< osg::ref_ptr<Extension> >::const_iterator i = _extensions.begin(); i != _extensions.end(); ++i) {
                T* e = dynamic_cast<T*>(i->get());
                if ( e ) return e;
            }
            return 0L;
        }

        //! Opens all layers that are not already open.
        void openMapLayers();

        //! Serializes the MapNode into a Config object
        Config getConfig() const;

        //! Opens the map (installs a terrain engine and initializes all the layers)
        bool open();

        //! Returns the map coordinates under the provided mouse (window) coordinates.
        //! @param view View in which to do the query
        //! @param mx, my Mouse coordinates
        //! @param out_point Outputs the point under the mouse (when returning true)
        //! @return true upon success, false upon failure
        bool getGeoPointUnderMouse(
            osg::View*  view,
            float       mx,
            float       my,
            GeoPoint&   out_point) const;

        //! Returns the map coordinates under the provided mouse (window) coordinates.
        //! @param view View in which to do the query
        //! @param mx, my Mouse coordinates
        //! @return Outputs the point under the mouse
        GeoPoint getGeoPointUnderMouse(
            osg::View* view,
            float mx,
            float my) const;

    public: // osg::Object

        const char* libraryName() const override { return "osgEarth"; }
        const char* className() const override { return "MapNode"; }

    public: //override osg::Node

        osg::BoundingSphere computeBound() const override;

        void traverse(class osg::NodeVisitor& nv) override;

        void resizeGLObjectBuffers(unsigned maxSize) override;

        void releaseGLObjects(osg::State* state) const override;

    protected:    

        virtual ~MapNode();

    private:

        osg::ref_ptr<Map>  _map;
        osg::Group*        _layerNodes;
        unsigned           _lastNumBlacklistedFilenames;
        Config             _externalConf;
        osg::Group*        _terrainGroup;
        std::shared_ptr<DrapingManager> _drapingManager;
        ClampingManager*   _clampingManager;
        std::atomic<bool>  _readyForUpdate;

        // yes, this must be a ref_ptr so observers can point to it
        // in setMap().
        osg::ref_ptr<TerrainEngineNode> _terrainEngine;

        std::vector< osg::ref_ptr<Extension> > _extensions;

        bool _isOpen;

    public: // MapCallback proxy

        void onLayerAdded(Layer* layer, unsigned index);
        void onLayerRemoved(Layer* layer, unsigned index);
        void onLayerMoved(Layer* layer, unsigned oldIndex, unsigned newIndex);

    private:

        osg::ref_ptr< MapCallback > _mapCallback;
        osg::ref_ptr<osg::Uniform> _sseU;
    
        void init();

        //std::shared_ptr<DrapingManager>& getDrapingManager();
        friend class DrapingTechnique;
        friend class DrapeableNode;
        
        ClampingManager* getClampingManager();
        friend class ClampingTechnique;
        friend class ClampableNode;

        struct PerCameraData {
            osg::ref_ptr<osg::StateSet> stateSet;
            osg::ref_ptr<osg::Uniform> dprU;
        };
        mutable PerObjectFastMap<osg::Camera*, PerCameraData> _percam;

    private:
        Options _optionsConcrete;
        Options& options() { return _optionsConcrete; }

    public:
        const Options& options() const { return _optionsConcrete; }
    };

} // namespace osgEarth
