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

#include <osgEarth/Filter>
#include <osgEarth/FeatureCursor>
#include <osgEarth/Layer>
#include <osgEarth/LayerReference>
#include <osgEarth/Status>
#include <set>

namespace osgEarth
{
    class Query;

    /**
     * Layer that provides raw feature data.
     */
    class OSGEARTH_EXPORT FeatureSource : public Layer
    {
    public: // serialization
        class OSGEARTH_EXPORT Options : public Layer::Options
        {
        public:
            META_LayerOptions(osgEarth, Options, Layer::Options);
            OE_OPTION(bool, openWrite, false);
            OE_OPTION(ProfileOptions, profile);
            OE_OPTION(GeoInterpolation, geoInterp);
            OE_OPTION(std::string, fidAttribute);
            OE_OPTION(bool, rewindPolygons, true);
            OE_OPTION(std::string, vdatum);
            OE_OPTION(Distance, bufferWidth);
            OE_OPTION(double, bufferWidthAsPercentage);
            OE_OPTION(bool, autoFID, false);
            OE_OPTION_VECTOR(ConfigOptions, filters);
            Config getConfig() const override;
            void fromConfig(const Config& conf);
        };

    public:
        META_Layer_Abstract(osgEarth, FeatureSource, Options, Layer);

        //! Open the source in a writable mode, if supported
        void setOpenWrite(bool value);
        bool getOpenWrite() const;

        //! Set the geo-interpolation method to use if applicable
        void setGeoInterpolation(GeoInterpolation value);
        GeoInterpolation getGeoInterpolation() const;

        //! Set the name of the attribute containing the feature ID
        void setFIDAttribute(const std::string& value);
        const std::string& getFIDAttribute() const;

        //! Sets whether to automatically rewind polygons to have the correct orientation
        void setRewindPolygons(bool value);
        bool getRewindPolygons() const;

    public: // Layer

        void init() override;

        Status openImplementation() override;

        //! Extents of this layer, if known
        const GeoExtent& getExtent() const override;

        void addedToMap(const class Map*) override;
        void removedFromMap(const class Map*) override;

        //! Marks the source as dirty and wipes any memory caches
        void dirty() override;

    public:

        //! Creates a cursor that iterates over all features corresponding to the
        //! specified Query.
        osg::ref_ptr<FeatureCursor> createFeatureCursor(
            const Query& query = {},
            const FeatureFilterChain& filters = {},
            FilterContext* context = nullptr,
            ProgressCallback* progress = nullptr) const;

        //! Creates and initializes a new feature source for writing
        virtual const Status& create(
            const FeatureProfile* profile,
            const FeatureSchema& schema,
            const Geometry::Type& geometryType,
            const osgDB::Options* readOptions);

        //! Gets a vector of keys required to cover the input key and
        //! a buffering distance.
        unsigned getKeys(
            const TileKey& key,
            const Distance& buffer,
            std::set<TileKey>& output) const;

        //! Gets a reference to the metadata that describes features that you can
        //! get from this FeatureSource. A valid feature profile indiciates that the
        //! feature source successfully initialized.
        const FeatureProfile* getFeatureProfile() const;

        //! Sets the feature profile for this source.
        //! This is required. Usually the subclass should call this from open().
        const FeatureProfile* setFeatureProfile(const FeatureProfile* profile);

        //! Whether this FeatureSource supports inserting and deleting features
        virtual bool isWritable() const { return false; }

        //! Deletes the feature with the given FID
        //! return True on success; false on failure or if the source is read-only
        virtual bool deleteFeature(FeatureID fid) { return false; }

        //! Gets the number of features in this FeatureSource
        //! @return Number of features or -1 if the number of features cannot be determined.
        virtual int getFeatureCount() const { return -1; }

        //! Whether the source can look up a Feature by its ID.
        //! @return True or False
        virtual bool supportsGetFeature() const { return false; }

        //! Gets the Feature with the given FID
        //! @return Feature with the given FID or NULL if not found.
        virtual Feature* getFeature( FeatureID fid ) { return 0L; }

        //! Gets the FeatureSchema for this FeatureSource. If the source doesn't
        //! populate a schema, this might be empty.
        const FeatureSchema& getSchema() const { return _schema; }

        //! Inserts the given feature into the FeatureSource
        //! @return  True if the feature was inserted, false if not
        virtual bool insertFeature(Feature* feature) { return false; }

        //! Gets the Geometry type of the FeatureSource
        //! @return Geometry type of the FeatureSource
        virtual Geometry::Type getGeometryType() const { return Geometry::TYPE_UNKNOWN; }

        //! Returns true if this source creates features with embedded style information.
        //! By default, this is false (features are not expected to carry their own
        //! style definitions).
        virtual bool hasEmbeddedStyles() const { return false; }

        //! Accesses the list of feature filters that will transform features
        //! before they are returned in a feature cursor.
        const FeatureFilterChain& getFilters() const;

        //! Adds a feature ID to the blacklist.
        void addToBlacklist( FeatureID fid );

        //! Removes a feature from the blacklist.
        void removeFromBlacklist( FeatureID fid );

        //! Clears the blacklist.
        void clearBlacklist();

        //! Checks the blacklist for a feature ID.
        bool isBlacklisted( FeatureID fid ) const;

        //! Build (or rebuild) a disk-based spatial index.
        virtual void buildSpatialIndex() { }


    protected:
        osg::ref_ptr<const FeatureProfile> _featureProfile;
        FeatureSchema _schema;
        URIContext _uriContext;
        mutable Threading::ReadWriteMutex  _blacklistMutex;
        std::unordered_set<FeatureID> _blacklist;
        unsigned _blacklistSize;

        using FeaturesLRU = LRUCache<std::string, FeatureList>;
        mutable std::unique_ptr<FeaturesLRU> _featuresCache;

        //! Implements the feature cursor creation
        virtual FeatureCursor* createFeatureCursorImplementation(
            const Query& query,
            ProgressCallback* progress) const =0;

        //! Opportunity for a subclass to complete replace the feature
        //! cursor returned from createFeatureCursor (mainly used for patches)
        virtual FeatureCursor* createPatchFeatureCursor(
            const Query& query,
            ProgressCallback* progress) const { return nullptr; }

    private:
        FeatureFilterChain _filters;

        //! Convenience function to apply the filters to a FeatureList
        void applyFilters(FeatureList& features, const GeoExtent& extent) const;

    };

    /**
    * A TiledFeatureSource is a FeatureSource that provides features
    * in a tiled format. It is used to store and retrieve features
    * based on tile keys.
    *
    * It also suppors a "patch", which is a separate TiledFeatureSource
    * that can be used to store features that are not part of the
    * main TiledFeatureSource.
    */
    class OSGEARTH_EXPORT TiledFeatureSource : public FeatureSource
    {
    public:
        class OSGEARTH_EXPORT Options : public FeatureSource::Options {
            META_LayerOptions(osgEarth, Options, FeatureSource::Options);
            OE_OPTION(int, minLevel);
            OE_OPTION(int, maxLevel);
            OE_OPTION_VECTOR(std::string, layers);
            OE_OPTION_LAYER(TiledFeatureSource, patch);
            Config getConfig() const override;
            void fromConfig(const Config& conf);
        };

        META_Layer_Abstract(osgEarth, TiledFeatureSource, Options, FeatureSource);

        void setPatchFeatureSource(TiledFeatureSource*);

    public:

        Status openImplementation() override;
        Status closeImplementation() override;
        void addedToMap(const Map*) override;
        void dirty() override;

    public:

        //! Minimium tile LOD of data that this source provides
        void setMinLevel(int);
        int getMinLevel() const;

        //! Maximium tile LOD of data that this source provides
        void setMaxLevel(int);
        int getMaxLevel() const;

        //! Insert a full tile of features (if the layer is writable)
        //! @param key TileKey of the tile to insert
        //! @param features Features comprising the tile
        //! @param overwrite If true, overwrite existing features
        //! @return True on success.
        virtual Status insert(const TileKey& key, const FeatureList& features, bool overwrite) {
            return Status::ServiceUnavailable;
        }

        //! Does this layer have an active patch installed?
        bool hasTilePatch() const;

        //! Insert a full tile of features as a patch (if one is configured)
        //! @param key TileKey of the tile to insert
        //! @param features Features comprising the tile
        //! @param overwrite If true, overwrite existing features in the patch
        //! @return True on success.
        Status insertPatch(const TileKey& key, const FeatureList& features, bool overwrite);

    protected:
        FeatureCursor* createPatchFeatureCursor(
            const Query& query,
            ProgressCallback* progress) const override;
    };
}

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::FeatureSource::Options);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::TiledFeatureSource::Options);