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

#include <osgEarth/Common>
#include <osgEarth/Feature>
#include <osgEarth/FilterContext>
#include <osgEarth/GeoData>
#include <osg/Matrixd>
#include <vector>

namespace osgEarth
{
    //! Hint to a filter about the type of output to produce.
    enum FilterUsage
    {
        //! Generate optimized output, ready for rendering
        FILTER_USAGE_NORMAL = 0,

        //! Generate simple output with the expectation that it will be further processed
        //! later in the pipeline
        FILTER_USAGE_PARAMETRIC = 1,
        FILTER_USAGE_ZERO_WORK_CALLBACK_BASED = 1 // alias, compatibility with old code
    };
}

namespace osgEarth { namespace Util
{
    using namespace osgEarth;

    /**
     * Base class for a filter.
     */
    class OSGEARTH_EXPORT Filter : public osg::Object
    {
    public:
        META_Object(osgEarth, Filter);

    protected:
        virtual ~Filter();
        Filter() : osg::Object() { }
        Filter(const Filter& rhs, const osg::CopyOp& copyop) : osg::Object(rhs, copyop) { }
    };

    /**
     * Base class for feature filters.
     */
    class OSGEARTH_EXPORT FeatureFilter : public Filter
    {
    public:
        /**
         * Push a list of features through the filter.
         */
        virtual FilterContext push( FeatureList& input, FilterContext& context ) =0;

        /**
         * Optionally initialize the filter.
         */
        virtual Status initialize(const osgDB::Options* readOptions) { return Status::OK(); }

        /**
         * Serialize this FeatureFilter
         */
        virtual Config getConfig() const { return Config(); }

        /**
         * Called when the FeatureFilter is added to the map.
         */
        virtual void addedToMap(const class Map*);

    protected:
        FeatureFilter() { }
        FeatureFilter(const FeatureFilter& rhs, const osg::CopyOp& c) : Filter(rhs, c) { }
        virtual ~FeatureFilter();
    };

    //! Vector of feature filters (ref counted)
    class OSGEARTH_EXPORT FeatureFilterChain : public std::vector<osg::ref_ptr<FeatureFilter>>
    {
    public:
        static FeatureFilterChain create(
            const std::vector<ConfigOptions>& filters,
            const osgDB::Options* readOptions);

        const Status& getStatus() const { return _status; }

        FilterContext push(FeatureList& input, FilterContext& context) const {
            FilterContext temp = context;
            for (auto& filter : *this) {
                temp = filter->push(input, temp);
            }
            return temp;
        }

    private:
        Status _status;
    };

    /**
     * A Factory that can create a FeatureFilter from a Config
     */
    class OSGEARTH_EXPORT FeatureFilterFactory : public osg::Referenced
    {
    public:
        virtual FeatureFilter* create( const Config& conf ) = 0;
    };    

    using FeatureFilterFactoryList = std::vector<osg::ref_ptr<FeatureFilterFactory>>;

    /**
     * A registry of FeatureFilter plugins
     */
    class OSGEARTH_EXPORT FeatureFilterRegistry : public osg::Referenced
    {         
    public:
        /**
         * The singleton instance of the factory
         */
        static FeatureFilterRegistry* instance();

        /*
         * Adds a new FeatureFilterFactory to the list
         */
        void add( FeatureFilterFactory* factory );

        /**
         * Creates a FeatureFilter with the registered plugins from the given Config
         */
        FeatureFilter* create(const Config& conf, const osgDB::Options* dbo);

    protected:
        FeatureFilterRegistry();
        FeatureFilterFactoryList _factories;
    };

    template<class T>
    struct SimpleFeatureFilterFactory : public FeatureFilterFactory
    {
        SimpleFeatureFilterFactory(const std::string& key):_key(key){}

        virtual FeatureFilter* create(const Config& conf)
        {
            if (conf.key() == _key) return new T(conf);            
            return 0;
        }

        std::string _key;
    };

    template<class T>
    struct RegisterFeatureFilterProxy
    {
        RegisterFeatureFilterProxy( T* factory) { FeatureFilterRegistry::instance()->add( factory ); }
        RegisterFeatureFilterProxy() { FeatureFilterRegistry::instance()->add( new T ); }
    };

#define OSGEARTH_REGISTER_FEATUREFILTER( CLASSNAME )\
    extern "C" void osgearth_featurefilter_##KEY(void) {} \
    static osgEarth::RegisterFeatureFilterProxy<CLASSNAME> s_osgEarthRegisterFeatureFilterProxy_##CLASSNAME;
    
#define USE_OSGEARTH_FEATUREFILTER( KEY ) \
    extern "C" void osgearth_featurefilter_##KEY(void); \
    static osgDB::PluginFunctionProxy proxy_osgearth_featurefilter_##KEY(osgearth_featurefilter_##KEY);

#define OSGEARTH_REGISTER_SIMPLE_FEATUREFILTER( KEY, CLASSNAME)\
    extern "C" void osgearth_simple_featurefilter_##KEY(void) {} \
    static osgEarth::RegisterFeatureFilterProxy< osgEarth::SimpleFeatureFilterFactory<CLASSNAME> > s_osgEarthRegisterFeatureFilterProxy_##CLASSNAME##KEY(new osgEarth::SimpleFeatureFilterFactory<CLASSNAME>(#KEY));
    
#define USE_OSGEARTH_SIMPLE_FEATUREFILTER( KEY ) \
    extern "C" void osgearth_simple_featurefilter_##KEY(void); \
    static osgDB::PluginFunctionProxy proxy_osgearth_simple_featurefilter_##KEY(osgearth_simple_featurefilter_##KEY);
    

    //--------------------------------------------------------------------

    class OSGEARTH_EXPORT FeatureFilterDriver : public osgDB::ReaderWriter
    {
    protected:
        const ConfigOptions& getConfigOptions(const osgDB::Options* options) const;
    };

    /**
     * Base class for a filter that converts features into an osg Node.
     */
    class OSGEARTH_EXPORT FeaturesToNodeFilter : public Filter
    {
    public:
        virtual osg::Node* push( FeatureList& input, FilterContext& context ) =0;

    public:
        const osg::Matrixd& local2world() const { return _local2world; }
        const osg::Matrixd& world2local() const { return _world2local; }
                        
    protected:

        virtual ~FeaturesToNodeFilter();

        // computes the matricies required to localizer/delocalize double-precision coords
        void computeLocalizers( const FilterContext& context );
        void computeLocalizers( const FilterContext& context, const osgEarth::GeoExtent &extent, osg::Matrixd &out_w2l, osg::Matrixd &out_l2w );

        /** Parents the node with a localizer group if necessary */
        osg::Node*  delocalize( osg::Node* node ) const;
        osg::Node*  delocalize( osg::Node* node, const osg::Matrixd &local2World ) const;
        osg::Group* delocalizeAsGroup( osg::Node* node ) const;
        osg::Group* delocalizeAsGroup( osg::Node* node, const osg::Matrixd &local2World ) const;
        osg::Group* createDelocalizeGroup() const;
        osg::Group* createDelocalizeGroup( const osg::Matrixd &local2World) const;

        void transformAndLocalize(
            const std::vector<osg::Vec3d>& input,
            const SpatialReference*        inputSRS,
            osg::Vec3Array*                output,
            const SpatialReference*        outputSRS,
            const osg::Matrixd&            world2local,
            bool                           toECEF );

        void transformAndLocalize(
            const std::vector<osg::Vec3d>& input,
            const SpatialReference*        inputSRS,
            osg::Vec3Array*                out_verts,
            osg::Vec3Array*                out_normals,
            const SpatialReference*        outputSRS,
            const osg::Matrixd&            world2local,
            bool                           toECEF );

        void transformAndLocalize(
            const osg::Vec3d&              input,
            const SpatialReference*        inputSRS,
            osg::Vec3d&                    output,
            const SpatialReference*        outputSRS,
            const osg::Matrixd&            world2local,
            bool                           toECEF );

        void applyPointSymbology(osg::StateSet*, const class PointSymbol*);

        osg::Matrixd _world2local, _local2world;   // for coordinate localization
    };

} }

