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

#include <osgEarth/FeatureSource>
#include <queue>
#include <thread>

namespace osgEarth
{
    namespace Util {
        struct OGRFeatureFactory;
    }

    /**
     * Feature Layer that accesses features via one of the many GDAL/OGR drivers.
     */
    class OSGEARTH_EXPORT OGRFeatureSource : public FeatureSource
    {   
    public: // serialization
        class OSGEARTH_EXPORT Options : public FeatureSource::Options
        {
        public:
            META_LayerOptions(osgEarth, Options, FeatureSource::Options);
            OE_OPTION(URI, url);
            OE_OPTION(std::string, connection);
            OE_OPTION(std::string, ogrDriver);
            OE_OPTION(bool, buildSpatialIndex);
            OE_OPTION(bool, forceRebuildSpatialIndex);
            OE_OPTION(Config, geometryConfig);
            OE_OPTION(URI, geometryUrl);
            OE_OPTION(std::string, layer);
            OE_OPTION(Query, query);
            virtual Config getConfig() const;
        private:
            void fromConfig(const Config& conf);
        };

    public:
        META_Layer(osgEarth, OGRFeatureSource, Options, FeatureSource, OGRFeatures);

        //! Location of the data resource
        void setURL(const URI& value);
        const URI& getURL() const;

        //! Database connection string (alterative to URL)
        void setConnection(const std::string& value);
        const std::string& getConnection() const;

        //! Whether to build a spatial index after opening the resource (if supported)
        void setBuildSpatialIndex(const bool& value);
        const bool& getBuildSpatialIndex() const;

        //! Specific OGR driver to use (default is ESRI Shapefile)
        void setOGRDriver(const std::string& value);
        const std::string& getOGRDriver() const;

        //! Driver-specific layer to use (to access subdatasets)
        void setLayer(const std::string& layer);
        const std::string& getLayer() const;

        //! Query expression to use when accessing data source
        void setQuery(const Query& value);
        const Query& getQuery() const;

        //! URL of inline geometry to load.
        void setGeometryURL(const URI& value);
        const URI& getGeometryURL() const;

        //! Sets an inline geometry to use.
        void setGeometry(const Geometry* geom) { _geometry = geom; }
        const Geometry* getGeometry() const { return _geometry.get(); }

        //! Tiling profile of feature data
        void setProfile(const Profile* profile) { _profile = profile; }
        const Profile* getProfile() const { return _profile.get(); }

    protected: // Layer

        void init() override;

        Status openImplementation() override;

        Status closeImplementation() override;

    public: // FeatureSource

        FeatureCursor* createFeatureCursorImplementation(const Query& query, ProgressCallback* progress) const override;

        bool deleteFeature(FeatureID fid) override;

        int getFeatureCount() const override;

        bool supportsGetFeature() const override;

        Feature* getFeature( FeatureID fid ) override;

        bool isWritable() const override;
    
        bool insertFeature(Feature* feature) override;

        osgEarth::Geometry::Type getGeometryType() const override;

        const Status& create(
                const FeatureProfile* profile,
                const FeatureSchema& schema,
                const Geometry::Type& geometryType,
                const osgDB::Options* readOptions);

        virtual void buildSpatialIndex();

        //! Call this if the underlying geometry changes and we need to
        //! recompute the profile.
        void dirty() override;

    protected:

        virtual ~OGRFeatureSource();

        // parses an explicit WKT geometry string into a Geometry.
        Geometry* parseGeometry( const Config& geomConf );

        // read the WKT geometry from a URL, then parse into a Geometry.
        Geometry* parseGeometryUrl( const URI& geomUrl, const osgDB::Options* dbOptions );

        void initSchema();

    private:
        osg::ref_ptr<const Profile> _profile;
        osg::ref_ptr<const Geometry> _geometry; // explicit geometry.
        std::string _source;
        void* _dsHandle;
        void* _layerHandle;
        void* _ogrDriverHandle;
        std::thread::id _dsHandleThreadId;
        int _featureCount;
        bool _needsSync;
        bool _writable;
        Geometry::Type _geometryType;
    };

    namespace OGR
    {
        //! Internal class - do not use directly
        class OGRFeatureCursor : public FeatureCursor
        {
        public:
            //! Create a feature cursor that can query data from a layer.
            OGRFeatureCursor(
                void*                     dsHandle,
                void*                     layerHandle,
                const FeatureSource*      source,
                const FeatureProfile*     featureProfile,
                const Query&              query,
                const FeatureFilterChain& filters,
                bool                      rewindPolygons,
                unsigned                  chunkSize,
                ProgressCallback*         progress
                );

            //! Create a feature cursor that will just iterate over
            //! the results in a prepopulated result set.
            OGRFeatureCursor(
                void* resultSet,
                const FeatureProfile* featureProfile);

        public: // FeatureCursor

            bool hasMore() const override;
            Feature* nextFeature() override;

        protected:
            virtual ~OGRFeatureCursor();

        private:
            void* _dsHandle = nullptr;
            void* _layerHandle = nullptr;
            void* _resultSetHandle = nullptr;
            void* _spatialFilter = nullptr;
            unsigned _chunkSize = 500u;
            void* _nextHandleToQueue = nullptr;
            Query _query;
            osg::ref_ptr<const FeatureSource> _source;
            osg::ref_ptr<const FeatureProfile> _featureProfile;
            std::queue< osg::ref_ptr<Feature> > _queue;
            osg::ref_ptr<Feature> _lastFeatureReturned;
            const FeatureFilterChain _filters;
            bool _resultSetEndReached = false;
            bool _rewindPolygons = true;
            Util::OGRFeatureFactory* _factory = nullptr;

        private:
            void readChunk();
        };
    }


} // namespace osgEarth

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::OGRFeatureSource::Options);
