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

#include <osgEarth/Common>
#include <osgEarth/Feature>
#include <osgEarth/Filter>
#include <osgEarth/Style>
#include <osgEarth/Containers>

namespace osgEarth { namespace Util
{
    class ResourceLibrary;

    /**
     * Feature filter that will substitute external nodes for feature geometry.
     */
    class OSGEARTH_EXPORT SubstituteModelFilter : public FeaturesToNodeFilter
    {
    public:
        // Call this determine whether this filter is available.
        static bool isSupported() { return true; }

    public:
        /** Construct a new sub-model filter that will operate on the given style */
        SubstituteModelFilter( const Style& style =Style() );

        virtual ~SubstituteModelFilter() { }

        /** Whether to cluster all the model instances into a single geode. Default is false. */
        void setClustering( bool value ) { _cluster = value; }
        bool getClustering() const { return _cluster; }

        /** Whether to convert model instances to use "DrawInstanced" instead of transforms. Default is false */
        void setUseDrawInstanced( bool value ) { _useDrawInstanced = value; }
        bool getUseDrawInstanced() const { return _useDrawInstanced; }

        /** Whether to merge marker geometries into geodes */
        void setMergeGeometry( bool value ) { _merge = value; }
        bool getMergeGeometry() const { return _merge; }

        void setFeatureNameExpr( const StringExpression& expr ) { _featureNameExpr = expr; }
        const StringExpression& getFeatureNameExpr() const { return _featureNameExpr; }

        //! In PARAMETRIC mode, tells the filter to generate separate node data and matrices for input, instead of
        //! creating a fully optimized renderable output.
        void setFilterUsage(FilterUsage usage) { _filterUsage = usage; }
        FilterUsage getFilterUsage(void) const{ return _filterUsage; }

    public:
        /** Processes a new feature list */
        virtual osg::Node* push( FeatureList& input, FilterContext& context );

    protected:
        FilterUsage                   _filterUsage;
        Style                         _style;
        bool                          _cluster;
        bool                          _useDrawInstanced;
        bool                          _merge;
        StringExpression              _featureNameExpr;
        osg::ref_ptr<ResourceLibrary> _resourceLib;
        bool                          _normalScalingRequired;

        using InstanceCache = LRUCache<URI, osg::ref_ptr<InstanceResource>>;
        InstanceCache _instanceCache{ 128u };
        
        bool process(const FeatureList& features, const InstanceSymbol* symbol, Session* session, osg::Group* ap, FilterContext& context );
        
        bool findResource( const URI& instanceURI, const InstanceSymbol* symbol, FilterContext& context, std::set<URI>& missing, osg::ref_ptr<InstanceResource>& output );
    };
} }

namespace osgEarth
{
    /**
    * Container Node for storing information about extruded geometry
    * without rendering it. An app can invoke the ExtrudeGeometryFilter
    * with FilterUsage=... to collect this information.
    */
    class OSGEARTH_EXPORT SubstituteModelFilterNode : public osg::Node
    {
    public:
        struct ModelSymbol
        {
            URI instanceURI;
            osg::Matrixd xform;
        };

        SubstituteModelFilterNode() = default;

        SubstituteModelFilterNode(const SubstituteModelFilterNode& rhs, const osg::CopyOp& copyop) :
            osg::Node(rhs, copyop),
            _instanced(rhs._instanced),
            _clustered(rhs._clustered),
            _modelSymbolList(rhs._modelSymbolList) { }

        META_Node(osgEarth, SubstituteModelFilterNode);


        typedef std::list<ModelSymbol> ModelSymbolList;

        ModelSymbolList& modelSymbolList()
        {
            return _modelSymbolList;
        }
        const ModelSymbolList& modelSymbolList() const
        {
            return _modelSymbolList;
        }
        bool getInstanced(void) const { return _instanced; }
        void setInstanced(bool instanced) { _instanced = instanced; }

        bool getClustered(void) const { return _clustered; }
        void setClustered(bool clustered) { _clustered = clustered; }

        osg::BoundingSphere computeBound() const override {
            osg::BoundingSphere bs;
            for (auto& entry : _modelSymbolList) {
                bs.expandBy(osg::Vec3d(0, 0, 0) * entry.xform);
            }
            return bs;
        }

    private:
        bool _instanced = false;
        bool _clustered = false;
        ModelSymbolList _modelSymbolList;
    };
}
