/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */

#ifndef OSGEARTH_TILE_LAYER_H
#define OSGEARTH_TILE_LAYER_H 1

#include <osgEarth/Common>
#include <osgEarth/CachePolicy>
#include <osgEarth/Config>
#include <osgEarth/VisibleLayer>
#include <osgEarth/Profile>
#include <osgEarth/Threading>
#include <osgEarth/Status>
#include <osgEarth/MemCache>

namespace osgEarth
{
    class Cache;
    class CacheBin;

    /**
     * A layer that comprises the terrain skin (image or elevation layer)
     */
    class OSGEARTH_EXPORT TileLayer : public VisibleLayer
    {
    public: // serialization
        class OSGEARTH_EXPORT Options : public VisibleLayer::Options {
        public:
            META_LayerOptions(osgEarth, Options, VisibleLayer::Options);
            OE_OPTION(unsigned, minLevel, 0u);
            OE_OPTION(unsigned, maxLevel, 23u);
            OE_OPTION(unsigned, maxDataLevel, 99u);
            OE_OPTION(double, minResolution);
            OE_OPTION(double, maxResolution);
            OE_OPTION(unsigned, tileSize, 256u);
            OE_OPTION(float, noDataValue, -32767.0f); // SHRT_MIN
            OE_OPTION(float, minValidValue, -32766.0f); // -(2^15 - 2)
            OE_OPTION(float, maxValidValue, 32767.0f); // 2^15 - 1
            OE_OPTION(bool, upsample, false);
            OE_OPTION(ProfileOptions, profile);
            virtual Config getConfig() const;
        private:
            void fromConfig( const Config& conf );
        };

    public:
        META_Layer_Abstract(osgEarth, TileLayer, Options, VisibleLayer);

        //! Minimum of detail for which this layer should generate data.
        void setMinLevel(unsigned value);
        unsigned getMinLevel() const;

        //! Minimum resolution for which this layer should generate data.
        void setMinResolution(double value);
        double getMinResolution() const;

        //! Maximum level of detail for which this layer should generate data.
        //! Data from this layer will not appear in map tiles above the maxLevel.
        void setMaxLevel(unsigned value);
        unsigned getMaxLevel() const;

        //! Maximum level resolution for which this layer should generate data.
        //! The value is in units per pixel, using the base units of the layer's source data.
        void setMaxResolution(double value);
        double getMaxResolution() const;

        //! Maximum level of detail for which this layer should generate new data.
        //! Data from this layer will be upsampled in map tiles above the maxDataLevel.
        void setMaxDataLevel(unsigned value);
        unsigned getMaxDataLevel() const;

        //! Whether to algorithmically upsample data to higher resolution
        //! as specified by maxDataLevel.
        //! Warning: don't use this without a cache (performance)
        void setUpsample(bool value);
        bool getUpsample() const;

        //! Number of samples in each dimension.
        void setTileSize(unsigned value);
        unsigned getTileSize() const;

        //! Value to treat as a "no data" marker.
        void setNoDataValue(float value);
        void resetNoDataValue();
        virtual float getNoDataValue() const;

        //! Treat any value less than this as a "no data" marker.
        void setMinValidValue(float value);
        void resetMinValidValue();
        virtual float getMinValidValue() const;

        //! Treat any value greater than this as a "no data" marker.
        void setMaxValidValue(float value);
        void resetMaxValidValue();
        virtual float getMaxValidValue() const;

    protected:

        //! DTOR
        virtual ~TileLayer();

        //! Tiling profile of this layer.
        //! Layer implementaions will call this to set the profile based
        //! on information gathered from source metadata. If your Layer
        //! needs the user to expressly set a profile, override this to
        //! make it public.
        virtual void setProfile(const Profile* profile);

    public: // Layer

        //! Open the layer for writing (calls open)
        const Status& openForWriting();

        //! Does the layer support writing?
        virtual bool isWritingSupported() const { return false; }

        //! Goes the layer support pre-encoded data writing?
        virtual bool isEncodingSupported() const { return false; }

        //! Did the user open this layer for writing?
        bool isWritingRequested() const { return _writingRequested; }

        //! Tiling profile for this layer
        const Profile* getProfile() const;

        /**
         * Whether the layer represents dynamic data, i.e. it generates data that requires
         * an update traversal.
         */
        virtual bool isDynamic() const;

        /**
         * Whether the data for the specified tile key is in the cache.
         */
        virtual bool isCached(const TileKey& key) const;

        /**
         * Disable this layer, setting an error status.
         */
        void disable(const std::string& msg);


    public: // Data availability methods

        /**
         * Given a TileKey, returns a TileKey representing the best known available.
         * For example, if the input TileKey exceeds the layer's max LOD, the return
         * value will be an ancestor key at that max LOD.
         *
         * If a setting that effects the visible range of this layer is set (minLevel, maxLevel, minResolution or maxResolution)
         * then any key passed in that falls outside of the valid range for the layer will return TileKey::INVALID.
         *
         * @param key Tile key to check
         * @param considerUpsampling Normally this method will only return a key
         *    corresponding to possible real data. If you set this to true, it may
         *    also return a TileKey that may correspond to upsampled data.
         */
        virtual TileKey getBestAvailableTileKey(
            const TileKey& key,
            bool considerUpsampling =false) const;

        /**
         * Whether the layer possibly has real data for the provided TileKey.
         * Best guess given available information.
         */
        virtual bool mayHaveData(const TileKey& key) const;

        /**
         * Whether the given key falls within the range limits set in the options;
         * i.e. min/maxLevel or min/maxResolution. (This does not mean that the key
         * will result in data.)
         */
        virtual bool isKeyInLegalRange(const TileKey& key) const;

        /**
         * Same as isKeyInLegalRange, but ignores the "maxDataLevel" setting
         * since that does NOT affect visibility of a tile.
         */
        virtual bool isKeyInVisualRange(const TileKey& key) const;

        /**
         * Data Extents reported for this layer are copied into output.
         */
        virtual void getDataExtents(DataExtentList& dataExtents) const;

        /**
        * Gets the number of data extents on the layer.
        */
        unsigned int getDataExtentsSize() const;

        /**
         * Gets an extent that is the union of all the extents in getDataExtents().
         */
        const DataExtent& getDataExtentsUnion() const;

        //! Assign a data extents collection to the layer
        virtual void setDataExtents(const DataExtentList& dataExtents);

        //! Adds a DataExent to this layer.
        void addDataExtent(const DataExtent& dataExtent);

    public: // Layer interface

        //! Extent of this layer's data.
        virtual const GeoExtent& getExtent() const;

        //! Called by Map when added
        virtual void addedToMap(const Map*);

        //! Called by Map when removed
        virtual void removedFromMap(const Map*);

    public:

        /**
         * Metadata about the terrain layer that is stored in the cache, and read
         * when the cache is opened.
         */
        struct OSGEARTH_EXPORT CacheBinMetadata : public osg::Referenced
        {
            CacheBinMetadata();

            CacheBinMetadata( const CacheBinMetadata& rhs );

            CacheBinMetadata( const Config& conf );

            bool isOK() const { return _valid; }

            Config getConfig() const;

            bool                     _valid;
            optional<std::string>    _cacheBinId;
            optional<std::string>    _sourceName;
            optional<std::string>    _sourceDriver;
            optional<int>            _sourceTileSize;
            optional<ProfileOptions> _sourceProfile;
            optional<ProfileOptions> _cacheProfile;
            optional<TimeStamp>      _cacheCreateTime;
            DataExtentList           _dataExtents;
        };

        //! Access to information about the cache
        CacheBinMetadata* getCacheBinMetadata(const Profile* profile);

        //! Sets up a small data cache if necessary.
        void setUpL2Cache(unsigned minSize =0u);

        //! Call this if you call dataExtents() and modify it.
        void dirtyDataExtents();

    protected: // Layer

        virtual void init() override;
        virtual Status openImplementation() override;
        virtual Status closeImplementation() override;

    protected:

        //! Opportunity for a subclass to alter and/or override components
        //! of the Profile
        virtual void applyProfileOverrides(osg::ref_ptr<const Profile>& inoutProfile) const { }

        //! Gets or create a caching bin to use with data in the supplied profile
        CacheBin* getCacheBin(const Profile* profile);

    protected:

        osg::ref_ptr<MemCache> _memCache;
        bool _writingRequested;

        // profile to use
        mutable osg::ref_ptr<const Profile> _profile;

        // cache key for metadata
        std::string getMetadataKey(const Profile*) const;

    private:
        // general purpose data protector
        mutable ReadWriteMutex _data_mutex;

        DataExtentList _dataExtents;
        mutable DataExtent _dataExtentsUnion;
        mutable void* _dataExtentsIndex;

        // The cache ID used at runtime. This will either be the cacheId found in
        // the TileLayerOptions, or a dynamic cacheID generated at runtime.
        std::string _runtimeCacheId;

        // cache policy that may be automatically set by the layer and will
        // override the runtime options policy if set.
        optional<CachePolicy> _runtimeCachePolicy;

        using CacheBinMetadataMap = std::unordered_map<std::string, osg::ref_ptr<CacheBinMetadata>>;
        CacheBinMetadataMap _cacheBinMetadata;

        // methods accesible by Map:
        friend class Map;

        // Figure out the cache settings for this layer.
        void establishCacheSettings();
    };

    typedef std::vector<osg::ref_ptr<TileLayer> > TileLayerVector;

} // namespace TileLayer

#endif // OSGEARTH_TILE_LAYER_H
