/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
 * Copyright 2019 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/>
 */
#ifndef OSGEARTHFEATURES_FILTER_H
#define OSGEARTHFEATURES_FILTER_H 1

#include <osgEarthFeatures/Common>
#include <osgEarthFeatures/Feature>
#include <osgEarthFeatures/FilterContext>
#include <osg/Matrixd>
#include <list>


namespace osgEarth { namespace Features
{
    using namespace osgEarth;

    /**
     * Base class for a filter.
     */
    class OSGEARTHFEATURES_EXPORT Filter : public osg::Object
    {
    public:
        META_Object(osgEarthFeatures, 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 OSGEARTHFEATURES_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(); }

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

    //! Vector of feature filters (ref counted)
    class FeatureFilterChain : public osg::Referenced,
                               public osg::MixinVector<osg::ref_ptr<FeatureFilter> >
    {
    };

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

    typedef std::list< osg::ref_ptr< FeatureFilterFactory > > FeatureFilterFactoryList;

    /**
     * A registry of FeatureFilter plugins
     */
    class OSGEARTHFEATURES_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::Features::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::Features::RegisterFeatureFilterProxy< osgEarth::Features::SimpleFeatureFilterFactory<CLASSNAME> > s_osgEarthRegisterFeatureFilterProxy_##CLASSNAME##KEY(new osgEarth::Features::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 OSGEARTHFEATURES_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 OSGEARTHFEATURES_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
    };

} } // namespace osgEarth::Features

#endif // OSGEARTHFEATURES_FILTER_H
